fix(ci-setup): make interactive auth/branch-strategy prompts reachable

The `--auth` and `--branch-strategy` commander options carried default
values ('token'/'pr-scoped'), so the parsed options object always
populated them. ci-setup.ts then always set partial.auth/branchStrategy,
which made promptAuth()/promptBranchStrategy() in resolveOptions
unreachable dead code — the interactive wizard silently asked only 2 of
its 4 questions and a TTY user could never choose none/main-only.

Drop the commander defaults (like --ci/--deploy, which have none and do
prompt); resolveOptions still supplies the token/pr-scoped fallbacks for
non-TTY/CI runs, so non-interactive behavior is unchanged. The resolved
default is now surfaced in the i18n help descriptions instead of the
auto-appended commander "(default: …)" annotation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-06-14 08:55:24 +00:00
parent 8666342b9f
commit 0806ac7c44
4 changed files with 53 additions and 6 deletions

View file

@ -167,8 +167,9 @@ export const en = {
'help.option.ciSetup.ci': 'CI/CD system: github-actions, azure-devops, or both',
'help.option.ciSetup.deploy': 'Deploy target: docker, azure-container-app, or both',
'help.option.ciSetup.port': 'Host port to bind (container always runs on 4747)',
'help.option.ciSetup.auth': 'Auth mode: token (Caddy proxy) or none',
'help.option.ciSetup.branchStrategy': 'Index strategy: pr-scoped or main-only',
'help.option.ciSetup.auth': 'Auth mode: token (Caddy proxy) or none (defaults to token)',
'help.option.ciSetup.branchStrategy':
'Index strategy: pr-scoped or main-only (defaults to pr-scoped)',
'help.option.ciSetup.dryRun':
'Print generated files without writing (default when no mode flag given)',
'help.option.ciSetup.apply': 'Write files with per-file confirmation gates',

View file

@ -159,8 +159,8 @@ export const zhCN = {
'help.option.ciSetup.ci': 'CI/CD 系统:github-actions、azure-devops 或 both',
'help.option.ciSetup.deploy': '部署目标:docker、azure-container-app 或 both',
'help.option.ciSetup.port': '绑定的主机端口(容器始终运行在 4747)',
'help.option.ciSetup.auth': '认证模式:token(Caddy 代理)或 none',
'help.option.ciSetup.branchStrategy': '索引策略:pr-scoped 或 main-only',
'help.option.ciSetup.auth': '认证模式:token(Caddy 代理)或 none(默认 token)',
'help.option.ciSetup.branchStrategy': '索引策略:pr-scoped 或 main-only(默认 pr-scoped)',
'help.option.ciSetup.dryRun': '打印生成的文件而不写入(未指定模式标志时的默认行为)',
'help.option.ciSetup.apply': '写入文件,每个文件有独立确认步骤',
'help.option.ciSetup.yes': '跳过逐文件确认提示(与 --apply 配合使用)',

View file

@ -58,8 +58,8 @@ program
.option('--ci <system>', 'CI/CD system: github-actions, azure-devops, or both')
.option('--deploy <target>', 'Deploy target: docker, azure-container-app, or both')
.option('--port <port>', 'gitnexus serve port', '4747')
.option('--auth <mode>', 'Auth mode: token (Caddy proxy) or none', 'token')
.option('--branch-strategy <strategy>', 'Index strategy: pr-scoped or main-only', 'pr-scoped')
.option('--auth <mode>', 'Auth mode: token (Caddy proxy) or none')
.option('--branch-strategy <strategy>', 'Index strategy: pr-scoped or main-only')
.option('--dry-run', 'Print generated files without writing (default when no mode flag given)')
.option('--apply', 'Write files with per-file confirmation gates')
.option('--yes', 'Skip per-file confirmation prompts (use with --apply)')

View file

@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { select } from '@inquirer/prompts';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
@ -188,3 +189,48 @@ describe('ciSetupCommand', () => {
expect(entries).toHaveLength(0);
});
});
describe('resolveOptions (U1: interactive prompts reachable)', () => {
const detect = {
gitRoot: '/tmp/repo',
detectedCi: null,
hasDocker: false,
portAvailable: true,
primaryLanguage: 'TypeScript',
};
let originalTTY: boolean | undefined;
beforeEach(() => {
vi.clearAllMocks();
originalTTY = process.stdin.isTTY;
});
afterEach(() => {
Object.defineProperty(process.stdin, 'isTTY', { value: originalTTY, configurable: true });
});
it('TTY with no auth/branch flags prompts for them (prompts are not dead code)', async () => {
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
const { resolveOptions } = await import('../../src/cli/ci-setup/prompts.js');
await resolveOptions(detect, { dryRun: true });
// select() is the prompt primitive for all four questions; reaching it proves
// promptAuth/promptBranchStrategy are no longer short-circuited by a commander default.
expect(vi.mocked(select)).toHaveBeenCalled();
});
it('non-TTY falls back to token/pr-scoped without prompting', async () => {
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
const { resolveOptions } = await import('../../src/cli/ci-setup/prompts.js');
const resolved = await resolveOptions(detect, { dryRun: true });
expect(resolved.auth).toBe('token');
expect(resolved.branchStrategy).toBe('pr-scoped');
expect(vi.mocked(select)).not.toHaveBeenCalled();
});
it('explicit auth flag wins over the prompt', async () => {
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
const { resolveOptions } = await import('../../src/cli/ci-setup/prompts.js');
const resolved = await resolveOptions(detect, { auth: 'none', dryRun: true });
expect(resolved.auth).toBe('none');
});
});