Skip to content

Commit

Permalink
[pkg/velox-luna]: Implement smart diff (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fryuni authored Mar 24, 2024
1 parent 7930803 commit 328f5c9
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 31 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-lamps-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@inox-tools/velox-luna": minor
---

Implement auto-diff with renames
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,10 @@
"turbo": "^1.12.5",
"typescript": "^5.4.2"
},
"packageManager": "pnpm@8.15.5+sha256.4b4efa12490e5055d59b9b9fc9438b7d581a6b7af3b5675eb5c5f447cee1a589"
"packageManager": "pnpm@8.15.5+sha256.4b4efa12490e5055d59b9b9fc9438b7d581a6b7af3b5675eb5c5f447cee1a589",
"pnpm": {
"patchedDependencies": {
"@lunariajs/core@0.0.31": "patches/@lunariajs__core@0.0.31.patch"
}
}
}
11 changes: 11 additions & 0 deletions packages/velox-luna/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<p align="center">
<img alt="InoxTools" width="350px" src="https://github.com/Fryuni/inox-tools/blob/main/assets/shield.png?raw=true"/>
</p>

# Velox Luna

Workflow automation for [Lunaria](https://lunaria.dev/).

### License

Velox Luna is available under the MIT license.
2 changes: 0 additions & 2 deletions packages/velox-luna/index.ts

This file was deleted.

25 changes: 14 additions & 11 deletions packages/velox-luna/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,28 @@
"i18n",
"cli"
],
"repository": "https://github.com/Fryuni/inox-tools.git",
"license": "MIT",
"author": "Luiz Ferraz <luiz@lferraz.com>",
"bin": {
"velox-luna": "index.js"
"velox-luna": "./dist/index.js",
"@inox-tools/velox-luna": "./dist/index.js"
},
"files": [
"index.js",
"index.js.map"
"README.md",
"dist",
"src"
],
"scripts": {
"build": "esbuild --bundle index.ts --outdir=dist --sourcemap",
"test": "pnpm run build && node dist/index.js"
},
"dependencies": {
"@lunariajs/core": "^0.0.31"
"build": "tsup",
"dev": "tsup --watch",
"prepublish": "pnpm run build",
"test": "echo 'No tests yet'"
},
"devDependencies": {
"esbuild": "^0.20.1",
"typescript": "^5.4.2",
"vitest": "^1.3.1"
"@lunariajs/core": "^0.0.31",
"tsup": "^8.0.2",
"typescript": "^5.4.3",
"vitest": "^1.4.0"
}
}
43 changes: 43 additions & 0 deletions packages/velox-luna/src/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { parseArgs } from 'node:util';

export function parseCommand() {
const {
positionals: [locale, targetFile, other],
values: { config: configPath, rebuild = false },
} = parseArgs({
options: {
config: {
type: 'string',
multiple: false,
short: 'c',
default: './lunaria.config.json',
},
rebuild: {
type: 'boolean',
multiple: false,
short: 'r',
default: false,
},
},
allowPositionals: true,
strict: true,
});

if (locale === undefined) {
throw new Error('Missing locale argument');
}

if (targetFile === undefined) {
throw new Error('Missing targetFile argument');
}

if (other !== undefined) {
throw new Error('Unexpected argument: ' + other);
}

if (!configPath) {
throw new Error('Missing config argument.');
}

return { locale, targetFile, configPath, rebuild };
}
56 changes: 56 additions & 0 deletions packages/velox-luna/src/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { git } from '@lunariajs/core/git';
import { spawn } from 'node:child_process';

export async function getAllFileRenames(
currentPath: string,
sinceCommit: string
): Promise<string[]> {
const res = await git.raw([
'log',
'--name-status',
'--diff-filter=R',
'--pretty=format:',
`${sinceCommit}...HEAD`,
]);

const fileRenames = res
.trim()
.split('\n')
.filter((line) => line.charAt(0) === 'R')
.map((line) => line.split('\t'))
.map(([, from, to]) => ({ from, to }));

let latestName = currentPath;
const previousNames: string[] = [];

for (const { from, to } of fileRenames) {
if (latestName === to) {
previousNames.push(from);
latestName = from;
}
}

return previousNames;
}

export async function showDiffForFile(filePath: string, sinceCommit: string): Promise<void> {
const oldNames = await getAllFileRenames(filePath, sinceCommit);

await new Promise<void>((resolve, reject) => {
const cmd = spawn(
'git',
['diff', '--find-renames', `${sinceCommit}...HEAD`, '--', filePath, ...oldNames],
{
stdio: 'inherit',
}
);

cmd.once('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`git diff exited with code ${code}`));
}
});
});
}
41 changes: 41 additions & 0 deletions packages/velox-luna/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { parseCommand } from './command.js';
import { showDiffForFile } from './diff.js';
import { getCommitRange, getTargetStatus } from './status.js';

async function main() {
const command = parseCommand();

const targetStatus = await getTargetStatus(command);
const commitRange = await getCommitRange(targetStatus);

await showDiffForFile(targetStatus.sourceFile, commitRange.from);

/*
BRANCH_SLUG="i18n/$(echo "$TARGET_FILE" | iconv -t ascii//TRANSLIT | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | tr A-Z a-z)"
echo "Branch slug: $BRANCH_SLUG"
git checkout main || true
git branch -D "$BRANCH_SLUG" || true
git checkout -b "$BRANCH_SLUG" main
git diff --find-renames "$LATEST_TRANSLATED_COMMIT...HEAD" -- "$ORIGINAL_FILE"
git add "$TRANSLATED_FILE"
git commit -m "i18n($TARGET_LANGUAGE): Update \`$TARGET_FILE\`" -- "$TRANSLATED_FILE"
git push --set-upstream origin "$BRANCH_SLUG"
*/
}

process.setSourceMapsEnabled(true);

main().catch((error) => {
// eslint-disable-next-line no-console
console.error(error);

process.exit(1);
});
88 changes: 88 additions & 0 deletions packages/velox-luna/src/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { loadConfig, type LunariaConfig } from '@lunariajs/core/config';
import { git } from '@lunariajs/core/git';
import { lunaria, type LocalizationStatus, type GitHistory } from '@lunariajs/core';
import { join } from 'node:path';
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import type { parseCommand } from './command.js';

type CommitRange = {
from: string;
to: string;
};

export async function getCommitRange(target: TargetStatus): Promise<CommitRange> {
const latestTranslatedCommit = await getCommitHashFromInfo(target);

const from = latestTranslatedCommit;
const to = 'HEAD';

return { from, to };
}

async function getCommitHashFromInfo(target: TargetStatus): Promise<string> {
if (!!target.git.lastMajorCommitHash) return target.git.lastMajorCommitHash;

const res = await git.raw([
'git',
'log',
'--follow',
'--format=%H',
`--since="${target.git.lastMajorChange}"`,
'--',
target.localizedFile,
]);

return res.trim().split('\n').at(-1)!.trim();
}

type TargetStatus = {
sourceFile: string;
localizedFile: string;
git: GitHistory;
};

export async function getTargetStatus(
command: ReturnType<typeof parseCommand>
): Promise<TargetStatus> {
const { userConfig } = await loadConfig(command.configPath);

const status = await getStatus(userConfig, command.rebuild);

const targetFileStatus = status.find((s) => s.sharedPath.endsWith(command.targetFile));

if (targetFileStatus === undefined) {
throw new Error(`Could not find status for target file: ${command.targetFile}`);
}

const localizedStatus = targetFileStatus.localizations[command.locale];

if (localizedStatus === undefined || localizedStatus.isMissing) {
throw new Error(`Could not find localization for locale: ${command.locale}`);
}

return {
sourceFile: targetFileStatus.sourceFile.path,
localizedFile: localizedStatus.path,
git: localizedStatus.git,
};
}

async function getStatus(config: LunariaConfig, rebuild: boolean): Promise<LocalizationStatus[]> {
const statusPath = join(config.outDir, 'status.json');

if (!rebuild) {
const prebuiltContent = await readFile(statusPath, 'utf-8').catch(() => null);

if (prebuiltContent !== null) {
return JSON.parse(prebuiltContent);
}
}

const newProcessedStatus = await lunaria(config);

// Ensure output directory exists
await mkdir(config.outDir, { recursive: true });
await writeFile(statusPath, JSON.stringify(newProcessedStatus, null, 2));

return newProcessedStatus;
}
17 changes: 17 additions & 0 deletions packages/velox-luna/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defineConfig } from 'tsup';

export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs'],
target: 'node18',
banner: { js: '#!/usr/bin/env node' },
bundle: true,
sourcemap: true,
clean: true,
splitting: true,
minify: false,
external: [],
noExternal: ['@lunariajs/core'],
treeshake: 'smallest',
tsconfig: 'tsconfig.json',
});
31 changes: 31 additions & 0 deletions patches/@lunariajs__core@0.0.31.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
diff --git a/dist/shared/core.1a197e9d.d.mts b/dist/shared/core.1a197e9d.d.mts
index e82d99d71e00d1f611ee2ee690de6ac572dbac39..c41b68b8b2f3aac0cf1ff9a162f3af2797cb7b49 100644
--- a/dist/shared/core.1a197e9d.d.mts
+++ b/dist/shared/core.1a197e9d.d.mts
@@ -602,8 +602,10 @@ type LunariaUserRendererConfig = z.input<typeof LunariaRendererConfigSchema>;
type GitHistory = {
lastChange: string;
lastCommitMessage: string;
+ lastCommitHash?: string;
lastMajorChange: string;
lastMajorCommitMessage: string;
+ lastMajorCommitHash?: string;
};
type GitHosting = {
gitHostingFileURL: string;
diff --git a/dist/status/index.mjs b/dist/status/index.mjs
index 828072dae0523cb49bbe72464a866b397923f67b..02e5e7d75bdf529a47a3787c908c4ccd52fa6671 100644
--- a/dist/status/index.mjs
+++ b/dist/status/index.mjs
@@ -268,8 +268,10 @@ async function getFileData(filePath, isSourceLocale, isShallowRepo, rootDir, loc
git: {
lastChange: toUtcString(lastCommit.date),
lastCommitMessage: lastCommit.message,
+ lastCommirHash: lastCommit.hash,
lastMajorChange: toUtcString(lastMajorCommit.date),
- lastMajorCommitMessage: lastMajorCommit.message
+ lastMajorCommitMessage: lastMajorCommit.message,
+ lastMajorCommitHash: lastMajorCommit.hash
}
};
}
Loading

0 comments on commit 328f5c9

Please sign in to comment.