From 409447f13349bd9ce8ca0873cd99e90581e683b2 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Tue, 12 Aug 2025 16:31:32 +0330 Subject: [PATCH 1/3] add benchmarks --- benchmarks/completion.bench.ts | 79 ++++++++++++++++++++++++++++++++++ package.json | 3 +- pnpm-lock.yaml | 9 ++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 benchmarks/completion.bench.ts diff --git a/benchmarks/completion.bench.ts b/benchmarks/completion.bench.ts new file mode 100644 index 0000000..f0fbdfa --- /dev/null +++ b/benchmarks/completion.bench.ts @@ -0,0 +1,79 @@ +import { Bench } from 'tinybench'; +import { RootCommand } from '../src/t'; + +const bench = new Bench({ time: 1000 }); + +const setupCompletion = () => { + const completion = new RootCommand(); + completion.command('dev', 'Start dev server'); + completion.command('build', 'Build project'); + completion.command('test', 'Run tests'); + + completion.option('--config', 'Config file'); + completion.option('--verbose', 'Verbose output'); + + const devCommand = completion.commands.get('dev')!; + devCommand.option('--port', 'Port number', function (complete) { + complete('3000', 'Development port'); + complete('8080', 'Alternative port'); + }); + + return completion; +}; + +const suppressOutput = (fn: () => void) => { + const originalLog = console.log; + console.log = () => {}; + fn(); + console.log = originalLog; +}; + +bench.add('command completion', () => { + const completion = setupCompletion(); + suppressOutput(() => completion.parse(['d'])); +}); + +bench.add('option completion', () => { + const completion = setupCompletion(); + suppressOutput(() => completion.parse(['dev', '--p'])); +}); + +bench.add('option value completion', () => { + const completion = setupCompletion(); + suppressOutput(() => completion.parse(['dev', '--port', ''])); +}); + +bench.add('no match', () => { + const completion = setupCompletion(); + suppressOutput(() => completion.parse(['xyz'])); +}); + +bench.add('large command set', () => { + const completion = new RootCommand(); + for (let i = 0; i < 100; i++) { + completion.command(`cmd${i}`, `Command ${i}`); + } + suppressOutput(() => completion.parse(['cmd5'])); +}); + +async function runBenchmarks() { + await bench.run(); + + console.table( + bench.tasks.map((task) => ({ + name: task.name, + 'ops/sec': task.result?.hz + ? Math.round(task.result.hz).toLocaleString() + : 'N/A', + 'avg (ms)': task.result?.mean + ? (task.result.mean * 1000).toFixed(3) + : 'N/A', + })) + ); +} + +if (process.argv[1]?.endsWith('completion.bench.ts')) { + runBenchmarks().catch(console.error); +} + +export { runBenchmarks }; diff --git a/package.json b/package.json index b7b3b9f..065cfe3 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build": "tsdown", "prepare": "pnpm build", "lint": "eslint src \"./*.ts\"", - "test-cli": "tsx bin/cli.ts" + "benchmark": "tsx benchmarks/completion.bench.ts" }, "files": [ "dist" @@ -30,6 +30,7 @@ "commander": "^13.1.0", "eslint-config-prettier": "^10.0.1", "prettier": "^3.5.2", + "tinybench": "^4.0.1", "tsdown": "^0.9.7", "tsx": "^4.19.1", "typescript": "^5.7.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78da1d2..d963698 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: prettier: specifier: ^3.5.2 version: 3.5.2 + tinybench: + specifier: ^4.0.1 + version: 4.0.1 tsdown: specifier: ^0.9.7 version: 0.9.7(typescript@5.7.3) @@ -1383,6 +1386,10 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@4.0.1: + resolution: {integrity: sha512-Nb1srn7dvzkVx0J5h1vq8f48e3TIcbrS7e/UfAI/cDSef/n8yLh4zsAEsFkfpw6auTY+ZaspEvam/xs8nMnotQ==} + engines: {node: '>=18.0.0'} + tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} @@ -2708,6 +2715,8 @@ snapshots: tinybench@2.9.0: {} + tinybench@4.0.1: {} + tinyexec@0.3.1: {} tinyexec@1.0.1: {} From bda81947993573c1b22eebf1c7dc8f569250b3b0 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Tue, 12 Aug 2025 16:34:04 +0330 Subject: [PATCH 2/3] add benchmarks to ci --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0647a22..3b849f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,3 +62,27 @@ jobs: - name: Type-check run: pnpm type-check + + benchmark: + name: Benchmarks + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.0.0 + + - name: Set node version to 20 + uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + cache: pnpm + + - name: Install deps + run: pnpm install + + - name: Run benchmarks + run: pnpm benchmark From 11d2b2b2d6878a54b9b72608619b066b8267f2b7 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Fri, 15 Aug 2025 10:21:35 +0330 Subject: [PATCH 3/3] using process.execPath --- benchmarks/completion.bench.ts | 85 ++++++++++++++-------------------- tsdown.config.ts | 4 ++ 2 files changed, 38 insertions(+), 51 deletions(-) diff --git a/benchmarks/completion.bench.ts b/benchmarks/completion.bench.ts index f0fbdfa..e84fa4d 100644 --- a/benchmarks/completion.bench.ts +++ b/benchmarks/completion.bench.ts @@ -1,74 +1,57 @@ import { Bench } from 'tinybench'; -import { RootCommand } from '../src/t'; +import { promisify } from 'node:util'; +import { exec as execCb } from 'node:child_process'; -const bench = new Bench({ time: 1000 }); +const exec = promisify(execCb); -const setupCompletion = () => { - const completion = new RootCommand(); - completion.command('dev', 'Start dev server'); - completion.command('build', 'Build project'); - completion.command('test', 'Run tests'); +const bench = new Bench({ time: 2000 }); - completion.option('--config', 'Config file'); - completion.option('--verbose', 'Verbose output'); +const cmdPrefix = `${process.execPath} ./dist/examples/demo.t.js complete --`; - const devCommand = completion.commands.get('dev')!; - devCommand.option('--port', 'Port number', function (complete) { - complete('3000', 'Development port'); - complete('8080', 'Alternative port'); - }); - - return completion; -}; - -const suppressOutput = (fn: () => void) => { - const originalLog = console.log; - console.log = () => {}; - fn(); - console.log = originalLog; -}; +async function run(cmd: string) { + await exec(cmd); +} -bench.add('command completion', () => { - const completion = setupCompletion(); - suppressOutput(() => completion.parse(['d'])); +bench.add('command completion', async () => { + await run(`${cmdPrefix} d`); }); -bench.add('option completion', () => { - const completion = setupCompletion(); - suppressOutput(() => completion.parse(['dev', '--p'])); +bench.add('option completion', async () => { + await run(`${cmdPrefix} dev --p`); }); -bench.add('option value completion', () => { - const completion = setupCompletion(); - suppressOutput(() => completion.parse(['dev', '--port', ''])); +bench.add('option value completion', async () => { + await run(`${cmdPrefix} dev --port ""`); }); -bench.add('no match', () => { - const completion = setupCompletion(); - suppressOutput(() => completion.parse(['xyz'])); +bench.add('config value completion', async () => { + await run(`${cmdPrefix} --config ""`); }); -bench.add('large command set', () => { - const completion = new RootCommand(); - for (let i = 0; i < 100; i++) { - completion.command(`cmd${i}`, `Command ${i}`); - } - suppressOutput(() => completion.parse(['cmd5'])); +bench.add('no match', async () => { + await run(`${cmdPrefix} xyz`); }); async function runBenchmarks() { await bench.run(); console.table( - bench.tasks.map((task) => ({ - name: task.name, - 'ops/sec': task.result?.hz - ? Math.round(task.result.hz).toLocaleString() - : 'N/A', - 'avg (ms)': task.result?.mean - ? (task.result.mean * 1000).toFixed(3) - : 'N/A', - })) + bench.tasks.map((task) => { + const hz = task.result?.hz; + const derivedMs = + typeof hz === 'number' && hz > 0 ? 1000 / hz : undefined; + const mean = task.result?.mean; + return { + name: task.name, + 'ops/sec': hz ? Math.round(hz).toLocaleString() : 'N/A', + 'avg (ms)': + derivedMs !== undefined + ? derivedMs.toFixed(3) + : mean !== undefined + ? (mean * 1000).toFixed(3) + : 'N/A', + }; + }) ); } diff --git a/tsdown.config.ts b/tsdown.config.ts index a29c942..e12fc42 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -7,6 +7,10 @@ export default defineConfig({ 'src/cac.ts', 'src/commander.ts', 'bin/cli.ts', + 'examples/demo.t.ts', + 'examples/demo.cac.ts', + 'examples/demo.citty.ts', + 'examples/demo.commander.ts', ], format: ['esm'], dts: true,