From 2c63e10d9a8042b4b6d0765ca7dc8a510c963440 Mon Sep 17 00:00:00 2001 From: Lex Date: Tue, 20 Oct 2026 15:55:43 +0700 Subject: [PATCH 1/6] fix(create-batch): preserve per-spec creation failures in --run JSON output (#483) --- src/commands/test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands/test.ts b/src/commands/test.ts index 8bf5ce5..fcb1ca9 100644 --- a/src/commands/test.ts +++ b/src/commands/test.ts @@ -4985,7 +4985,10 @@ async function runBatchRun( return { ...r, dashboardUrl: state.dashboardUrl }; }) : batchRunResults; - out.print({ results: enrichedResults }); + out.print({ + results: enrichedResults, + creation: { results: createResponse.results, summary: createResponse.summary }, + }); } else if (!opts.wait) { // Text mode, no --wait: statuses are non-terminal by design ('queued'), // so a pass/fail summary would misread a successful dispatch as From 4c5eb4c253800de7f4312c9581e40086be8d589c Mon Sep 17 00:00:00 2001 From: Lex Date: Tue, 20 Oct 2026 16:02:00 +0700 Subject: [PATCH 2/6] fix(cli): preserve non-zero exit code on stdout EPIPE (#489) --- src/lib/interrupt.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/interrupt.ts b/src/lib/interrupt.ts index 939a2fd..f291d32 100644 --- a/src/lib/interrupt.ts +++ b/src/lib/interrupt.ts @@ -165,7 +165,7 @@ export class ShutdownController { } class BrokenPipeInterruptError extends InterruptError { - override readonly exitCode = 0; + override readonly exitCode = (process.exitCode as number | undefined) ?? 0; constructor() { // Existing graceful-detach consumers read `.signal` to render their @@ -366,7 +366,7 @@ export function installBrokenPipeGuard(deps: BrokenPipeDeps = {}): void { shutdown.brokenPipe(); return; } - exit(0); + exit((process.exitCode as number | undefined) ?? 0); return; } throw error; From 8de9efaaf4e4d7a3cc2fbe809447104d0514066f Mon Sep 17 00:00:00 2001 From: Lex Date: Thu, 22 Oct 2026 16:55:54 +0700 Subject: [PATCH 3/6] chore: trigger CI entry check From ffd6e44b3cbee334a1f9e6af21633103c32ef3c9 Mon Sep 17 00:00:00 2001 From: Lex Date: Thu, 22 Oct 2026 17:17:31 +0700 Subject: [PATCH 4/6] feat(completion): add PowerShell completion support (#642) --- src/commands/completion.test.ts | 6 +++--- src/commands/completion.ts | 25 ++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/commands/completion.test.ts b/src/commands/completion.test.ts index c1bbdcf..6bc171b 100644 --- a/src/commands/completion.test.ts +++ b/src/commands/completion.test.ts @@ -10,11 +10,11 @@ const SPEC: CompletionSpec = { }; describe('isShell / detectShell', () => { - it('recognizes the three supported shells', () => { + it('recognizes the four supported shells', () => { expect(isShell('bash')).toBe(true); expect(isShell('zsh')).toBe(true); expect(isShell('fish')).toBe(true); - expect(isShell('powershell')).toBe(false); + expect(isShell('powershell')).toBe(true); }); it('detects the shell from a $SHELL path', () => { @@ -81,7 +81,7 @@ describe('createCompletionCommand', () => { it('rejects an unsupported shell with VALIDATION_ERROR (exit 5)', async () => { const cmd = createCompletionCommand(() => SPEC, { env: {}, stdout: () => undefined }); - await expect(cmd.parseAsync(['powershell'], { from: 'user' })).rejects.toMatchObject({ + await expect(cmd.parseAsync(['tcsh'], { from: 'user' })).rejects.toMatchObject({ code: 'VALIDATION_ERROR', }); }); diff --git a/src/commands/completion.ts b/src/commands/completion.ts index 42668fa..f74aea4 100644 --- a/src/commands/completion.ts +++ b/src/commands/completion.ts @@ -16,7 +16,7 @@ import { Command } from 'commander'; import { localValidationError } from '../lib/errors.js'; -export const SUPPORTED_SHELLS = ['bash', 'zsh', 'fish'] as const; +export const SUPPORTED_SHELLS = ['bash', 'zsh', 'fish', 'powershell'] as const; export type Shell = (typeof SUPPORTED_SHELLS)[number]; export interface CompletionSpec { @@ -55,6 +55,8 @@ export function renderCompletion(shell: Shell, spec: CompletionSpec): string { return renderZsh(spec); case 'fish': return renderFish(spec); + case 'powershell': + return renderPowerShell(spec); } } @@ -125,15 +127,32 @@ function renderFish(spec: CompletionSpec): string { return lines.join('\n'); } +function renderPowerShell(spec: CompletionSpec): string { + const lines = [ + `# ${spec.program} PowerShell completion. Enable with: testsprite completion powershell | Out-String | Invoke-Expression`, + `Register-ArgumentCompleter -Native -CommandName ${spec.program} -ScriptBlock {`, + ' param($wordToComplete, $commandAst, $cursorPosition)', + ` $commands = @(${spec.commands.map(c => "'" + c + "'").join(', ')})`, + ' if ($wordToComplete -like "-*") {', + ` $flags = @(${spec.globalFlags.map(f => "'" + f + "'").join(', ')})`, + ' $flags | Where-Object { $_ -like "$wordToComplete*" }', + ' return', + ' }', + ' $commands | Where-Object { $_ -like "$wordToComplete*" }', + '}', + ]; + return lines.join('\n'); +} + export function createCompletionCommand( getSpec: () => CompletionSpec, deps: CompletionDeps = {}, ): Command { return new Command('completion') - .description('Print a shell completion script (bash|zsh|fish)') + .description('Print a shell completion script (bash|zsh|fish|powershell)') .argument( '[shell]', - 'Shell to generate for (bash|zsh|fish); auto-detected from $SHELL when omitted', + 'Shell to generate for (bash|zsh|fish|powershell); auto-detected from $SHELL when omitted', ) .addHelpText( 'after', From 233ce194d202a853a0ac727519cd893566d83738 Mon Sep 17 00:00:00 2001 From: Lex Date: Thu, 22 Oct 2026 17:18:06 +0700 Subject: [PATCH 5/6] chore: trigger CI entry check From 730f28238888054f4700ad5b8a8319d897359d70 Mon Sep 17 00:00:00 2001 From: Lex Date: Thu, 22 Oct 2026 17:34:32 +0700 Subject: [PATCH 6/6] test(completion): cover powershell render --- src/commands/completion.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/commands/completion.test.ts b/src/commands/completion.test.ts index 6bc171b..e33de7e 100644 --- a/src/commands/completion.test.ts +++ b/src/commands/completion.test.ts @@ -60,6 +60,13 @@ describe('renderCompletion', () => { expect(script).toContain('__fish_seen_subcommand_from auth'); expect(script).toContain('-l output'); }); + + it('powershell script registers an argument completer and lists commands', () => { + const script = renderCompletion('powershell', SPEC); + expect(script).toContain('Register-ArgumentCompleter'); + expect(script).toContain('setup'); + expect(script).toContain('--output'); + }); }); describe('createCompletionCommand', () => {