mirror of
https://github.com/przeprogramowani/10x-cli.git
synced 2026-09-19 03:30:01 +08:00
857a825da6
Wave 2.1 PR1 lands two low-risk CLI polish items ahead of the prework release so first-run students benefit from a smarter default and the profile layer gains a drift-safety net. - Auto-detection: scan the project root for tool-native markers (.cursor/rules, .claude/, CLAUDE.md, .github/copilot-instructions.md, AGENTS.md, .agents/, .ai/) and rank signals by confidence. The interactive first-run prompt pre-fills initialValue with the detected tool and prints a "Detected: <tool> (<reason>)" hint. User still confirms — no silent selection. Non-TTY paths are unchanged. - Coherence invariants: SENTINEL_BEGIN/END hoisted to shared constants; every profile now references them. New describe block asserts 7 invariants (paths rooted under manifestDir, safe leaf names, non-empty rulesFile, identical sentinels, unique manifestDir, unique toolId, non-empty displayName) so refactors fail fast on CI. Plan: thoughts/shared/plans/2026-04-17-wave2.1-cli-polish.md (Phases 1-2) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
/**
|
|
* Shared module mock for @clack/prompts.
|
|
*
|
|
* Same singleton pattern as auth-flow-mock.ts so that any test file
|
|
* importing this helper installs the mock exactly once for the whole
|
|
* `bun test` process. The interactive `text()` / `select()` are steerable
|
|
* via clackMockState; `note()` messages are captured for assertions.
|
|
*
|
|
* The mock is harmless for tests that don't reach the interactive path —
|
|
* it only replaces the module, and every call site is covered below.
|
|
*/
|
|
|
|
import { mock } from "bun:test";
|
|
|
|
export interface SelectOption {
|
|
value: string;
|
|
label: string;
|
|
hint?: string | undefined;
|
|
}
|
|
|
|
export interface SelectOpts {
|
|
message: string;
|
|
options: SelectOption[];
|
|
initialValue?: string;
|
|
}
|
|
|
|
export interface ClackMockState {
|
|
textImpl: () => unknown;
|
|
selectImpl: (opts: SelectOpts) => unknown;
|
|
noteMessages: string[];
|
|
lastSelect: SelectOpts | null;
|
|
selectCalls: SelectOpts[];
|
|
}
|
|
|
|
function defaultSelectImpl(opts: SelectOpts): unknown {
|
|
return opts.initialValue ?? opts.options[0]?.value;
|
|
}
|
|
|
|
export const clackMockState: ClackMockState = {
|
|
textImpl: () => "you@example.com",
|
|
selectImpl: defaultSelectImpl,
|
|
noteMessages: [],
|
|
lastSelect: null,
|
|
selectCalls: [],
|
|
};
|
|
|
|
mock.module("@clack/prompts", () => ({
|
|
intro: () => undefined,
|
|
outro: () => undefined,
|
|
cancel: () => undefined,
|
|
note: (msg: string) => {
|
|
clackMockState.noteMessages.push(msg);
|
|
},
|
|
spinner: () => ({
|
|
start: () => undefined,
|
|
stop: () => undefined,
|
|
message: () => undefined,
|
|
}),
|
|
text: () => Promise.resolve(clackMockState.textImpl()),
|
|
select: (opts: SelectOpts) => {
|
|
clackMockState.lastSelect = opts;
|
|
clackMockState.selectCalls.push(opts);
|
|
return Promise.resolve(clackMockState.selectImpl(opts));
|
|
},
|
|
isCancel: (value: unknown) => typeof value === "symbol",
|
|
}));
|
|
|
|
export function resetClackMock(): void {
|
|
clackMockState.textImpl = () => "you@example.com";
|
|
clackMockState.selectImpl = defaultSelectImpl;
|
|
clackMockState.noteMessages = [];
|
|
clackMockState.lastSelect = null;
|
|
clackMockState.selectCalls = [];
|
|
}
|