115 lines
3.5 KiB
TypeScript
115 lines
3.5 KiB
TypeScript
import type { QueryRegistry } from './registry.js';
|
|
import type { QueryHandler } from './utils.js';
|
|
import {
|
|
resolveQueryCommand,
|
|
explainQueryCommandNoMatch,
|
|
type QueryCommandRegistryLike,
|
|
} from './query-command-resolution-strategy.js';
|
|
import { supportsMutationCommand, supportsRawOutputCommand } from './query-policy-capability.js';
|
|
import { UNKNOWN_COMMAND_HINTS } from './query-unknown-command-hints.js';
|
|
import { describeFallbackDisabledPolicy } from './query-fallback-policy.js';
|
|
|
|
export type CommandTopologyOutputMode = 'json' | 'text' | 'raw';
|
|
|
|
export interface CommandTopologyMatch {
|
|
kind: 'match';
|
|
canonical: string;
|
|
args: string[];
|
|
output_mode: CommandTopologyOutputMode;
|
|
mutation: boolean;
|
|
adapter: QueryHandler;
|
|
}
|
|
|
|
export interface CommandTopologyNoMatch {
|
|
kind: 'no_match';
|
|
attempted: string[];
|
|
normalized?: string;
|
|
hints: string[];
|
|
message: string;
|
|
}
|
|
|
|
export type CommandTopologyResult = CommandTopologyMatch | CommandTopologyNoMatch;
|
|
|
|
export interface CommandTopology {
|
|
resolve(tokens: string[], fallbackRestricted?: boolean): CommandTopologyResult;
|
|
}
|
|
|
|
export interface UnknownCommandDiagnosis {
|
|
normalized: string;
|
|
attempted: string[];
|
|
hints: string[];
|
|
message: string;
|
|
}
|
|
|
|
export function diagnoseUnknownCommand(
|
|
command: string,
|
|
args: string[],
|
|
registry: QueryCommandRegistryLike,
|
|
fallbackRestricted: boolean,
|
|
): UnknownCommandDiagnosis {
|
|
const noMatch = explainQueryCommandNoMatch(command, args, registry);
|
|
const normalized = [noMatch.normalized.command, ...noMatch.normalized.args].join(' ');
|
|
const attempted = noMatch.attempted.dotted.slice(0, 2);
|
|
const hints = [...UNKNOWN_COMMAND_HINTS];
|
|
const attemptedSuffix = attempted.length > 0 ? ` Attempted dotted: ${attempted.join(' | ')}.` : '';
|
|
const fallbackClause = fallbackRestricted ? `${describeFallbackDisabledPolicy()} ` : '';
|
|
const message = `Error: Unknown command: "${normalized}". ${hints[0]} ${hints[1]} ${fallbackClause}${hints[2]}${attemptedSuffix}`;
|
|
|
|
return {
|
|
normalized,
|
|
attempted,
|
|
hints,
|
|
message,
|
|
};
|
|
}
|
|
|
|
export function createCommandTopology(registry: QueryRegistry): CommandTopology {
|
|
return {
|
|
resolve(tokens: string[], fallbackRestricted = false): CommandTopologyResult {
|
|
const command = tokens[0];
|
|
const args = tokens.slice(1);
|
|
if (!command) {
|
|
return {
|
|
kind: 'no_match',
|
|
attempted: [],
|
|
hints: [],
|
|
message: 'Error: "gsd-sdk query" requires a command',
|
|
};
|
|
}
|
|
|
|
const matched = resolveQueryCommand(command, args, registry);
|
|
if (!matched) {
|
|
const diagnosis = diagnoseUnknownCommand(command, args, registry, fallbackRestricted);
|
|
return {
|
|
kind: 'no_match',
|
|
normalized: diagnosis.normalized,
|
|
attempted: diagnosis.attempted,
|
|
hints: diagnosis.hints,
|
|
message: diagnosis.message,
|
|
};
|
|
}
|
|
|
|
const adapter = registry.getHandler(matched.cmd);
|
|
if (!adapter) {
|
|
const diagnosis = diagnoseUnknownCommand(command, args, registry, fallbackRestricted);
|
|
return {
|
|
kind: 'no_match',
|
|
normalized: diagnosis.normalized,
|
|
attempted: diagnosis.attempted,
|
|
hints: diagnosis.hints,
|
|
message: diagnosis.message,
|
|
};
|
|
}
|
|
|
|
return {
|
|
kind: 'match',
|
|
canonical: matched.cmd,
|
|
args: matched.args,
|
|
output_mode: supportsRawOutputCommand(matched.cmd) ? 'raw' : 'json',
|
|
mutation: supportsMutationCommand(matched.cmd),
|
|
adapter,
|
|
};
|
|
},
|
|
};
|
|
}
|