cjsFallbackHandler was a no-op pass-through returning only its last argument, and state-command-router was its sole consumer — every handler wrapped a thunk in a 5-arg projection tuple whose first four args (registryCommand, registryArgs, legacyArgs, rawFormatter) were constructed and immediately discarded (rawFormatter null at 100% of sites). The SDK-dispatch path the metadata was nominally for is a retired shim. - Collapse all 20 state handlers to plain () => state.cmd*(...) thunks, matching the post-#298 inline convention used by the verify/roadmap/ phases routers (complete-phase already used this form). - Remove the local fallback() helper and Handler type from state-command-router.cts. - Drop cjsFallbackHandler from cjs-command-router-adapter.cts exports — no remaining consumers. Behavior-preserving: each thunk body is unchanged. No test referenced cjsFallbackHandler. Adapter test (8/8), state-routing tests (36/36), and the full unit suite (3435 pass / 0 fail) stay green. Source-only diff; gsd-core/bin/lib/*.cjs are built-at-publish per ADR-457. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
142 lines
3.8 KiB
TypeScript
142 lines
3.8 KiB
TypeScript
/**
|
|
* CJS Command Router Adapter Module
|
|
*
|
|
* Compatibility routing for gsd-tools.cjs command families. Uses generated
|
|
* command metadata for availability and small family-local argument shapers for
|
|
* CJS handler calls.
|
|
*
|
|
* ADR-457 build-at-publish: the hand-written bin/lib/cjs-command-router-adapter.cjs
|
|
* collapsed to a TypeScript source of truth. Behaviour is preserved byte-for-behaviour
|
|
* from the prior hand-written .cjs; only types are added.
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
import commandRoutingHub = require('./command-routing-hub.cjs');
|
|
const { createHub, ERROR_KINDS } = commandRoutingHub;
|
|
|
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
type Handler = () => unknown;
|
|
|
|
interface RouteCjsCommandFamilyOptions {
|
|
args: string[];
|
|
subcommands: string[];
|
|
handlers: Record<string, Handler>;
|
|
defaultSubcommand?: string;
|
|
unsupported?: Record<string, string>;
|
|
unknownMessage: (subcommand: string, available: string[]) => string;
|
|
error: (message: string) => void;
|
|
cwd?: string;
|
|
raw?: boolean;
|
|
}
|
|
|
|
interface RouteHubCommandFamilyOptions {
|
|
family: string;
|
|
args: string[];
|
|
subcommands: string[];
|
|
handlers: Record<string, Handler>;
|
|
defaultSubcommand?: string;
|
|
unsupported?: Record<string, string>;
|
|
unknownMessage: (subcommand: string, available: string[]) => string;
|
|
error: (message: string) => void;
|
|
cwd?: string;
|
|
raw?: boolean;
|
|
}
|
|
|
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
|
|
function routeCjsCommandFamily({
|
|
args,
|
|
subcommands,
|
|
handlers,
|
|
defaultSubcommand,
|
|
unsupported = {},
|
|
unknownMessage,
|
|
error,
|
|
cwd,
|
|
raw,
|
|
}: RouteCjsCommandFamilyOptions): void {
|
|
routeHubCommandFamily({
|
|
family: '__legacy_cjs_family__',
|
|
args,
|
|
subcommands,
|
|
handlers,
|
|
defaultSubcommand,
|
|
unsupported,
|
|
unknownMessage,
|
|
error,
|
|
cwd,
|
|
raw,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hub-backed family router adapter.
|
|
*
|
|
* Deepens the command-topology seam by routing family handlers through
|
|
* CommandRoutingHub's typed Result contract instead of ad-hoc per-router
|
|
* lookup + error handling branches.
|
|
*/
|
|
function routeHubCommandFamily({
|
|
family,
|
|
args,
|
|
subcommands,
|
|
handlers,
|
|
defaultSubcommand,
|
|
unsupported = {},
|
|
unknownMessage,
|
|
error,
|
|
cwd,
|
|
raw,
|
|
}: RouteHubCommandFamilyOptions): void {
|
|
const subcommand = args[1] || defaultSubcommand;
|
|
|
|
if (subcommand && unsupported[subcommand]) {
|
|
error(unsupported[subcommand]);
|
|
return;
|
|
}
|
|
|
|
const available = subcommands.filter((s) => !unsupported[s]);
|
|
const registryHandlers = Object.fromEntries(
|
|
Object.entries(handlers).map(([name, handler]) => [
|
|
name,
|
|
(): { ok: true; data: unknown } => {
|
|
const result = handler();
|
|
if (result && typeof result === 'object' && Object.prototype.hasOwnProperty.call(result, 'ok')) {
|
|
return result as { ok: true; data: unknown };
|
|
}
|
|
return { ok: true as const, data: null };
|
|
},
|
|
]),
|
|
);
|
|
|
|
const hub = createHub({
|
|
cjsRegistry: { [family]: registryHandlers },
|
|
manifest: { [family]: available },
|
|
});
|
|
|
|
const result = hub.dispatch({
|
|
family,
|
|
subcommand,
|
|
args: args.slice(2),
|
|
cwd,
|
|
raw,
|
|
});
|
|
|
|
if (result.ok) return;
|
|
if (result.kind === ERROR_KINDS.UnknownCommand) {
|
|
error(unknownMessage(subcommand ?? '', available));
|
|
return;
|
|
}
|
|
if (result.kind === ERROR_KINDS.InvalidArgs || result.kind === ERROR_KINDS.HandlerRefusal) {
|
|
error((result as { reason: string }).reason);
|
|
return;
|
|
}
|
|
error((result as { message: string }).message);
|
|
}
|
|
|
|
export = {
|
|
routeCjsCommandFamily,
|
|
routeHubCommandFamily,
|
|
};
|