@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.generated\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
if git diff --cached --name-only | grep -Eq "^sdk/src/query/command-manifest\.|^sdk/src/query/command-aliases\.generated\.ts$|^get-shit-done/bin/lib/command-aliases\.cjs$|^sdk/scripts/gen-command-aliases\.ts$"; then
|
||||
npm run check:alias-drift
|
||||
fi
|
||||
|
||||
|
||||
@@ -198,6 +198,7 @@ const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
||||
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
||||
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
|
||||
const { routeRoadmapCommand } = require('./lib/roadmap-command-router.cjs');
|
||||
const { parseNamedArgs, parseMultiwordArg } = require('./lib/command-arg-projection.cjs');
|
||||
|
||||
// ─── Bridge collapsed (Phase 4) ────────────────────────────────────────────────
|
||||
// Non-family commands now run through their CJS handlers directly. Keep the
|
||||
@@ -235,44 +236,6 @@ function _dispatchNonFamily({ registryCommand, registryArgs, legacyCommand, lega
|
||||
|
||||
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Extract named --flag <value> pairs from an args array.
|
||||
* Returns an object mapping flag names to their values (null if absent).
|
||||
* Flags listed in `booleanFlags` are treated as boolean (no value consumed).
|
||||
*
|
||||
* parseNamedArgs(args, 'phase', 'plan') → { phase: '3', plan: '1' }
|
||||
* parseNamedArgs(args, [], ['amend', 'force']) → { amend: true, force: false }
|
||||
*/
|
||||
function parseNamedArgs(args, valueFlags = [], booleanFlags = []) {
|
||||
const result = {};
|
||||
for (const flag of valueFlags) {
|
||||
const idx = args.indexOf(`--${flag}`);
|
||||
result[flag] = idx !== -1 && args[idx + 1] !== undefined && !args[idx + 1].startsWith('--')
|
||||
? args[idx + 1]
|
||||
: null;
|
||||
}
|
||||
for (const flag of booleanFlags) {
|
||||
result[flag] = args.includes(`--${flag}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all tokens after --flag until the next --flag or end of args.
|
||||
* Handles multi-word values like --name Foo Bar Version 1.
|
||||
* Returns null if the flag is absent.
|
||||
*/
|
||||
function parseMultiwordArg(args, flag) {
|
||||
const idx = args.indexOf(`--${flag}`);
|
||||
if (idx === -1) return null;
|
||||
const tokens = [];
|
||||
for (let i = idx + 1; i < args.length; i++) {
|
||||
if (args[i].startsWith('--')) break;
|
||||
tokens.push(args[i]);
|
||||
}
|
||||
return tokens.length > 0 ? tokens.join(' ') : null;
|
||||
}
|
||||
|
||||
// ─── CLI Router ───────────────────────────────────────────────────────────────
|
||||
|
||||
async function main() {
|
||||
@@ -563,7 +526,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
@@ -935,7 +897,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
output: core.output,
|
||||
error,
|
||||
});
|
||||
@@ -1015,7 +976,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
args,
|
||||
cwd,
|
||||
raw,
|
||||
parseNamedArgs,
|
||||
error,
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { createHub, ERROR_KINDS } = require('./command-routing-hub.cjs');
|
||||
|
||||
/**
|
||||
* CJS Command Router Adapter Module
|
||||
*
|
||||
@@ -16,6 +18,41 @@ function routeCjsCommandFamily({
|
||||
unsupported = {},
|
||||
unknownMessage,
|
||||
error,
|
||||
cwd,
|
||||
raw,
|
||||
}) {
|
||||
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,
|
||||
}) {
|
||||
const subcommand = args[1] || defaultSubcommand;
|
||||
|
||||
@@ -24,16 +61,58 @@ function routeCjsCommandFamily({
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = subcommand ? handlers[subcommand] : null;
|
||||
if (handler) {
|
||||
handler();
|
||||
const available = subcommands.filter((s) => !unsupported[s]);
|
||||
const registryHandlers = Object.fromEntries(
|
||||
Object.entries(handlers).map(([name, handler]) => [
|
||||
name,
|
||||
() => {
|
||||
const result = handler();
|
||||
if (result && typeof result === 'object' && Object.prototype.hasOwnProperty.call(result, 'ok')) {
|
||||
return result;
|
||||
}
|
||||
return { ok: true, 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.reason);
|
||||
return;
|
||||
}
|
||||
error(result.message);
|
||||
}
|
||||
|
||||
const available = subcommands.filter(s => !unsupported[s]);
|
||||
error(unknownMessage(subcommand, available));
|
||||
/**
|
||||
* Projection helper for family routers that still declare SDK registry metadata
|
||||
* but execute the CJS fallback path at this seam.
|
||||
*
|
||||
* Accepts variable argument shapes so routers can pass legacy projection tuples
|
||||
* (`registryCommand`, `registryArgs`, `legacyArgs`, optional `rawFormatter`, `cjsFallback`).
|
||||
*/
|
||||
function cjsFallbackHandler(...projectionArgs) {
|
||||
return projectionArgs[projectionArgs.length - 1];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
routeCjsCommandFamily,
|
||||
routeHubCommandFamily,
|
||||
cjsFallbackHandler,
|
||||
};
|
||||
|
||||
55
get-shit-done/bin/lib/command-arg-projection.cjs
Normal file
55
get-shit-done/bin/lib/command-arg-projection.cjs
Normal file
@@ -0,0 +1,55 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Command Argument Projection Module
|
||||
*
|
||||
* Shared helpers for command-family adapters to project argv tokens into
|
||||
* typed named values and multi-word segments.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extract named --flag <value> pairs from an args array.
|
||||
* Returns an object mapping flag names to their values (null if absent).
|
||||
* Flags listed in `booleanFlags` are treated as booleans.
|
||||
*
|
||||
* @param {string[]} args
|
||||
* @param {string[]} [valueFlags]
|
||||
* @param {string[]} [booleanFlags]
|
||||
* @returns {Record<string, string|boolean|null>}
|
||||
*/
|
||||
function parseNamedArgs(args, valueFlags = [], booleanFlags = []) {
|
||||
const result = {};
|
||||
for (const flag of valueFlags) {
|
||||
const idx = args.indexOf(`--${flag}`);
|
||||
result[flag] = idx !== -1 && args[idx + 1] !== undefined && !args[idx + 1].startsWith('--')
|
||||
? args[idx + 1]
|
||||
: null;
|
||||
}
|
||||
for (const flag of booleanFlags) {
|
||||
result[flag] = args.includes(`--${flag}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all tokens after --flag until the next --flag or end of args.
|
||||
*
|
||||
* @param {string[]} args
|
||||
* @param {string} flag
|
||||
* @returns {string|null}
|
||||
*/
|
||||
function parseMultiwordArg(args, flag) {
|
||||
const idx = args.indexOf(`--${flag}`);
|
||||
if (idx === -1) return null;
|
||||
const tokens = [];
|
||||
for (let i = idx + 1; i < args.length; i++) {
|
||||
if (args[i].startsWith('--')) break;
|
||||
tokens.push(args[i]);
|
||||
}
|
||||
return tokens.length > 0 ? tokens.join(' ') : null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseNamedArgs,
|
||||
parseMultiwordArg,
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const { INIT_SUBCOMMANDS } = require('./command-aliases.cjs');
|
||||
const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
const { parseNamedArgs } = require('./command-arg-projection.cjs');
|
||||
|
||||
/**
|
||||
* Manifest-backed init subcommand router.
|
||||
@@ -15,11 +16,7 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
* CJS-only subcommands: none.
|
||||
* SDK-only (unsupported in CJS router): none.
|
||||
*/
|
||||
function routeInitCommand({ init, args, cwd, raw, parseNamedArgs, error }) {
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
function routeInitCommand({ init, args, cwd, raw, error }) {
|
||||
routeCjsCommandFamily({
|
||||
args,
|
||||
subcommands: INIT_SUBCOMMANDS,
|
||||
@@ -27,111 +24,31 @@ function routeInitCommand({ init, args, cwd, raw, parseNamedArgs, error }) {
|
||||
error,
|
||||
unknownMessage: (_subcommand, available) => `Unknown init workflow: ${_subcommand}\nAvailable: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
'execute-phase': sdkHandler(
|
||||
'init.execute-phase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => {
|
||||
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
|
||||
},
|
||||
),
|
||||
'plan-phase': sdkHandler(
|
||||
'init.plan-phase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => {
|
||||
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
|
||||
},
|
||||
),
|
||||
'new-project': sdkHandler(
|
||||
'init.new-project',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitNewProject(cwd, raw),
|
||||
),
|
||||
'new-milestone': sdkHandler(
|
||||
'init.new-milestone',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitNewMilestone(cwd, raw),
|
||||
),
|
||||
quick: sdkHandler(
|
||||
'init.quick',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitQuick(cwd, args.slice(2).join(' '), raw),
|
||||
),
|
||||
'ingest-docs': sdkHandler(
|
||||
'init.ingest-docs',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitIngestDocs(cwd, raw),
|
||||
),
|
||||
resume: sdkHandler(
|
||||
'init.resume',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitResume(cwd, raw),
|
||||
),
|
||||
'verify-work': sdkHandler(
|
||||
'init.verify-work',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitVerifyWork(cwd, args[2], raw),
|
||||
),
|
||||
'phase-op': sdkHandler(
|
||||
'init.phase-op',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitPhaseOp(cwd, args[2], raw),
|
||||
),
|
||||
todos: sdkHandler(
|
||||
'init.todos',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitTodos(cwd, args[2], raw),
|
||||
),
|
||||
'milestone-op': sdkHandler(
|
||||
'init.milestone-op',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitMilestoneOp(cwd, raw),
|
||||
),
|
||||
'map-codebase': sdkHandler(
|
||||
'init.map-codebase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitMapCodebase(cwd, raw),
|
||||
),
|
||||
progress: sdkHandler(
|
||||
'init.progress',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitProgress(cwd, raw),
|
||||
),
|
||||
'execute-phase': () => {
|
||||
const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd });
|
||||
},
|
||||
'plan-phase': () => {
|
||||
const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']);
|
||||
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd });
|
||||
},
|
||||
'new-project': () => init.cmdInitNewProject(cwd, raw),
|
||||
'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
|
||||
quick: () => init.cmdInitQuick(cwd, args.slice(2).join(' '), raw),
|
||||
'ingest-docs': () => init.cmdInitIngestDocs(cwd, raw),
|
||||
resume: () => init.cmdInitResume(cwd, raw),
|
||||
'verify-work': () => init.cmdInitVerifyWork(cwd, args[2], raw),
|
||||
'phase-op': () => init.cmdInitPhaseOp(cwd, args[2], raw),
|
||||
todos: () => init.cmdInitTodos(cwd, args[2], raw),
|
||||
'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),
|
||||
'map-codebase': () => init.cmdInitMapCodebase(cwd, raw),
|
||||
progress: () => init.cmdInitProgress(cwd, raw),
|
||||
// Keep manager on CJS for now so runtime-specific command rendering
|
||||
// (e.g. $gsd-* for codex) stays consistent with runtime-slash helpers.
|
||||
manager: () => init.cmdInitManager(cwd, raw),
|
||||
'new-workspace': sdkHandler(
|
||||
'init.new-workspace',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitNewWorkspace(cwd, raw),
|
||||
),
|
||||
'list-workspaces': sdkHandler(
|
||||
'init.list-workspaces',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitListWorkspaces(cwd, raw),
|
||||
),
|
||||
'remove-workspace': sdkHandler(
|
||||
'init.remove-workspace',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => init.cmdInitRemoveWorkspace(cwd, args[2], raw),
|
||||
),
|
||||
'new-workspace': () => init.cmdInitNewWorkspace(cwd, raw),
|
||||
'list-workspaces': () => init.cmdInitListWorkspaces(cwd, raw),
|
||||
'remove-workspace': () => init.cmdInitRemoveWorkspace(cwd, args[2], raw),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
* CJS-only subcommands: none.
|
||||
*/
|
||||
function routePhasesCommand({ phase, milestone, args, cwd, raw, error }) {
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args,
|
||||
// Exclude 'archive' — it's SDK-only and not supported in CJS. Excluding
|
||||
@@ -34,27 +30,17 @@ function routePhasesCommand({ phase, milestone, args, cwd, raw, error }) {
|
||||
error,
|
||||
unknownMessage: (_subcommand, available) => `Unknown phases subcommand. Available: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
list: sdkHandler(
|
||||
'phases.list',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => {
|
||||
const typeIndex = args.indexOf('--type');
|
||||
const phaseIndex = args.indexOf('--phase');
|
||||
const options = {
|
||||
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
||||
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
||||
includeArchived: args.includes('--include-archived'),
|
||||
};
|
||||
phase.cmdPhasesList(cwd, options, raw);
|
||||
},
|
||||
),
|
||||
clear: sdkHandler(
|
||||
'phases.clear',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => milestone.cmdPhasesClear(cwd, raw, args.slice(2)),
|
||||
),
|
||||
list: () => {
|
||||
const typeIndex = args.indexOf('--type');
|
||||
const phaseIndex = args.indexOf('--phase');
|
||||
const options = {
|
||||
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
||||
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
||||
includeArchived: args.includes('--include-archived'),
|
||||
};
|
||||
phase.cmdPhasesList(cwd, options, raw);
|
||||
},
|
||||
clear: () => milestone.cmdPhasesClear(cwd, raw, args.slice(2)),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
* SDK-only (unsupported in CJS router): none.
|
||||
*/
|
||||
function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args,
|
||||
subcommands: ROADMAP_SUBCOMMANDS,
|
||||
@@ -27,30 +23,10 @@ function routeRoadmapCommand({ roadmap, args, cwd, raw, error }) {
|
||||
error,
|
||||
unknownMessage: (_subcommand, available) => `Unknown roadmap subcommand. Available: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
'get-phase': sdkHandler(
|
||||
'roadmap.get-phase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => roadmap.cmdRoadmapGetPhase(cwd, args[2], raw),
|
||||
),
|
||||
analyze: sdkHandler(
|
||||
'roadmap.analyze',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => roadmap.cmdRoadmapAnalyze(cwd, raw),
|
||||
),
|
||||
'update-plan-progress': sdkHandler(
|
||||
'roadmap.update-plan-progress',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw),
|
||||
),
|
||||
'annotate-dependencies': sdkHandler(
|
||||
'roadmap.annotate-dependencies',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw),
|
||||
),
|
||||
'get-phase': () => roadmap.cmdRoadmapGetPhase(cwd, args[2], raw),
|
||||
analyze: () => roadmap.cmdRoadmapAnalyze(cwd, raw),
|
||||
'update-plan-progress': () => roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw),
|
||||
'annotate-dependencies': () => roadmap.cmdRoadmapAnnotateDependencies(cwd, args[2], raw),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const { STATE_SUBCOMMANDS } = require('./command-aliases.cjs');
|
||||
const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
const { routeHubCommandFamily, cjsFallbackHandler } = require('./cjs-command-router-adapter.cjs');
|
||||
const { parseNamedArgs } = require('./command-arg-projection.cjs');
|
||||
|
||||
/**
|
||||
* Manifest-backed state subcommand router.
|
||||
@@ -14,7 +15,7 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
* for workstream requests; subprocess is disabled in the sync bridge worker).
|
||||
* - Any command when the SDK is not available (build not present).
|
||||
*/
|
||||
function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
function routeStateCommand({ state, args, cwd, raw, error }) {
|
||||
const parsePlans = (plans) => {
|
||||
const parsedPlans = plans == null ? null : Number.parseInt(plans, 10);
|
||||
if (plans != null && Number.isNaN(parsedPlans)) {
|
||||
@@ -24,11 +25,8 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
return parsedPlans;
|
||||
};
|
||||
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, _rawFormatter, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
routeCjsCommandFamily({
|
||||
routeHubCommandFamily({
|
||||
family: 'state',
|
||||
args,
|
||||
subcommands: ['load', 'complete-phase', ...STATE_SUBCOMMANDS.filter((s) => s !== 'load')],
|
||||
defaultSubcommand: 'load',
|
||||
@@ -36,37 +34,39 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
'add-roadmap-evolution': 'state add-roadmap-evolution is SDK-only. Use: gsd-sdk query state.add-roadmap-evolution ...',
|
||||
},
|
||||
error,
|
||||
cwd,
|
||||
raw,
|
||||
unknownMessage: (subcommand, available) => `Unknown state subcommand: "${subcommand}". Available: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
load: sdkHandler(
|
||||
load: cjsFallbackHandler(
|
||||
'state.load',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateLoad(cwd, raw),
|
||||
),
|
||||
json: sdkHandler(
|
||||
json: cjsFallbackHandler(
|
||||
'state.json',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateJson(cwd, raw),
|
||||
),
|
||||
get: sdkHandler(
|
||||
get: cjsFallbackHandler(
|
||||
'state.get',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateGet(cwd, args[2], raw),
|
||||
),
|
||||
update: sdkHandler(
|
||||
update: cjsFallbackHandler(
|
||||
'state.update',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateUpdate(cwd, args[2], args[3]),
|
||||
),
|
||||
patch: sdkHandler(
|
||||
patch: cjsFallbackHandler(
|
||||
'state.patch',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -83,14 +83,14 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStatePatch(cwd, patches, raw);
|
||||
},
|
||||
),
|
||||
'advance-plan': sdkHandler(
|
||||
'advance-plan': cjsFallbackHandler(
|
||||
'state.advance-plan',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateAdvancePlan(cwd, raw),
|
||||
),
|
||||
'record-metric': sdkHandler(
|
||||
'record-metric': cjsFallbackHandler(
|
||||
'state.record-metric',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -100,14 +100,14 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateRecordMetric(cwd, { phase: p, plan, duration, tasks, files }, raw);
|
||||
},
|
||||
),
|
||||
'update-progress': sdkHandler(
|
||||
'update-progress': cjsFallbackHandler(
|
||||
'state.update-progress',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateUpdateProgress(cwd, raw),
|
||||
),
|
||||
'add-decision': sdkHandler(
|
||||
'add-decision': cjsFallbackHandler(
|
||||
'state.add-decision',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -117,7 +117,7 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateAddDecision(cwd, { phase: p, summary, summary_file, rationale: rationale || '', rationale_file }, raw);
|
||||
},
|
||||
),
|
||||
'add-blocker': sdkHandler(
|
||||
'add-blocker': cjsFallbackHandler(
|
||||
'state.add-blocker',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -127,14 +127,14 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateAddBlocker(cwd, { text, text_file }, raw);
|
||||
},
|
||||
),
|
||||
'resolve-blocker': sdkHandler(
|
||||
'resolve-blocker': cjsFallbackHandler(
|
||||
'state.resolve-blocker',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateResolveBlocker(cwd, parseNamedArgs(args, ['text']).text, raw),
|
||||
),
|
||||
'record-session': sdkHandler(
|
||||
'record-session': cjsFallbackHandler(
|
||||
'state.record-session',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -144,7 +144,7 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateRecordSession(cwd, { stopped_at, resume_file: resume_file || 'None' }, raw);
|
||||
},
|
||||
),
|
||||
'begin-phase': sdkHandler(
|
||||
'begin-phase': cjsFallbackHandler(
|
||||
'state.begin-phase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -154,7 +154,7 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateBeginPhase(cwd, p, name, parsePlans(plans), raw);
|
||||
},
|
||||
),
|
||||
'signal-waiting': sdkHandler(
|
||||
'signal-waiting': cjsFallbackHandler(
|
||||
'state.signal-waiting',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -164,14 +164,14 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdSignalWaiting(cwd, type, question, options, p, raw);
|
||||
},
|
||||
),
|
||||
'signal-resume': sdkHandler(
|
||||
'signal-resume': cjsFallbackHandler(
|
||||
'state.signal-resume',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdSignalResume(cwd, raw),
|
||||
),
|
||||
'planned-phase': sdkHandler(
|
||||
'planned-phase': cjsFallbackHandler(
|
||||
'state.planned-phase',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -181,14 +181,14 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStatePlannedPhase(cwd, p, parsePlans(plans), raw);
|
||||
},
|
||||
),
|
||||
validate: sdkHandler(
|
||||
validate: cjsFallbackHandler(
|
||||
'state.validate',
|
||||
[],
|
||||
args.slice(1),
|
||||
null,
|
||||
() => state.cmdStateValidate(cwd, raw),
|
||||
),
|
||||
sync: sdkHandler(
|
||||
sync: cjsFallbackHandler(
|
||||
'state.sync',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -198,7 +198,7 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
state.cmdStateSync(cwd, { verify }, raw);
|
||||
},
|
||||
),
|
||||
prune: sdkHandler(
|
||||
prune: cjsFallbackHandler(
|
||||
'state.prune',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
@@ -213,7 +213,7 @@ function routeStateCommand({ state, args, cwd, raw, parseNamedArgs, error }) {
|
||||
const { phase: p } = parseNamedArgs(args, ['phase']);
|
||||
state.cmdStateCompletePhase(cwd, raw, p || args[2]);
|
||||
},
|
||||
'milestone-switch': sdkHandler(
|
||||
'milestone-switch': cjsFallbackHandler(
|
||||
'state.milestone-switch',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const { VALIDATE_SUBCOMMANDS } = require('./command-aliases.cjs');
|
||||
const { formatGsdSlash, resolveRuntime } = require('./runtime-slash.cjs');
|
||||
const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
const { parseNamedArgs } = require('./command-arg-projection.cjs');
|
||||
|
||||
/**
|
||||
* Manifest-backed validate subcommand router.
|
||||
@@ -20,11 +21,7 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
*
|
||||
* SDK-only (unsupported in CJS router): none.
|
||||
*/
|
||||
function routeValidateCommand({ verify, args, cwd, raw, parseNamedArgs, output: outputFn, error }) {
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
function routeValidateCommand({ verify, args, cwd, raw, output: outputFn, error }) {
|
||||
routeCjsCommandFamily({
|
||||
args,
|
||||
subcommands: VALIDATE_SUBCOMMANDS,
|
||||
@@ -32,12 +29,7 @@ function routeValidateCommand({ verify, args, cwd, raw, parseNamedArgs, output:
|
||||
error,
|
||||
unknownMessage: (_subcommand, available) => `Unknown validate subcommand. Available: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
consistency: sdkHandler(
|
||||
'validate.consistency',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdValidateConsistency(cwd, raw),
|
||||
),
|
||||
consistency: () => verify.cmdValidateConsistency(cwd, raw),
|
||||
// Keep health on CJS for now so fix hints are rendered via runtime-slash
|
||||
// helpers (codex expects $gsd-* command shape).
|
||||
health: () => {
|
||||
@@ -45,12 +37,7 @@ function routeValidateCommand({ verify, args, cwd, raw, parseNamedArgs, output:
|
||||
const backfillFlag = args.includes('--backfill');
|
||||
verify.cmdValidateHealth(cwd, { repair: repairFlag, backfill: backfillFlag }, raw);
|
||||
},
|
||||
agents: sdkHandler(
|
||||
'validate.agents',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdValidateAgents(cwd, raw),
|
||||
),
|
||||
agents: () => verify.cmdValidateAgents(cwd, raw),
|
||||
// context: CJS-only — complex inline logic using classifyContextUtilization
|
||||
// with custom output formatting that has no direct SDK counterpart.
|
||||
context: () => {
|
||||
|
||||
@@ -16,10 +16,6 @@ const { routeCjsCommandFamily } = require('./cjs-command-router-adapter.cjs');
|
||||
* SDK-only (unsupported in CJS router): none.
|
||||
*/
|
||||
function routeVerifyCommand({ verify, args, cwd, raw, error }) {
|
||||
function sdkHandler(_registryCommand, _registryArgs, _legacyArgs, cjsFallback) {
|
||||
return cjsFallback;
|
||||
}
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args,
|
||||
subcommands: VERIFY_SUBCOMMANDS,
|
||||
@@ -27,53 +23,18 @@ function routeVerifyCommand({ verify, args, cwd, raw, error }) {
|
||||
error,
|
||||
unknownMessage: (_subcommand, available) => `Unknown verify subcommand. Available: ${available.join(', ')}`,
|
||||
handlers: {
|
||||
'plan-structure': sdkHandler(
|
||||
'verify.plan-structure',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyPlanStructure(cwd, args[2], raw),
|
||||
),
|
||||
'phase-completeness': sdkHandler(
|
||||
'verify.phase-completeness',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw),
|
||||
),
|
||||
references: sdkHandler(
|
||||
'verify.references',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyReferences(cwd, args[2], raw),
|
||||
),
|
||||
commits: sdkHandler(
|
||||
'verify.commits',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyCommits(cwd, args.slice(2), raw),
|
||||
),
|
||||
artifacts: sdkHandler(
|
||||
'verify.artifacts',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyArtifacts(cwd, args[2], raw),
|
||||
),
|
||||
'key-links': sdkHandler(
|
||||
'verify.key-links',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => verify.cmdVerifyKeyLinks(cwd, args[2], raw),
|
||||
),
|
||||
'schema-drift': sdkHandler(
|
||||
'verify.schema-drift',
|
||||
args.slice(2),
|
||||
args.slice(1),
|
||||
() => {
|
||||
const rest = args.slice(2);
|
||||
const skipFlag = rest.includes('--skip');
|
||||
const phaseArg = rest.find((arg) => !arg.startsWith('-'));
|
||||
verify.cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw);
|
||||
},
|
||||
),
|
||||
'plan-structure': () => verify.cmdVerifyPlanStructure(cwd, args[2], raw),
|
||||
'phase-completeness': () => verify.cmdVerifyPhaseCompleteness(cwd, args[2], raw),
|
||||
references: () => verify.cmdVerifyReferences(cwd, args[2], raw),
|
||||
commits: () => verify.cmdVerifyCommits(cwd, args.slice(2), raw),
|
||||
artifacts: () => verify.cmdVerifyArtifacts(cwd, args[2], raw),
|
||||
'key-links': () => verify.cmdVerifyKeyLinks(cwd, args[2], raw),
|
||||
'schema-drift': () => {
|
||||
const rest = args.slice(2);
|
||||
const skipFlag = rest.includes('--skip');
|
||||
const phaseArg = rest.find((arg) => !arg.startsWith('-'));
|
||||
verify.cmdVerifySchemaDrift(cwd, phaseArg, skipFlag, raw);
|
||||
},
|
||||
// verify codebase-drift dispatches direct to CJS — drift is out-of-seam
|
||||
// per ADR/PRD 3524 §3 / L160 (CJS-only by design). Routing through
|
||||
// sdkHandler would re-enter the SDK bridge, and Phase 6's removed
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"check:env": "bash scripts/check-env.sh",
|
||||
"check:alias-drift": "node scripts/check-alias-drift.cjs",
|
||||
"check:integrity": "./scripts/check-npm-integrity.sh",
|
||||
"build": "npm run build:hooks",
|
||||
"build:hooks": "node scripts/build-hooks.js",
|
||||
|
||||
108
scripts/check-alias-drift.cjs
Normal file
108
scripts/check-alias-drift.cjs
Normal file
@@ -0,0 +1,108 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const ROOT = path.resolve(__dirname, '..');
|
||||
const aliasesPath = path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'command-aliases.cjs');
|
||||
|
||||
function fail(message) {
|
||||
process.stderr.write(`${message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function ensureArray(value, name) {
|
||||
if (!Array.isArray(value)) {
|
||||
fail(`check:alias-drift: expected ${name} to be an array`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertNoDuplicates(values, label) {
|
||||
const seen = new Set();
|
||||
for (const value of values) {
|
||||
if (seen.has(value)) {
|
||||
fail(`check:alias-drift: duplicate ${label} value "${value}"`);
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(aliasesPath)) {
|
||||
fail(`check:alias-drift: missing ${path.relative(ROOT, aliasesPath)}`);
|
||||
}
|
||||
|
||||
const aliases = require(aliasesPath);
|
||||
|
||||
const families = [
|
||||
{
|
||||
commandAliases: 'STATE_COMMAND_ALIASES',
|
||||
subcommands: 'STATE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'state-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'VERIFY_COMMAND_ALIASES',
|
||||
subcommands: 'VERIFY_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'verify-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'INIT_COMMAND_ALIASES',
|
||||
subcommands: 'INIT_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'init-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'PHASE_COMMAND_ALIASES',
|
||||
subcommands: 'PHASE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'phase-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'PHASES_COMMAND_ALIASES',
|
||||
subcommands: 'PHASES_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'phases-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'VALIDATE_COMMAND_ALIASES',
|
||||
subcommands: 'VALIDATE_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'validate-command-router.cjs'),
|
||||
},
|
||||
{
|
||||
commandAliases: 'ROADMAP_COMMAND_ALIASES',
|
||||
subcommands: 'ROADMAP_SUBCOMMANDS',
|
||||
routerPath: path.join(ROOT, 'get-shit-done', 'bin', 'lib', 'roadmap-command-router.cjs'),
|
||||
},
|
||||
];
|
||||
|
||||
for (const family of families) {
|
||||
const commandAliases = aliases[family.commandAliases];
|
||||
const subcommands = aliases[family.subcommands];
|
||||
|
||||
ensureArray(commandAliases, family.commandAliases);
|
||||
ensureArray(subcommands, family.subcommands);
|
||||
|
||||
const derivedSubcommands = commandAliases.map((entry) => entry && entry.subcommand);
|
||||
assertNoDuplicates(derivedSubcommands, `${family.commandAliases}.subcommand`);
|
||||
|
||||
if (derivedSubcommands.length !== subcommands.length) {
|
||||
fail(
|
||||
`check:alias-drift: ${family.subcommands} length ${subcommands.length} does not match ` +
|
||||
`${family.commandAliases} length ${derivedSubcommands.length}`,
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < derivedSubcommands.length; i++) {
|
||||
if (derivedSubcommands[i] !== subcommands[i]) {
|
||||
fail(
|
||||
`check:alias-drift: ${family.subcommands}[${i}] = "${subcommands[i]}" ` +
|
||||
`does not match ${family.commandAliases}[${i}].subcommand = "${derivedSubcommands[i]}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const routerSource = fs.readFileSync(family.routerPath, 'utf8');
|
||||
if (!routerSource.includes(family.subcommands)) {
|
||||
fail(
|
||||
`check:alias-drift: ${path.relative(ROOT, family.routerPath)} does not reference ${family.subcommands}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write('check:alias-drift ok\n');
|
||||
191
tests/cjs-command-router-adapter.test.cjs
Normal file
191
tests/cjs-command-router-adapter.test.cjs
Normal file
@@ -0,0 +1,191 @@
|
||||
'use strict';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
const { routeCjsCommandFamily, routeHubCommandFamily } = require('../get-shit-done/bin/lib/cjs-command-router-adapter.cjs');
|
||||
const { makeInvalidArgs } = require('../get-shit-done/bin/lib/command-routing-hub.cjs');
|
||||
|
||||
describe('cjs-command-router-adapter routeHubCommandFamily', () => {
|
||||
test('routes known subcommand handler through the hub', () => {
|
||||
let calls = 0;
|
||||
let errorMessage = null;
|
||||
|
||||
routeHubCommandFamily({
|
||||
family: 'unit',
|
||||
args: ['unit', 'ok'],
|
||||
subcommands: ['ok'],
|
||||
handlers: {
|
||||
ok: () => {
|
||||
calls += 1;
|
||||
},
|
||||
},
|
||||
unknownMessage: (subcommand, available) => `Unknown ${subcommand}. Available: ${available.join(', ')}`,
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(errorMessage, null);
|
||||
});
|
||||
|
||||
test('maps unknown subcommands via unknownMessage and filtered availability', () => {
|
||||
let errorMessage = null;
|
||||
|
||||
routeHubCommandFamily({
|
||||
family: 'unit',
|
||||
args: ['unit', 'missing'],
|
||||
subcommands: ['ok', 'legacy'],
|
||||
unsupported: { legacy: 'legacy disabled' },
|
||||
handlers: { ok: () => {} },
|
||||
unknownMessage: (subcommand, available) => `Unknown ${subcommand}. Available: ${available.join(', ')}`,
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(errorMessage, 'Unknown missing. Available: ok');
|
||||
});
|
||||
|
||||
test('returns unsupported subcommand error before dispatch', () => {
|
||||
let errorMessage = null;
|
||||
|
||||
routeHubCommandFamily({
|
||||
family: 'unit',
|
||||
args: ['unit', 'legacy'],
|
||||
subcommands: ['ok', 'legacy'],
|
||||
unsupported: { legacy: 'legacy disabled' },
|
||||
handlers: { ok: () => {} },
|
||||
unknownMessage: () => 'should not be used',
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(errorMessage, 'legacy disabled');
|
||||
});
|
||||
|
||||
test('projects InvalidArgs result reason via error callback', () => {
|
||||
let errorMessage = null;
|
||||
|
||||
routeHubCommandFamily({
|
||||
family: 'unit',
|
||||
args: ['unit', 'invalid'],
|
||||
subcommands: ['invalid'],
|
||||
handlers: {
|
||||
invalid: () => makeInvalidArgs('--phase', '--phase must be an integer'),
|
||||
},
|
||||
unknownMessage: () => 'should not be used',
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(errorMessage, '--phase must be an integer');
|
||||
});
|
||||
|
||||
test('projects thrown handler exceptions as HandlerFailure message', () => {
|
||||
let errorMessage = null;
|
||||
|
||||
routeHubCommandFamily({
|
||||
family: 'unit',
|
||||
args: ['unit', 'boom'],
|
||||
subcommands: ['boom'],
|
||||
handlers: {
|
||||
boom: () => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
},
|
||||
unknownMessage: () => 'should not be used',
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(errorMessage, 'boom');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cjs-command-router-adapter routeCjsCommandFamily', () => {
|
||||
test('routes known subcommand handler via the legacy adapter', () => {
|
||||
let calls = 0;
|
||||
let errorMessage = null;
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args: ['unit', 'ok'],
|
||||
subcommands: ['ok'],
|
||||
handlers: {
|
||||
ok: () => {
|
||||
calls += 1;
|
||||
},
|
||||
},
|
||||
unknownMessage: (subcommand, available) => `Unknown ${subcommand}. Available: ${available.join(', ')}`,
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(errorMessage, null);
|
||||
});
|
||||
|
||||
test('honors defaultSubcommand when args[1] is absent', () => {
|
||||
let calls = 0;
|
||||
let errorMessage = null;
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args: ['unit'],
|
||||
subcommands: ['load'],
|
||||
defaultSubcommand: 'load',
|
||||
handlers: {
|
||||
load: () => {
|
||||
calls += 1;
|
||||
},
|
||||
},
|
||||
unknownMessage: (subcommand, available) => `Unknown ${subcommand}. Available: ${available.join(', ')}`,
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(calls, 1);
|
||||
assert.equal(errorMessage, null);
|
||||
});
|
||||
|
||||
test('converts thrown handler exceptions into error callback messages', () => {
|
||||
let errorMessage = null;
|
||||
|
||||
routeCjsCommandFamily({
|
||||
args: ['unit', 'boom'],
|
||||
subcommands: ['boom'],
|
||||
handlers: {
|
||||
boom: () => {
|
||||
throw new Error('boom');
|
||||
},
|
||||
},
|
||||
unknownMessage: () => 'should not be used',
|
||||
error: (message) => {
|
||||
errorMessage = message;
|
||||
},
|
||||
cwd: '/tmp/proj',
|
||||
raw: false,
|
||||
});
|
||||
|
||||
assert.equal(errorMessage, 'boom');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user