From 409447f13349bd9ce8ca0873cd99e90581e683b2 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Tue, 12 Aug 2025 16:31:32 +0330 Subject: [PATCH 01/11] 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 02/11] 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 03/11] 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, From dae6ae7ef31f0da14ab79cea4bbf07f0121f7e95 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 12:40:45 +0330 Subject: [PATCH 04/11] update --- src/bash.ts | 9 +- tests/package-manager-integration.test.ts | 255 ++++++++++++++++++++++ tests/shell-integration.test.ts | 246 +++++++++++++++++++++ 3 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 tests/package-manager-integration.test.ts create mode 100644 tests/shell-integration.test.ts diff --git a/src/bash.ts b/src/bash.ts index 3e3a4f2..e20b046 100644 --- a/src/bash.ts +++ b/src/bash.ts @@ -42,7 +42,14 @@ __${nameForVar}_complete() { local requestComp out directive # Build the command to get completions - requestComp="${exec} complete -- \${words[@]:1}" + # Use a more compatible approach instead of \${words[@]:1} + local args="" + for (( i=1; i { + const packageManagers = ['npm', 'pnpm', 'yarn', 'bun']; + const shells = ['zsh', 'bash', 'fish', 'powershell']; + + describe('package manager completion script generation', () => { + packageManagers.forEach((pm) => { + shells.forEach((shell) => { + it(`should generate ${shell} completion script for ${pm}`, async () => { + const command = `pnpm tsx bin/cli.ts ${pm} ${shell}`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have output + expect(stdout).toBeTruthy(); + expect(stdout.length).toBeGreaterThan(100); + + // Should contain shell-specific markers + expect(stdout).toContain(`# ${shell} completion for`); + + // Should contain package manager name + expect(stdout).toContain(pm); + + // Should not have errors + expect(stderr).toBe(''); + } catch (error) { + throw new Error( + `Failed to generate ${shell} script for ${pm}: ${error}` + ); + } + }); + }); + }); + }); + + // Test package manager completion functionality + describe('package manager completion functionality', () => { + packageManagers.forEach((pm) => { + describe(`${pm} completion`, () => { + it('should handle basic command completion', async () => { + const command = `pnpm tsx bin/cli.ts ${pm} complete -- install`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have some output (completions or directive) + expect(stdout).toBeTruthy(); + + // Should end with completion directive + expect(stdout.trim()).toMatch(/:\d+$/); + + // Should not have errors + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed ${pm} completion test: ${error}`); + } + }); + + it('should handle script completion', async () => { + const command = `pnpm tsx bin/cli.ts ${pm} complete -- run`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have output + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed ${pm} script completion: ${error}`); + } + }); + + it('should handle no match scenarios', async () => { + const command = `pnpm tsx bin/cli.ts ${pm} complete -- nonexistentcommand`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have output (even if no matches) + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed ${pm} no-match test: ${error}`); + } + }); + }); + }); + }); + + // Test bash-specific package manager issues + describe('bash package manager completion validation', () => { + packageManagers.forEach((pm) => { + it(`should generate syntactically valid bash completion for ${pm}`, async () => { + const command = `pnpm tsx bin/cli.ts ${pm} bash`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join(process.cwd(), `temp-${pm}-bash-completion.sh`); + await writeFile(scriptPath, stdout); + + try { + // Test bash syntax with -n flag (syntax check only) + await exec(`bash -n ${scriptPath}`); + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + throw new Error(`${pm} bash script has syntax errors: ${error}`); + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }); + + it(`should generate bash completion for ${pm} without problematic syntax`, async () => { + const command = `pnpm tsx bin/cli.ts ${pm} bash`; + const { stdout } = await exec(command); + + // Check for the actual problematic bash syntax in the requestComp assignment + expect(stdout).not.toMatch(/requestComp="[^"]*\$\{words\[@\]:1\}/); // Should not use ${words[@]:1} in requestComp + expect(stdout).toContain('complete -F'); // Should register completion properly + + // Should contain package manager specific function + expect(stdout).toMatch( + new RegExp(`__${pm.replace('-', '_')}_complete\\(\\)`) + ); + }); + }); + }); + + describe('package manager completion registration', () => { + shells.forEach((shell) => { + it(`should generate ${shell} completion with proper registration for all package managers`, async () => { + for (const pm of packageManagers) { + const command = `pnpm tsx bin/cli.ts ${pm} ${shell}`; + const { stdout } = await exec(command); + + switch (shell) { + case 'bash': + expect(stdout).toMatch(/complete -F __\w+_complete/); + expect(stdout).toContain(pm); // Should register for the specific package manager + break; + case 'zsh': + expect(stdout).toMatch(/#compdef \w+/); + expect(stdout).toMatch(/compdef _\w+ \w+/); + break; + case 'fish': + expect(stdout).toContain('complete -c'); + expect(stdout).toContain(pm); + break; + case 'powershell': + expect(stdout).toContain('Register-ArgumentCompleter'); + expect(stdout).toContain(pm); + break; + } + } + }); + }); + }); + + // Test error handling + describe('package manager error handling', () => { + it('should handle unsupported package manager', async () => { + const command = `pnpm tsx bin/cli.ts unsupported complete -- test`; + + try { + await exec(command); + // Should not reach here + expect(true).toBe(false); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + expect(errorMessage).toContain('Unsupported package manager'); + } + }); + + it('should handle missing -- separator', async () => { + const command = `pnpm tsx bin/cli.ts pnpm complete test`; + + try { + await exec(command); + // Should not reach here + expect(true).toBe(false); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + expect(errorMessage).toContain("Expected '--' followed by command"); + } + }); + }); + + // Test specific package manager features + describe('package manager specific features', () => { + it('should handle pnpm-specific commands', async () => { + const command = `pnpm tsx bin/cli.ts pnpm complete -- dlx`; + + try { + const { stdout, stderr } = await exec(command); + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed pnpm dlx completion: ${error}`); + } + }); + + it('should handle yarn-specific commands', async () => { + const command = `pnpm tsx bin/cli.ts yarn complete -- workspace`; + + try { + const { stdout, stderr } = await exec(command); + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed yarn workspace completion: ${error}`); + } + }); + + it('should handle npm-specific commands', async () => { + const command = `pnpm tsx bin/cli.ts npm complete -- audit`; + + try { + const { stdout, stderr } = await exec(command); + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed npm audit completion: ${error}`); + } + }); + + it('should handle bun-specific commands', async () => { + const command = `pnpm tsx bin/cli.ts bun complete -- create`; + + try { + const { stdout, stderr } = await exec(command); + expect(stdout).toBeTruthy(); + expect(stdout.trim()).toMatch(/:\d+$/); + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed bun create completion: ${error}`); + } + }); + }); +}); diff --git a/tests/shell-integration.test.ts b/tests/shell-integration.test.ts new file mode 100644 index 0000000..ee40e1b --- /dev/null +++ b/tests/shell-integration.test.ts @@ -0,0 +1,246 @@ +import { describe, it, expect } from 'vitest'; +import { promisify } from 'node:util'; +import { exec as execCb } from 'node:child_process'; +import { writeFile, unlink } from 'node:fs/promises'; +import { join } from 'node:path'; + +const exec = promisify(execCb); + +describe('shell integration tests', () => { + const shells = ['zsh', 'bash', 'fish', 'powershell']; + const cliTool = 'cac'; + + describe('shell script generation', () => { + shells.forEach((shell) => { + it(`should generate ${shell} completion script without errors`, async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete ${shell}`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have output + expect(stdout).toBeTruthy(); + expect(stdout.length).toBeGreaterThan(100); + + // Should contain shell-specific markers + expect(stdout).toContain(`# ${shell} completion for`); + + // Should not have errors + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed to generate ${shell} script: ${error}`); + } + }); + }); + }); + + describe('completion functionality', () => { + const completionTests = [ + { name: 'command completion', args: 'serve' }, + { name: 'option completion', args: 'serve --p' }, + { name: 'no match', args: 'nonexistent' }, + ]; + + completionTests.forEach(({ name, args }) => { + it(`should handle ${name}`, async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete -- ${args}`; + + try { + const { stdout, stderr } = await exec(command); + + // Should have some output (completions or directive) + expect(stdout).toBeTruthy(); + + // Should end with completion directive (e.g., :0, :1, etc.) + expect(stdout.trim()).toMatch(/:\d+$/); + + // Should not have errors + expect(stderr).toBe(''); + } catch (error) { + throw new Error(`Failed completion test '${name}': ${error}`); + } + }); + }); + }); + + describe('shell script syntax validation', () => { + it('should generate syntactically valid bash script', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join(process.cwd(), 'temp-bash-completion.sh'); + await writeFile(scriptPath, stdout); + + try { + // Test bash syntax with -n flag (syntax check only) + await exec(`bash -n ${scriptPath}`); + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + throw new Error(`Bash script has syntax errors: ${error}`); + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }); + + it('should generate syntactically valid zsh script', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete zsh`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join(process.cwd(), 'temp-zsh-completion.zsh'); + await writeFile(scriptPath, stdout); + + try { + // Test zsh syntax with -n flag (syntax check only) + await exec(`zsh -n ${scriptPath}`); + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + throw new Error(`Zsh script has syntax errors: ${error}`); + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }); + + it('should generate syntactically valid fish script', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete fish`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join(process.cwd(), 'temp-fish-completion.fish'); + await writeFile(scriptPath, stdout); + + try { + // Test fish syntax with -n flag (syntax check only) + await exec(`fish -n ${scriptPath}`); + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + // Fish might not be available, so make this a softer check + const errorMessage = + error instanceof Error ? error.message : String(error); + if ( + errorMessage.includes('command not found') || + errorMessage.includes('not recognized') + ) { + console.warn( + 'Fish shell not available for syntax testing, skipping...' + ); + expect(true).toBe(true); + } else { + throw new Error(`Fish script has syntax errors: ${errorMessage}`); + } + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }); + + it('should generate syntactically valid powershell script', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete powershell`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join(process.cwd(), 'temp-powershell-completion.ps1'); + await writeFile(scriptPath, stdout); + + try { + // Test PowerShell syntax + await exec( + `pwsh -NoProfile -Command "& { . '${scriptPath}'; exit 0 }"` + ); + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + // PowerShell might not be available, so make this a softer check + const errorMessage = + error instanceof Error ? error.message : String(error); + if ( + errorMessage.includes('command not found') || + errorMessage.includes('not recognized') + ) { + console.warn( + 'PowerShell not available for syntax testing, skipping...' + ); + expect(true).toBe(true); + } else { + throw new Error( + `PowerShell script has syntax errors: ${errorMessage}` + ); + } + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }); + }); + + // Test shell-specific features + describe('shell-specific functionality', () => { + it('bash completion should include proper function definitions', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Should contain bash completion function + expect(stdout).toMatch(/__\w+_complete\(\)/); + + // Should contain complete command registration + expect(stdout).toMatch(/complete -F __\w+_complete/); + + // Should handle bash completion variables (using _get_comp_words_by_ref) + expect(stdout).toContain('_get_comp_words_by_ref'); + expect(stdout).toContain('cur prev words cword'); + }); + + it('zsh completion should include proper compdef', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete zsh`; + const { stdout } = await exec(command); + + // Should contain compdef directive + expect(stdout).toMatch(/#compdef \w+/); + + // Should contain completion function + expect(stdout).toMatch(/_\w+\(\)/); + + // Should register the completion + expect(stdout).toMatch(/compdef _\w+ \w+/); + }); + + it('fish completion should include proper complete commands', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete fish`; + const { stdout } = await exec(command); + + // Should contain fish complete commands + expect(stdout).toContain('complete -c'); + + // Should handle command completion + expect(stdout).toMatch(/complete -c \w+ -f/); + }); + }); + + // Test for potential bash issues (related to the user's problem) + describe('bash-specific issue detection', () => { + it('should generate bash script with proper escaping', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Check for the actual problematic bash syntax in the requestComp assignment + expect(stdout).not.toMatch(/requestComp="[^"]*\$\{words\[@\]:1\}/); // Should not use ${words[@]:1} in requestComp + expect(stdout).toContain('requestComp='); // Should have proper variable assignment + expect(stdout).toContain('complete -F'); // Should register completion properly + }); + + it('should generate bash script that handles empty parameters correctly', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Should handle empty parameters + expect(stdout).toContain(`''`); // Should add empty parameter handling + expect(stdout).toContain('requestComp='); // Should build command properly + }); + }); +}); From 7c0ad2713cb8fc152feca4d865ea449aa2fe502b Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 13:54:43 +0330 Subject: [PATCH 05/11] update --- src/bash.ts | 10 ++-------- tests/package-manager-integration.test.ts | 22 ++++++++++++++++++--- tests/shell-integration.test.ts | 24 +++++++++++++++++++---- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/bash.ts b/src/bash.ts index e20b046..7bcb680 100644 --- a/src/bash.ts +++ b/src/bash.ts @@ -42,14 +42,7 @@ __${nameForVar}_complete() { local requestComp out directive # Build the command to get completions - # Use a more compatible approach instead of \${words[@]:1} - local args="" - for (( i=1; i { // If we get here, syntax is valid expect(true).toBe(true); } catch (error) { - throw new Error(`${pm} bash script has syntax errors: ${error}`); + const errorMessage = + error instanceof Error ? error.message : String(error); + + // Provide helpful error message about bash-completion dependency + const helpMessage = ` +${pm} bash script has syntax errors: ${errorMessage} + +This might be due to missing bash-completion dependency. +To fix this, install bash-completion@2: + + brew install bash-completion@2 + +Then source the completion in your shell profile: + echo 'source $(brew --prefix)/share/bash-completion/bash_completion' >> ~/.bashrc +`; + throw new Error(helpMessage); } finally { // Clean up await unlink(scriptPath).catch(() => {}); @@ -125,9 +140,10 @@ describe('package manager integration tests', () => { const command = `pnpm tsx bin/cli.ts ${pm} bash`; const { stdout } = await exec(command); - // Check for the actual problematic bash syntax in the requestComp assignment - expect(stdout).not.toMatch(/requestComp="[^"]*\$\{words\[@\]:1\}/); // Should not use ${words[@]:1} in requestComp + // Check that it uses the correct ${words[@]:1} syntax (requires bash-completion@2) + expect(stdout).toContain('${words[@]:1}'); // Should use ${words[@]:1} (requires bash-completion@2) expect(stdout).toContain('complete -F'); // Should register completion properly + expect(stdout).toContain('_get_comp_words_by_ref'); // Should use bash-completion functions // Should contain package manager specific function expect(stdout).toMatch( diff --git a/tests/shell-integration.test.ts b/tests/shell-integration.test.ts index ee40e1b..8506dca 100644 --- a/tests/shell-integration.test.ts +++ b/tests/shell-integration.test.ts @@ -78,7 +78,22 @@ describe('shell integration tests', () => { // If we get here, syntax is valid expect(true).toBe(true); } catch (error) { - throw new Error(`Bash script has syntax errors: ${error}`); + const errorMessage = + error instanceof Error ? error.message : String(error); + + // Provide helpful error message about bash-completion dependency + const helpMessage = ` +Bash script has syntax errors: ${errorMessage} + +This might be due to missing bash-completion dependency. +To fix this, install bash-completion@2: + + brew install bash-completion@2 + +Then source the completion in your shell profile: + echo 'source $(brew --prefix)/share/bash-completion/bash_completion' >> ~/.bashrc +`; + throw new Error(helpMessage); } finally { // Clean up await unlink(scriptPath).catch(() => {}); @@ -224,14 +239,15 @@ describe('shell integration tests', () => { // Test for potential bash issues (related to the user's problem) describe('bash-specific issue detection', () => { - it('should generate bash script with proper escaping', async () => { + it('should generate bash script with proper syntax (requires bash-completion@2)', async () => { const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; const { stdout } = await exec(command); - // Check for the actual problematic bash syntax in the requestComp assignment - expect(stdout).not.toMatch(/requestComp="[^"]*\$\{words\[@\]:1\}/); // Should not use ${words[@]:1} in requestComp + // Check that it uses the correct ${words[@]:1} syntax + expect(stdout).toContain('${words[@]:1}'); // Should use ${words[@]:1} (requires bash-completion@2) expect(stdout).toContain('requestComp='); // Should have proper variable assignment expect(stdout).toContain('complete -F'); // Should register completion properly + expect(stdout).toContain('_get_comp_words_by_ref'); // Should use bash-completion functions }); it('should generate bash script that handles empty parameters correctly', async () => { From 8ad79549078c9fe69f04fed3eda25b7584f8dca0 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 20:36:27 +0330 Subject: [PATCH 06/11] gh matrixes --- .github/workflows/ci.yml | 99 +++++++++- tests/shell-integration.test.ts | 320 +++++++++++++++----------------- 2 files changed, 241 insertions(+), 178 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b849f9..5b9e2c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ on: jobs: test: - name: Tests + name: Tests (${{ matrix.os }}) strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -33,8 +33,101 @@ jobs: - name: Install deps run: pnpm install - - name: Run tests - run: pnpm test + - name: Run core tests (excluding shell integration) + run: pnpm test --exclude="**/shell-integration.test.ts" + + shell-tests: + name: Shell Tests (${{ matrix.shell }} on ${{ matrix.os }}) + strategy: + matrix: + shell: [bash, zsh, fish, powershell] + os: [ubuntu-latest, macos-latest] + exclude: + # PowerShell installation can be flaky on macOS in CI + - shell: powershell + os: macos-latest + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install shell dependencies (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + case "${{ matrix.shell }}" in + bash) + sudo apt-get install -y bash-completion + ;; + zsh) + sudo apt-get install -y zsh + ;; + fish) + sudo apt-get install -y fish + ;; + powershell) + wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb + sudo dpkg -i packages-microsoft-prod.deb + sudo apt-get update + sudo apt-get install -y powershell + ;; + esac + + - name: Install shell dependencies (macOS) + if: matrix.os == 'macos-latest' + run: | + case "${{ matrix.shell }}" in + bash) + brew install bash-completion@2 + ;; + zsh) + # zsh is already installed on macOS + echo "zsh already available" + ;; + fish) + brew install fish + ;; + powershell) + # Skip PowerShell on macOS for now due to CI flakiness + echo "PowerShell skipped on macOS" + ;; + esac + + - name: Verify shell installation + run: | + case "${{ matrix.shell }}" in + bash) + bash --version + ;; + zsh) + zsh --version + ;; + fish) + fish --version + ;; + powershell) + pwsh --version + ;; + esac + + - 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 shell-specific tests + run: pnpm test tests/shell-integration.test.ts + env: + TEST_SHELL: ${{ matrix.shell }} typecheck: name: Lint and Type Check diff --git a/tests/shell-integration.test.ts b/tests/shell-integration.test.ts index 8506dca..5744044 100644 --- a/tests/shell-integration.test.ts +++ b/tests/shell-integration.test.ts @@ -7,7 +7,11 @@ import { join } from 'node:path'; const exec = promisify(execCb); describe('shell integration tests', () => { - const shells = ['zsh', 'bash', 'fish', 'powershell']; + // Support matrix testing - if TEST_SHELL is set, only test that shell + const testShell = process.env.TEST_SHELL; + const shells = testShell + ? [testShell] + : ['zsh', 'bash', 'fish', 'powershell']; const cliTool = 'cac'; describe('shell script generation', () => { @@ -64,25 +68,66 @@ describe('shell integration tests', () => { }); describe('shell script syntax validation', () => { - it('should generate syntactically valid bash script', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; - const { stdout } = await exec(command); - - // Write script to temp file - const scriptPath = join(process.cwd(), 'temp-bash-completion.sh'); - await writeFile(scriptPath, stdout); - - try { - // Test bash syntax with -n flag (syntax check only) - await exec(`bash -n ${scriptPath}`); - // If we get here, syntax is valid - expect(true).toBe(true); - } catch (error) { - const errorMessage = - error instanceof Error ? error.message : String(error); - - // Provide helpful error message about bash-completion dependency - const helpMessage = ` + // Only run syntax validation for the shells we're testing + const syntaxTestShells = shells.filter( + (shell) => + shell === 'bash' || + shell === 'zsh' || + shell === 'fish' || + shell === 'powershell' + ); + + syntaxTestShells.forEach((shell) => { + it( + `should generate syntactically valid ${shell} script`, + async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete ${shell}`; + const { stdout } = await exec(command); + + // Write script to temp file + const scriptPath = join( + process.cwd(), + `temp-${shell}-completion.${shell === 'powershell' ? 'ps1' : shell === 'fish' ? 'fish' : 'sh'}` + ); + await writeFile(scriptPath, stdout); + + try { + // Test syntax based on shell type + switch (shell) { + case 'bash': + await exec(`bash -n ${scriptPath}`); + break; + case 'zsh': + await exec(`zsh -n ${scriptPath}`); + break; + case 'fish': + await exec(`fish -n ${scriptPath}`); + break; + case 'powershell': + // Test PowerShell syntax with timeout to prevent hanging in CI + const testPromise = exec( + `pwsh -NoProfile -Command "& { . '${scriptPath}'; exit 0 }"` + ); + + const timeoutPromise = new Promise((_, reject) => + setTimeout( + () => reject(new Error('PowerShell test timed out')), + 10000 + ) + ); + + await Promise.race([testPromise, timeoutPromise]); + break; + } + // If we get here, syntax is valid + expect(true).toBe(true); + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + + // Provide helpful error message for bash-completion dependency + if (shell === 'bash' && errorMessage.includes('syntax')) { + const helpMessage = ` Bash script has syntax errors: ${errorMessage} This might be due to missing bash-completion dependency. @@ -93,170 +138,95 @@ To fix this, install bash-completion@2: Then source the completion in your shell profile: echo 'source $(brew --prefix)/share/bash-completion/bash_completion' >> ~/.bashrc `; - throw new Error(helpMessage); - } finally { - // Clean up - await unlink(scriptPath).catch(() => {}); - } - }); - - it('should generate syntactically valid zsh script', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete zsh`; - const { stdout } = await exec(command); - - // Write script to temp file - const scriptPath = join(process.cwd(), 'temp-zsh-completion.zsh'); - await writeFile(scriptPath, stdout); - - try { - // Test zsh syntax with -n flag (syntax check only) - await exec(`zsh -n ${scriptPath}`); - // If we get here, syntax is valid - expect(true).toBe(true); - } catch (error) { - throw new Error(`Zsh script has syntax errors: ${error}`); - } finally { - // Clean up - await unlink(scriptPath).catch(() => {}); - } - }); - - it('should generate syntactically valid fish script', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete fish`; - const { stdout } = await exec(command); - - // Write script to temp file - const scriptPath = join(process.cwd(), 'temp-fish-completion.fish'); - await writeFile(scriptPath, stdout); - - try { - // Test fish syntax with -n flag (syntax check only) - await exec(`fish -n ${scriptPath}`); - // If we get here, syntax is valid - expect(true).toBe(true); - } catch (error) { - // Fish might not be available, so make this a softer check - const errorMessage = - error instanceof Error ? error.message : String(error); - if ( - errorMessage.includes('command not found') || - errorMessage.includes('not recognized') - ) { - console.warn( - 'Fish shell not available for syntax testing, skipping...' - ); - expect(true).toBe(true); - } else { - throw new Error(`Fish script has syntax errors: ${errorMessage}`); - } - } finally { - // Clean up - await unlink(scriptPath).catch(() => {}); - } - }); - - it('should generate syntactically valid powershell script', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete powershell`; - const { stdout } = await exec(command); - - // Write script to temp file - const scriptPath = join(process.cwd(), 'temp-powershell-completion.ps1'); - await writeFile(scriptPath, stdout); - - try { - // Test PowerShell syntax - await exec( - `pwsh -NoProfile -Command "& { . '${scriptPath}'; exit 0 }"` - ); - // If we get here, syntax is valid - expect(true).toBe(true); - } catch (error) { - // PowerShell might not be available, so make this a softer check - const errorMessage = - error instanceof Error ? error.message : String(error); - if ( - errorMessage.includes('command not found') || - errorMessage.includes('not recognized') - ) { - console.warn( - 'PowerShell not available for syntax testing, skipping...' - ); - expect(true).toBe(true); - } else { - throw new Error( - `PowerShell script has syntax errors: ${errorMessage}` - ); - } - } finally { - // Clean up - await unlink(scriptPath).catch(() => {}); - } + throw new Error(helpMessage); + } + + throw new Error(`${shell} script has syntax errors: ${error}`); + } finally { + // Clean up + await unlink(scriptPath).catch(() => {}); + } + }, + shell === 'powershell' ? 15000 : 5000 + ); // Longer timeout for PowerShell }); }); // Test shell-specific features describe('shell-specific functionality', () => { - it('bash completion should include proper function definitions', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; - const { stdout } = await exec(command); - - // Should contain bash completion function - expect(stdout).toMatch(/__\w+_complete\(\)/); - - // Should contain complete command registration - expect(stdout).toMatch(/complete -F __\w+_complete/); - - // Should handle bash completion variables (using _get_comp_words_by_ref) - expect(stdout).toContain('_get_comp_words_by_ref'); - expect(stdout).toContain('cur prev words cword'); - }); - - it('zsh completion should include proper compdef', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete zsh`; - const { stdout } = await exec(command); - - // Should contain compdef directive - expect(stdout).toMatch(/#compdef \w+/); - - // Should contain completion function - expect(stdout).toMatch(/_\w+\(\)/); - - // Should register the completion - expect(stdout).toMatch(/compdef _\w+ \w+/); - }); - - it('fish completion should include proper complete commands', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete fish`; - const { stdout } = await exec(command); - - // Should contain fish complete commands - expect(stdout).toContain('complete -c'); - - // Should handle command completion - expect(stdout).toMatch(/complete -c \w+ -f/); + shells.forEach((shell) => { + switch (shell) { + case 'bash': + it('bash completion should include proper function definitions', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Should contain bash completion function + expect(stdout).toMatch(/__\w+_complete\(\)/); + + // Should contain complete command registration + expect(stdout).toMatch(/complete -F __\w+_complete/); + + // Should handle bash completion variables (using _get_comp_words_by_ref) + expect(stdout).toContain('_get_comp_words_by_ref'); + expect(stdout).toContain('cur prev words cword'); + }); + break; + + case 'zsh': + it('zsh completion should include proper compdef', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete zsh`; + const { stdout } = await exec(command); + + // Should contain compdef directive + expect(stdout).toMatch(/#compdef \w+/); + + // Should contain completion function + expect(stdout).toMatch(/_\w+\(\)/); + + // Should register the completion + expect(stdout).toMatch(/compdef _\w+ \w+/); + }); + break; + + case 'fish': + it('fish completion should include proper complete commands', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete fish`; + const { stdout } = await exec(command); + + // Should contain fish complete commands + expect(stdout).toContain('complete -c'); + + // Should handle command completion + expect(stdout).toMatch(/complete -c \w+ -f/); + }); + break; + } }); }); - // Test for potential bash issues (related to the user's problem) - describe('bash-specific issue detection', () => { - it('should generate bash script with proper syntax (requires bash-completion@2)', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; - const { stdout } = await exec(command); - - // Check that it uses the correct ${words[@]:1} syntax - expect(stdout).toContain('${words[@]:1}'); // Should use ${words[@]:1} (requires bash-completion@2) - expect(stdout).toContain('requestComp='); // Should have proper variable assignment - expect(stdout).toContain('complete -F'); // Should register completion properly - expect(stdout).toContain('_get_comp_words_by_ref'); // Should use bash-completion functions - }); + // Test for potential bash issues (only run for bash) + if (shells.includes('bash')) { + describe('bash-specific issue detection', () => { + it('should generate bash script with proper syntax (requires bash-completion@2)', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); + + // Check that it uses the correct ${words[@]:1} syntax + expect(stdout).toContain('${words[@]:1}'); // Should use ${words[@]:1} (requires bash-completion@2) + expect(stdout).toContain('requestComp='); // Should have proper variable assignment + expect(stdout).toContain('complete -F'); // Should register completion properly + expect(stdout).toContain('_get_comp_words_by_ref'); // Should use bash-completion functions + }); - it('should generate bash script that handles empty parameters correctly', async () => { - const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; - const { stdout } = await exec(command); + it('should generate bash script that handles empty parameters correctly', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete bash`; + const { stdout } = await exec(command); - // Should handle empty parameters - expect(stdout).toContain(`''`); // Should add empty parameter handling - expect(stdout).toContain('requestComp='); // Should build command properly + // Should handle empty parameters + expect(stdout).toContain(`''`); // Should add empty parameter handling + expect(stdout).toContain('requestComp='); // Should build command properly + }); }); - }); + } }); From d81bae5dc07da6bb8f89ed468041747d89e16354 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 20:50:26 +0330 Subject: [PATCH 07/11] pwsh on windwows --- .github/workflows/ci.yml | 50 +++++++++++++++++++++++++++++++-- tests/shell-integration.test.ts | 28 ++++++++++++++++++ 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b9e2c2..d752d2f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,18 +34,23 @@ jobs: run: pnpm install - name: Run core tests (excluding shell integration) - run: pnpm test --exclude="**/shell-integration.test.ts" + run: pnpm vitest run --exclude "**/shell-integration.test.ts" shell-tests: name: Shell Tests (${{ matrix.shell }} on ${{ matrix.os }}) strategy: matrix: shell: [bash, zsh, fish, powershell] - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest, windows-latest] exclude: # PowerShell installation can be flaky on macOS in CI - shell: powershell os: macos-latest + # Some shells are not easily available on Windows + - shell: zsh + os: windows-latest + - shell: fish + os: windows-latest runs-on: ${{ matrix.os }} steps: @@ -94,7 +99,30 @@ jobs: ;; esac - - name: Verify shell installation + - name: Install shell dependencies (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + # Windows specific shell setup + switch ("${{ matrix.shell }}") { + "bash" { + # Git for Windows includes bash + if (!(Get-Command git -ErrorAction SilentlyContinue)) { + choco install git -y + } + Write-Host "Bash available via Git for Windows" + } + "powershell" { + # PowerShell is already installed on Windows + Write-Host "PowerShell already available" + } + default { + Write-Host "Shell ${{ matrix.shell }} not supported on Windows" + } + } + + - name: Verify shell installation (Unix) + if: matrix.os != 'windows-latest' run: | case "${{ matrix.shell }}" in bash) @@ -111,6 +139,22 @@ jobs: ;; esac + - name: Verify shell installation (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + switch ("${{ matrix.shell }}") { + "bash" { + bash --version + } + "powershell" { + pwsh --version + } + default { + Write-Host "Verification not needed for ${{ matrix.shell }} on Windows" + } + } + - name: Install pnpm uses: pnpm/action-setup@v4.0.0 diff --git a/tests/shell-integration.test.ts b/tests/shell-integration.test.ts index 5744044..b2a01f8 100644 --- a/tests/shell-integration.test.ts +++ b/tests/shell-integration.test.ts @@ -201,6 +201,34 @@ Then source the completion in your shell profile: expect(stdout).toMatch(/complete -c \w+ -f/); }); break; + + case 'powershell': + it('powershell completion should include proper functions', async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete powershell`; + const { stdout } = await exec(command); + + // Should contain PowerShell function definition + expect(stdout).toContain('function __'); + + // Should contain Register-ArgumentCompleter + expect(stdout).toContain('Register-ArgumentCompleter'); + + // Should handle PowerShell completion parameters + expect(stdout).toContain('$WordToComplete'); + expect(stdout).toContain('$CommandAst'); + expect(stdout).toContain('$CursorPosition'); + }); + break; + + default: + // For shells without specific tests, add a basic test to avoid empty suites + it(`should generate ${shell} completion script`, async () => { + const command = `pnpm tsx examples/demo.${cliTool}.ts complete ${shell}`; + const { stdout } = await exec(command); + expect(stdout).toBeTruthy(); + expect(stdout).toContain(`# ${shell} completion for`); + }); + break; } }); }); From 47814a7e3dbd2643e3d283d47ae991dfdb7bcc66 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 23:07:29 +0330 Subject: [PATCH 08/11] update ci --- .github/workflows/ci.yml | 8 ++++---- tests/package-manager-integration.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d752d2f..46ea935 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: - name: Install shell dependencies (Windows) if: matrix.os == 'windows-latest' - shell: pwsh + shell: powershell run: | # Windows specific shell setup switch ("${{ matrix.shell }}") { @@ -113,8 +113,8 @@ jobs: Write-Host "Bash available via Git for Windows" } "powershell" { - # PowerShell is already installed on Windows - Write-Host "PowerShell already available" + # Install PowerShell Core for consistency across platforms + winget install --id Microsoft.Powershell --source winget --silent } default { Write-Host "Shell ${{ matrix.shell }} not supported on Windows" @@ -141,7 +141,7 @@ jobs: - name: Verify shell installation (Windows) if: matrix.os == 'windows-latest' - shell: pwsh + shell: powershell run: | switch ("${{ matrix.shell }}") { "bash" { diff --git a/tests/package-manager-integration.test.ts b/tests/package-manager-integration.test.ts index 8df9866..e22ec70 100644 --- a/tests/package-manager-integration.test.ts +++ b/tests/package-manager-integration.test.ts @@ -179,7 +179,7 @@ Then source the completion in your shell profile: break; } } - }); + }, 15000); // Increase timeout to 15 seconds for these intensive tests }); }); From 3e7b59332f80c543b07534aa628d785942cd1acd Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 23:12:12 +0330 Subject: [PATCH 09/11] shell deps --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46ea935..b53e7dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: } "powershell" { # Install PowerShell Core for consistency across platforms - winget install --id Microsoft.Powershell --source winget --silent + choco install powershell-core -y } default { Write-Host "Shell ${{ matrix.shell }} not supported on Windows" From 668875ce99429d46199880dc960c582c9b7e6dc9 Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 23:13:42 +0330 Subject: [PATCH 10/11] update ci --- .github/workflows/ci.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b53e7dd..f8c9917 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,8 +113,13 @@ jobs: Write-Host "Bash available via Git for Windows" } "powershell" { - # Install PowerShell Core for consistency across platforms - choco install powershell-core -y + # Check if PowerShell Core is available, install if needed + if (!(Get-Command pwsh -ErrorAction SilentlyContinue)) { + Write-Host "Installing PowerShell Core..." + choco install powershell-core -y --force + } else { + Write-Host "PowerShell Core already available" + } } default { Write-Host "Shell ${{ matrix.shell }} not supported on Windows" From 98dcf062a80a83902d4644781c305f5208a240ba Mon Sep 17 00:00:00 2001 From: AmirSa12 Date: Sun, 17 Aug 2025 23:18:40 +0330 Subject: [PATCH 11/11] rm installation step --- .github/workflows/ci.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8c9917..c2d791a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,10 +72,8 @@ jobs: sudo apt-get install -y fish ;; powershell) - wget -q https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb - sudo dpkg -i packages-microsoft-prod.deb - sudo apt-get update - sudo apt-get install -y powershell + # PowerShell Core is pre-installed on GitHub Actions runners + echo "PowerShell Core already available" ;; esac @@ -113,13 +111,8 @@ jobs: Write-Host "Bash available via Git for Windows" } "powershell" { - # Check if PowerShell Core is available, install if needed - if (!(Get-Command pwsh -ErrorAction SilentlyContinue)) { - Write-Host "Installing PowerShell Core..." - choco install powershell-core -y --force - } else { - Write-Host "PowerShell Core already available" - } + # PowerShell Core is pre-installed on GitHub Actions runners + Write-Host "PowerShell Core already available" } default { Write-Host "Shell ${{ matrix.shell }} not supported on Windows"