From 3d092f76687659684a10a017762d82d5b70cc73b Mon Sep 17 00:00:00 2001 From: JounQin Date: Wed, 28 Oct 2026 01:32:56 +0000 Subject: [PATCH 1/4] fix: align gcb help with gh help paths gcb help now dispatches to --help; gcb help is not a supported alias, matching gh. --- packages/cli/src/index.ts | 8 +++++++- test/unit/commands.test.ts | 16 ++++++++++++++++ test/unit/gh-command.test.ts | 8 ++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 5dbab4fa..138dfa1c 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -66,7 +66,13 @@ export async function runCli(argv: readonly string[], backend: CommandBackend): process.stderr.write(`${backend.help}\n`); return 1; } - if (command === 'help' || command === '--help' || command === '-h') { + if (command === 'help') { + const path = argv.slice(1); + if (path.length > 0) return backend.run([...path, '--help']); + process.stdout.write(`${backend.help}\n`); + return 0; + } + if (command === '--help' || command === '-h') { process.stdout.write(`${backend.help}\n`); return 0; } diff --git a/test/unit/commands.test.ts b/test/unit/commands.test.ts index 05b894b3..cf560849 100644 --- a/test/unit/commands.test.ts +++ b/test/unit/commands.test.ts @@ -1247,3 +1247,19 @@ describe('the anchor flags, at the CLI boundary', () => { assert.doesNotMatch(noNumber.stderr, /--number is required/, 'without being asked for a number'); }); }); + +describe('the help path', () => { + it('routes gcb help pr to gcb pr --help', () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gcb-help-path-')); + try { + const result = execFileSync('node', ['--conditions=development', 'packages/client/bin/gcb.js', 'help', 'pr'], { + cwd: path.resolve('.'), + encoding: 'utf8', + env: { ...process.env, HOME: home, GIT_BROKER_SOCKET: '', GIT_BROKER_SOCKET_FILE: '' }, + }); + assert.match(result, /Usage: gcb pr/); + } finally { + fs.rmSync(home, { recursive: true, force: true }); + } + }); +}); diff --git a/test/unit/gh-command.test.ts b/test/unit/gh-command.test.ts index 962a799f..55ff1278 100644 --- a/test/unit/gh-command.test.ts +++ b/test/unit/gh-command.test.ts @@ -418,3 +418,11 @@ describe('the -- terminator', () => { assert.match(result.stderr, /too many positional arguments/); }); }); + +describe('the help path', () => { + it('routes gcb help gh pr to gh pr --help', async () => { + const result = await runClient(['help', 'gh', 'pr']); + assert.equal(result.status, 0); + assert.match(result.stdout, /Usage: gcb gh/); + }); +}); From 7190ad8251c5927c41eacceaf7eaf198e8e552e3 Mon Sep 17 00:00:00 2001 From: JounQin Date: Wed, 28 Oct 2026 02:30:59 +0000 Subject: [PATCH 2/4] fix: reject options in a help path help only accepts a command path; a -- or a flag-looking token is refused instead of being appended to a command invocation. --- packages/cli/src/index.ts | 8 +++++++- test/unit/gh-command.test.ts | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 138dfa1c..34132c04 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -68,7 +68,13 @@ export async function runCli(argv: readonly string[], backend: CommandBackend): } if (command === 'help') { const path = argv.slice(1); - if (path.length > 0) return backend.run([...path, '--help']); + if (path.length > 0) { + if (path.some((argument) => argument === '--' || argument.startsWith('-'))) { + process.stderr.write('gcb: help takes a command path, not options\n'); + return 1; + } + return backend.run([...path, '--help']); + } process.stdout.write(`${backend.help}\n`); return 0; } diff --git a/test/unit/gh-command.test.ts b/test/unit/gh-command.test.ts index 55ff1278..2412bfc1 100644 --- a/test/unit/gh-command.test.ts +++ b/test/unit/gh-command.test.ts @@ -426,3 +426,17 @@ describe('the help path', () => { assert.match(result.stdout, /Usage: gcb gh/); }); }); + +describe('an invalid help path', () => { + it('does not run a command when the path contains options', async () => { + for (const args of [ + ['help', 'pr', '--'], + ['help', 'pr', '--number'], + ['help', '--help'], + ]) { + const result = await runClient(args); + assert.equal(result.status, 1, args.join(' ')); + assert.match(result.stderr, /help takes a command path/); + } + }); +}); From 3fef94d977814491379058eb51f565177050ad74 Mon Sep 17 00:00:00 2001 From: JounQin Date: Wed, 28 Oct 2026 02:39:47 +0000 Subject: [PATCH 3/4] fix: recurse the help path through the dispatcher Keep the platform refusal and host-command message, treat help --help/help help as root help, and drop the redundant -- special case. --- packages/cli/src/index.ts | 19 +++++++++++-------- test/unit/gh-command.test.ts | 12 +++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 34132c04..308fb061 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -68,15 +68,18 @@ export async function runCli(argv: readonly string[], backend: CommandBackend): } if (command === 'help') { const path = argv.slice(1); - if (path.length > 0) { - if (path.some((argument) => argument === '--' || argument.startsWith('-'))) { - process.stderr.write('gcb: help takes a command path, not options\n'); - return 1; - } - return backend.run([...path, '--help']); + if ( + path.length === 0 || + (path.length === 1 && (path[0] === 'help' || path[0] === '--help' || path[0] === '-h')) + ) { + process.stdout.write(`${backend.help}\n`); + return 0; } - process.stdout.write(`${backend.help}\n`); - return 0; + if (path.some((argument) => argument.startsWith('-'))) { + process.stderr.write('gcb: help takes a command path, not options\n'); + return 1; + } + return runCli([...path, '--help'], backend); } if (command === '--help' || command === '-h') { process.stdout.write(`${backend.help}\n`); diff --git a/test/unit/gh-command.test.ts b/test/unit/gh-command.test.ts index 2412bfc1..14651d20 100644 --- a/test/unit/gh-command.test.ts +++ b/test/unit/gh-command.test.ts @@ -427,12 +427,22 @@ describe('the help path', () => { }); }); +describe('a help path that is the help command', () => { + it('prints root help for help --help and help help', async () => { + for (const args of [['help', '--help'], ['help', 'help']]) { + const result = await runClient(args); + assert.equal(result.status, 0, args.join(' ')); + assert.match(result.stdout, /Usage: gcb/); + } + }); +}); + describe('an invalid help path', () => { it('does not run a command when the path contains options', async () => { for (const args of [ ['help', 'pr', '--'], ['help', 'pr', '--number'], - ['help', '--help'], + ['help', 'pr', '--help'], ]) { const result = await runClient(args); assert.equal(result.status, 1, args.join(' ')); From 14ec5e1de95b6fa6f83949f3908a58670cccf934 Mon Sep 17 00:00:00 2001 From: JounQin Date: Wed, 28 Oct 2026 03:32:22 +0000 Subject: [PATCH 4/4] test: cover the help host refusal path --- test/unit/gh-command.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/unit/gh-command.test.ts b/test/unit/gh-command.test.ts index 14651d20..1a32e1da 100644 --- a/test/unit/gh-command.test.ts +++ b/test/unit/gh-command.test.ts @@ -450,3 +450,16 @@ describe('an invalid help path', () => { } }); }); + +describe('command-specific help refusal', () => { + it('runs the dispatcher checks for help host', async () => { + const result = await runClient(['help', 'host']); + assert.equal(result.status, 1); + assert.equal(result.stdout, ''); + if (process.platform === 'win32') { + assert.match(result.stderr, /native Windows is not supported/); + } else { + assert.match(result.stderr, /`host` is a host command; install gcb-host on the host/); + } + }); +});