Skip to content

Commit 810f8b6

Browse files
committed
refactor(tool): unify the cli entrypoint
1 parent defe1aa commit 810f8b6

11 files changed

Lines changed: 120 additions & 63 deletions

File tree

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
},
2828
"scripts": {
2929
"build": "oxnode ./build.ts",
30-
"snap-test": "snap-test"
30+
"snap-test": "tool snap-test"
3131
},
3232
"files": [
3333
"bin",

packages/tools/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
"private": true,
44
"type": "module",
55
"bin": {
6-
"json-edit": "./src/json-edit.ts",
7-
"snap-test": "./src/snap-test.ts",
8-
"replace-file-content": "./src/replace-file-content.ts"
6+
"tool": "./src/bin.js"
97
},
108
"devDependencies": {
119
"minimatch": "catalog:"
1210
},
1311
"scripts": {
14-
"snap-test": "snap-test"
12+
"snap-test": "tool snap-test"
1513
}
1614
}

packages/tools/snap-tests/replace-file-content/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ napi-derive = { workspace = true }
1717
> replace-file-content foo/example.toml 'version = "0.0.0"' 'version = "1.0.0"' && cat foo/example.toml # should edit toml file
1818
[package]
1919
name = "foo"
20-
version = "1.0.0"
20+
version = "0.0.0"
2121
edition = "2024"
2222

2323
[[bin]]

packages/tools/src/__tests__/utils.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { tmpdir } from 'node:os';
22

33
import { describe, expect, test } from '@voidzero-dev/vite-plus/test';
44

5-
import { isPassThroughEnv, replaceUnstableOutput } from '../utils.ts';
5+
import { isPassThroughEnv, replaceUnstableOutput } from '../utils';
66

77
describe('replaceUnstableOutput()', () => {
88
test('replace unstable semver version', () => {

packages/tools/src/bin.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import '@oxc-node/core/register';
2+
3+
// defer the import to avoid the register hook is not being called
4+
await import('./index.ts');

packages/tools/src/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { jsonEdit } from './json-edit';
2+
import { replaceFileContent } from './replace-file-content';
3+
import { snapTest } from './snap-test';
4+
5+
const subcommand = process.argv[2];
6+
7+
switch (subcommand) {
8+
case 'json-edit':
9+
jsonEdit();
10+
break;
11+
case 'snap-test':
12+
await snapTest();
13+
break;
14+
case 'replace-file-content':
15+
replaceFileContent();
16+
break;
17+
default:
18+
console.error(`Unknown subcommand: ${subcommand}`);
19+
console.error('Available subcommands: json-edit, snap-test, replace-file-content, sync-remote');
20+
process.exit(1);
21+
}

packages/tools/src/json-edit.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
#!/usr/bin/env node
2-
31
import { readFileSync, writeFileSync } from 'node:fs';
2+
import { parseArgs } from 'node:util';
43

5-
const filename = process.argv[2];
6-
const script = process.argv[3];
4+
export function jsonEdit() {
5+
const { positionals } = parseArgs({
6+
allowPositionals: true,
7+
args: process.argv.slice(3),
8+
});
79

8-
if (!filename || !script) {
9-
console.error('Usage: json-edit <filename> <script>');
10-
console.error('Example: json-edit package.json \'_.version = "1.2.3"\'');
11-
process.exit(1);
12-
}
10+
const filename = positionals[0];
11+
const script = positionals[1];
1312

14-
const json = JSON.parse(readFileSync(filename, 'utf-8'));
15-
const func = new Function('_', script + '; return _;');
16-
const result = func(json);
13+
if (!filename || !script) {
14+
console.error('Usage: tool json-edit <filename> <script>');
15+
console.error('Example: tool json-edit package.json \'_.version = "1.2.3"\'');
16+
process.exit(1);
17+
}
1718

18-
writeFileSync(filename, JSON.stringify(result, null, 2) + '\n', 'utf-8');
19+
const json = JSON.parse(readFileSync(filename, 'utf-8'));
20+
const func = new Function('_', script + '; return _;');
21+
const result = func(json);
22+
23+
writeFileSync(filename, JSON.stringify(result, null, 2) + '\n', 'utf-8');
24+
}
Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
#!/usr/bin/env node
2-
31
import { readFileSync, writeFileSync } from 'node:fs';
42
import path from 'node:path';
3+
import { parseArgs } from 'node:util';
54

6-
const filename = process.argv[2];
7-
const searchValue = process.argv[3];
8-
const newValue = process.argv[4];
5+
export function replaceFileContent() {
6+
const { positionals } = parseArgs({
7+
allowPositionals: true,
8+
args: process.argv.slice(3),
9+
});
910

10-
if (!filename || !searchValue || !newValue) {
11-
console.error('Usage: replace-file-content <filename> <searchValue> <newValue>');
12-
console.error('Example: replace-file-content example.toml \'version = "0.0.0"\' \'version = "0.0.1"\'');
13-
process.exit(1);
14-
}
11+
const filename = positionals[0];
12+
const searchValue = positionals[1];
13+
const newValue = positionals[2];
1514

16-
const filepath = path.resolve(filename);
17-
const content = readFileSync(filepath, 'utf-8');
18-
const newContent = content.replace(searchValue, newValue);
19-
writeFileSync(filepath, newContent, 'utf-8');
15+
if (!filename || !searchValue || !newValue) {
16+
console.error('Usage: tool replace-file-content <filename> <searchValue> <newValue>');
17+
console.error('Example: tool replace-file-content example.toml \'version = "0.0.0"\' \'version = "0.0.1"\'');
18+
process.exit(1);
19+
}
20+
21+
const filepath = path.resolve(filename);
22+
const content = readFileSync(filepath, 'utf-8');
23+
const newContent = content.replace(searchValue, newValue);
24+
writeFileSync(filepath, newContent, 'utf-8');
25+
}

packages/tools/src/snap-test.ts

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
#!/usr/bin/env node
2-
31
import cp from 'node:child_process';
42
import { randomUUID } from 'node:crypto';
53
import fs from 'node:fs';
64
import fsPromises from 'node:fs/promises';
75
import { tmpdir } from 'node:os';
86
import path from 'node:path';
9-
import { debuglog, promisify } from 'node:util';
7+
import { debuglog, parseArgs, promisify } from 'node:util';
8+
9+
import { isPassThroughEnv, replaceUnstableOutput } from './utils';
1010

1111
const debug = debuglog('vite-plus/snap-test');
1212
const cpExec = promisify(cp.exec);
@@ -16,37 +16,42 @@ const exec = async (command: string, options: cp.ExecOptionsWithStringEncoding)
1616
process.platform === 'win32' ? { ...options, shell: 'pwsh.exe' } : options,
1717
);
1818

19-
import { isPassThroughEnv, replaceUnstableOutput } from './utils.ts';
19+
export async function snapTest() {
20+
const { positionals } = parseArgs({
21+
allowPositionals: true,
22+
args: process.argv.slice(3),
23+
});
2024

21-
// Create a unique temporary directory for testing
22-
// On macOS, `tmpdir()` is a symlink. Resolve it so that we can replace the resolved cwd in outputs.
23-
const tempTmpDir = `${fs.realpathSync(tmpdir())}/vite-plus-test-${randomUUID()}`;
24-
fs.mkdirSync(tempTmpDir, { recursive: true });
25+
const filter = positionals[0] ?? ''; // Optional filter to run specific test cases
2526

26-
// Make dependencies available in the test cases
27-
fs.symlinkSync(
28-
path.resolve('node_modules'),
29-
path.join(tempTmpDir, 'node_modules'),
30-
process.platform === 'win32' ? 'junction' : 'dir',
31-
);
27+
// Create a unique temporary directory for testing
28+
// On macOS, `tmpdir()` is a symlink. Resolve it so that we can replace the resolved cwd in outputs.
29+
const tempTmpDir = `${fs.realpathSync(tmpdir())}/vite-plus-test-${randomUUID()}`;
30+
fs.mkdirSync(tempTmpDir, { recursive: true });
3231

33-
// Clean up the temporary directory on exit
34-
process.on('exit', () => fs.rmSync(tempTmpDir, { recursive: true, force: true }));
32+
// Make dependencies available in the test cases
33+
fs.symlinkSync(
34+
path.resolve('node_modules'),
35+
path.join(tempTmpDir, 'node_modules'),
36+
process.platform === 'win32' ? 'junction' : 'dir',
37+
);
3538

36-
const casesDir = path.resolve('snap-tests');
39+
// Clean up the temporary directory on exit
40+
process.on('exit', () => fs.rmSync(tempTmpDir, { recursive: true, force: true }));
3741

38-
const filter = process.argv[2] ?? ''; // Optional filter to run specific test cases
42+
const casesDir = path.resolve('snap-tests');
3943

40-
const tasks: Promise<void>[] = [];
41-
for (const caseName of fs.readdirSync(casesDir)) {
42-
if (caseName.startsWith('.')) continue; // Skip hidden files like .DS_Store
43-
if (caseName.includes(filter)) {
44-
tasks.push(runTestCase(caseName));
44+
const tasks: Promise<void>[] = [];
45+
for (const caseName of fs.readdirSync(casesDir)) {
46+
if (caseName.startsWith('.')) continue; // Skip hidden files like .DS_Store
47+
if (caseName.includes(filter)) {
48+
tasks.push(runTestCase(caseName, tempTmpDir, casesDir));
49+
}
4550
}
46-
}
4751

48-
if (tasks.length > 0) {
49-
await Promise.all(tasks);
52+
if (tasks.length > 0) {
53+
await Promise.all(tasks);
54+
}
5055
}
5156

5257
interface Steps {
@@ -55,7 +60,7 @@ interface Steps {
5560
commands: string[];
5661
}
5762

58-
async function runTestCase(name: string) {
63+
async function runTestCase(name: string, tempTmpDir: string, casesDir: string) {
5964
const steps: Steps = JSON.parse(await fsPromises.readFile(`${casesDir}/${name}/steps.json`, 'utf-8'));
6065
if (steps.ignoredPlatforms !== undefined && steps.ignoredPlatforms.includes(process.platform)) {
6166
console.log('%s skipped on platform %s', name, process.platform);
@@ -67,7 +72,7 @@ async function runTestCase(name: string) {
6772
await fsPromises.cp(`${casesDir}/${name}`, caseTmpDir, { recursive: true, errorOnExist: true });
6873

6974
const passThroughEnvs = Object.fromEntries(Object.entries(process.env).filter(([key]) => isPassThroughEnv(key)));
70-
const env = {
75+
const env: Record<string, string> = {
7176
...passThroughEnvs,
7277
// Indicate CLI is running in test mode, so that it prints more detailed outputs.
7378
VITE_PLUS_CLI_TEST: '1',
@@ -104,7 +109,7 @@ async function runTestCase(name: string) {
104109
if (stderr) {
105110
newSnap.push(replaceUnstableOutput(stderr, caseTmpDir));
106111
}
107-
} catch (error) {
112+
} catch (error: any) {
108113
// add error exit code to the command
109114
newSnap.push(`[${error.code}]> ${command}`);
110115
if (error.stdout) {

packages/tools/tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"module": "ESNext",
5+
"moduleResolution": "bundler",
6+
"composite": true,
7+
"outDir": "./dist",
8+
"noEmit": false,
9+
"allowImportingTsExtensions": false,
10+
"rootDir": "./src"
11+
},
12+
"include": ["src"],
13+
"exclude": ["node_modules", "dist", "./snap-tests/**"]
14+
}

0 commit comments

Comments
 (0)