import { type Command, type CommandContext, define...

Creato il: 24 maggio 2026

Risposto usando GPT-5.5 Thinking da Chat01

Domanda

import { type Command, type CommandContext, defineCommand, type ExecResult } from "just-bash";

export function commandError(stderr: string, exitCode: number): ExecResult {
return { stdout: "", stderr, exitCode };
}

export interface HelpInfo {
name: string;
trusted?: boolean;
summary?: string;
usage?: string;
description?: string | string[];
options?: string[];
examples?: string[];
notes?: string[];
}

export interface CommandExample {
command: string;
description?: string;
}

export interface CommandArgDefinition {
default?: string | string[];
defaultHelp?: string;
description?: string;
hidden?: boolean;
multiple?: boolean;
name: string;
options?: string[];
required?: boolean;
summary?: string;
}

interface BaseFlagDefinition {
aliases?: string[];
char?: string;
default?: boolean | string | string[];
defaultHelp?: string;
description?: string;
helpLabel?: string;
hidden?: boolean;
required?: boolean;
summary?: string;
}

export interface BooleanFlagDefinition extends BaseFlagDefinition {
allowNo?: boolean;
type: "boolean";
}

export interface StringFlagDefinition extends BaseFlagDefinition {
helpValue?: string;
multiple?: boolean;
options?: string[];
type: "string";
}

export type CommandFlagDefinition = BooleanFlagDefinition | StringFlagDefinition;

interface BaseCommandDefinition {
aliases?: string[];
description?: string | string[];
examples?: Array<CommandExample | string>;
hidden?: boolean;
id: string;
strict?: boolean;
summary?: string;
trusted?: boolean;
usage?: string;
}

export interface ParsedCommandInput {
args: Record<string, string | string[] | undefined>;
argv: string[];
flags: Record<string, boolean | string | string[] | undefined>;
}

export interface OclifCommandDefinition extends BaseCommandDefinition {
args?: CommandArgDefinition[];
flags?: Record<string, CommandFlagDefinition>;
run: (input: ParsedCommandInput, ctx: CommandContext) => ExecResult | Promise<ExecResult>;
type: "command";
}

export interface OclifTopicDefinition extends BaseCommandDefinition {
subcommands: OclifDefinition[];
type: "topic";
}

export type OclifDefinition = OclifCommandDefinition | OclifTopicDefinition;

export function hasHelpFlag(args: string[]): boolean {
for (const arg of args) {
if (arg === "--") {
return false;
}

text
if (arg === "--help" || arg === "-h") { return true; } } return false;

}

export function showHelp(info: HelpInfo): ExecResult {
let output = info.summary ? ${info.name} - ${info.summary}\n : ${info.name}\n;
if (info.usage) {
output += \nUsage: ${info.usage}\n;
}
if (info.description) {
output += "\nDescription:\n";
if (typeof info.description === "string") {
for (const line of info.description.split("\n")) {
output += line ? ${line}\n : "\n";
}
} else if (info.description.length > 0) {
for (const line of info.description) {
output += line ? ${line}\n : "\n";
}
}
}
if (info.options && info.options.length > 0) {
output += "\nOptions:\n";
for (const opt of info.options) {
output += ${opt}\n;
}
}
if (info.examples && info.examples.length > 0) {
output += "\nExamples:\n";
for (const example of info.examples) {
output += ${example}\n;
}
}
if (info.notes && info.notes.length > 0) {
output += "\nNotes:\n";
for (const note of info.notes) {
output += ${note}\n;
}
}
return { stdout: output, stderr: "", exitCode: 0 };
}

export function createCommand(definition: OclifDefinition): Command {
const command = defineCommand(definition.id, async (argv, ctx) => {
return runDefinition(definition, argv, ctx, [definition.id]);
});

text
if (definition.trusted !== undefined) { return { ...command, trusted: definition.trusted }; } return command;

}

function findSubcommand(
definition: OclifTopicDefinition,
name: string,
): OclifDefinition | undefined {
for (const subcommand of definition.subcommands) {
if (subcommand.id === name || subcommand.aliases?.includes(name)) {
return subcommand;
}
}

return undefined;

}

async function runDefinition(
definition: OclifDefinition,
argv: string[],
ctx: CommandContext,
path: string[],
): Promise<ExecResult> {
if (definition.type === "topic") {
const [subcommandName, ...rest] = argv;
if (!subcommandName) {
return topicHelp(definition, path);
}

text
const subcommand = findSubcommand(definition, subcommandName); if (subcommand) { return runDefinition(subcommand, rest, ctx, [...path, subcommand.id]); } if (hasHelpFlag(argv)) { return topicHelp(definition, path); } return topicUsageError( definition, path, `${path.join(" ")}: unknown command: ${subcommandName}\n`, ); } if (hasHelpFlag(argv)) { return commandHelp(definition, path); } const parsed = parseCommandInput(definition, argv); if ("error" in parsed) { return commandUsageError(definition, path, parsed.error); } return definition.run(parsed, ctx);

}

function parseCommandInput(
definition: OclifCommandDefinition,
argv: string[],
): ParsedCommandInput | { error: string } {
const flagDefinitions = definition.flags ?? {};
const aliasToName = new Map<string, string>();
for (const [name, flag] of Object.entries(flagDefinitions)) {
if (flag.char) {
aliasToName.set(flag.char, name);
}
for (const alias of flag.aliases ?? []) {
aliasToName.set(alias, name);
}
}

text
const flags: Record<string, boolean | string | string[] | undefined> = {}; const positionals: string[] = []; for (let index = 0; index < argv.length; index++) { const token = argv[index]; if (token === "--") { positionals.push(...argv.slice(index + 1)); break; } if (token.startsWith("--no-")) { const name = token.slice(5); const flag = flagDefinitions[name]; if (!flag || flag.type !== "boolean" || !flag.allowNo) { return { error: `Nonexistent flag: --no-${name}\n` }; } flags[name] = false; continue; } if (token.startsWith("--")) { const body = token.slice(2); const equalsIndex = body.indexOf("="); const name = equalsIndex >= 0 ? body.slice(0, equalsIndex) : body; const flag = flagDefinitions[name]; if (!flag) { return { error: `Nonexistent flag: --${name}\n` }; } if (flag.type === "boolean") { if (equalsIndex >= 0) { return { error: `Unexpected value for boolean flag: --${name}\n` }; } flags[name] = true; continue; } let value = equalsIndex >= 0 ? body.slice(equalsIndex + 1) : undefined; if (value === undefined) { index += 1; value = argv[index]; } if (value === undefined) { return { error: `Missing value for flag: --${name}\n` }; } if (flag.options && !flag.options.includes(value)) { return { error: `Expected --${name} to be one of: ${flag.options.join(", ")}\n` }; } if (flag.multiple) { const current = flags[name]; flags[name] = Array.isArray(current) ? [...current, value] : [value]; } else { flags[name] = value; } continue; } if (token.startsWith("-") && token !== "-") { if (token.length !== 2) { return { error: `Unsupported flag syntax: ${token}\n` }; } const alias = token.slice(1); const name = aliasToName.get(alias); if (!name) { return { error: `Nonexistent flag: ${token}\n` }; } const flag = flagDefinitions[name]; if (!flag) { return { error: `Nonexistent flag: ${token}\n` }; } if (flag.type === "boolean") { flags[name] = true; continue; } index += 1; const value = argv[index]; if (value === undefined) { return { error: `Missing value for flag: ${token}\n` }; } if (flag.options && !flag.options.includes(value)) { return { error: `Expected ${token} to be one of: ${flag.options.join(", ")}\n` }; } if (flag.multiple) { const current = flags[name]; flags[name] = Array.isArray(current) ? [...current, value] : [value]; } else { flags[name] = value; } continue; } positionals.push(token); } for (const [name, flag] of Object.entries(flagDefinitions)) { if (flags[name] === undefined && flag.default !== undefined) { flags[name] = flag.default; } if (flag.required && flags[name] === undefined) { return { error: `Missing required flag: --${name}\n` }; } } const args: Record<string, string | string[] | undefined> = {}; const definitions = definition.args ?? []; let offset = 0; for (let index = 0; index < definitions.length; index++) { const arg = definitions[index]; if (arg.multiple) { const values = positionals.slice(offset); if (values.length === 0) { if (arg.default !== undefined) { args[arg.name] = arg.default; } else if (arg.required) { return { error: `Missing required arg: ${arg.name}\n` }; } } else { if (arg.options && values.some((value) => !arg.options?.includes(value))) { return { error: `Expected ${arg.name} to be one of: ${arg.options.join(", ")}\n` }; } args[arg.name] = values; } offset = positionals.length; continue; } const value = positionals[offset]; if (value === undefined) { if (arg.default !== undefined) { args[arg.name] = arg.default; } else if (arg.required) { return { error: `Missing required arg: ${arg.name}\n` }; } continue; } if (arg.options && !arg.options.includes(value)) { return { error: `Expected ${arg.name} to be one of: ${arg.options.join(", ")}\n` }; } args[arg.name] = value; offset += 1; } if ((definition.strict ?? true) && offset < positionals.length) { return { error: `Unexpected arg: ${positionals[offset]}\n` }; } return { args, argv, flags };

}

function commandHelp(definition: OclifCommandDefinition, path: string[]): ExecResult {
const lines: string[] = [];
if (definition.summary) {
lines.push(${path.join(" ")} - ${definition.summary});
lines.push("");
}

text
lines.push(`Usage: ${definition.usage ?? buildCommandUsage(definition, path)}`); if (definition.description) { lines.push(""); lines.push("Description:"); lines.push(...formatIndentedLines(definition.description)); } const args = (definition.args ?? []).filter((arg) => !arg.hidden); if (args.length > 0) { lines.push(""); lines.push("Arguments:"); for (const arg of args) { const required = arg.required ? " (required)" : ""; const multiple = arg.multiple ? "..." : ""; const summary = arg.summary ?? arg.description ?? ""; lines.push(` ${arg.name}${multiple}${required}${summary ? ` - ${summary}` : ""}`); } } const flags = Object.entries(definition.flags ?? {}).filter(([, flag]) => !flag.hidden); if (flags.length > 0) { lines.push(""); lines.push("Flags:"); for (const [name, flag] of flags) { lines.push(` ${formatFlagLabel(name, flag)}${formatFlagSummary(flag)}`); } } const examples = definition.examples ?? []; if (examples.length > 0) { lines.push(""); lines.push("Examples:"); for (const example of examples) { if (typeof example === "string") { lines.push(` ${example}`); continue; } if (example.description) { lines.push(` ${example.description}`); } lines.push(` ${example.command}`); } } return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 };

}

function topicHelp(definition: OclifTopicDefinition, path: string[]): ExecResult {
const lines: string[] = [];
if (definition.summary) {
lines.push(${path.join(" ")} - ${definition.summary});
lines.push("");
}

text
lines.push(`Usage: ${definition.usage ?? `${path.join(" ")} <command>`}`); if (definition.description) { lines.push(""); lines.push("Description:"); lines.push(...formatIndentedLines(definition.description)); } const subcommands = definition.subcommands.filter((subcommand) => !subcommand.hidden); if (subcommands.length > 0) { lines.push(""); lines.push("Commands:"); for (const subcommand of subcommands) { const description = subcommand.summary ?? firstLine(subcommand.description); lines.push(` ${subcommand.id}${description ? ` - ${description}` : ""}`); } } const examples = definition.examples ?? []; if (examples.length > 0) { lines.push(""); lines.push("Examples:"); for (const example of examples) { if (typeof example === "string") { lines.push(` ${example}`); continue; } if (example.description) { lines.push(` ${example.description}`); } lines.push(` ${example.command}`); } } return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 };

}

function topicUsageError(
definition: OclifTopicDefinition,
path: string[],
message: string,
): ExecResult {
const help = topicHelp(definition, path);
return {
stdout: help.stdout,
stderr: message,
exitCode: 1,
};
}

export function commandUsageError(
definition: OclifCommandDefinition,
path: string[],
message: string,
): ExecResult {
const help = commandHelp(definition, path);
return {
stdout: help.stdout,
stderr: message,
exitCode: 1,
};
}

function buildCommandUsage(definition: OclifCommandDefinition, path: string[]): string {
const parts = [path.join(" ")];
for (const arg of definition.args ?? []) {
const name = arg.multiple ? ${arg.name}... : arg.name;
parts.push(arg.required ? <${name}> : [${name}]);
}
const hasFlags = Object.keys(definition.flags ?? {}).length > 0;
if (hasFlags) {
parts.push("[flags]");
}
return parts.join(" ");
}

function formatFlagLabel(name: string, flag: CommandFlagDefinition): string {
if (flag.helpLabel) {
return flag.helpLabel;
}

text
const labels: string[] = []; if (flag.char) { labels.push(`-${flag.char}`); } labels.push(`--${name}`); if (flag.type === "boolean" && flag.allowNo) { labels.push(`--no-${name}`); } if (flag.type === "string") { labels[labels.length - 1] += ` <${flag.helpValue ?? name}>`; } return labels.join(", ");

}

function formatFlagSummary(flag: CommandFlagDefinition): string {
const summary = flag.summary ?? flag.description;
return summary ? - ${summary} : "";
}

function formatIndentedLines(value: string | string[]): string[] {
if (typeof value === "string") {
return value.split("\n").map((line) => (line ? ${line} : ""));
}

return value.map((line) => (line ? `  ${line}` : ""));

}

function firstLine(value: string | string[] | undefined): string | undefined {
if (typeof value === "string") {
return value.split("\n")[0];
}
return value?.[0];
}
帮我修改这个 createCommand函数

Pensiero

我会按你这段代码里已经定义但目前没被顶层使用的字段来改:让 createCommand 支持顶层 aliases,并保留 trusted 等元数据。

Thought for 15s

Risposta

可以。按你现有结构来看,BaseCommandDefinition 里有 aliases / hidden / summary / trusted,但现在 createCommand 只处理了 trusted。我建议把这些顶层元数据都透传到 Command 上,尤其是 aliases

把原来的 createCommand 改成这样:

ts
export function createCommand(definition: OclifDefinition): Command { const command = defineCommand(definition.id, async (argv, ctx) => { return runDefinition(definition, argv, ctx, [definition.id]); }); return { ...command, // 让顶层命令支持 aliases ...(definition.aliases && definition.aliases.length > 0 ? { aliases: definition.aliases } : {}), // 透传命令是否隐藏 ...(definition.hidden !== undefined ? { hidden: definition.hidden } : {}), // 透传 trusted ...(definition.trusted !== undefined ? { trusted: definition.trusted } : {}), // 可选:如果 just-bash 的 Command 支持这些字段,也可以透传 ...(definition.summary !== undefined ? { summary: definition.summary } : {}), }; }

如果你只想最小改动,重点是支持 aliasestrusted,可以用这个更保守的版本:

ts
export function createCommand(definition: OclifDefinition): Command { const command = defineCommand(definition.id, async (argv, ctx) => { return runDefinition(definition, argv, ctx, [definition.id]); }); return { ...command, ...(definition.aliases?.length ? { aliases: definition.aliases } : {}), ...(definition.trusted !== undefined ? { trusted: definition.trusted } : {}), }; }

这样例如:

ts
const gitCommand = createCommand({ id: "git", aliases: ["g"], type: "topic", summary: "Git commands", subcommands: [], });

生成出来的 command 对象就会带上:

ts
{ ..., aliases: ["g"] }

如果你的 just-bashCommand 类型不允许 aliases 字段,TypeScript 可能会报错。那可以改成:

ts
type ExtendedCommand = Command & { aliases?: string[]; hidden?: boolean; summary?: string; trusted?: boolean; }; export function createCommand(definition: OclifDefinition): ExtendedCommand { const command = defineCommand(definition.id, async (argv, ctx) => { return runDefinition(definition, argv, ctx, [definition.id]); }); return { ...command, ...(definition.aliases?.length ? { aliases: definition.aliases } : {}), ...(definition.hidden !== undefined ? { hidden: definition.hidden } : {}), ...(definition.trusted !== undefined ? { trusted: definition.trusted } : {}), ...(definition.summary !== undefined ? { summary: definition.summary } : {}), }; }

我更推荐最后这个 ExtendedCommand 版本,类型更明确,也方便后续继续扩展。

Condividi questa Q&A