all repos — cmd @ 46c9141b19d060ead1c0be82a1cc815aa92f2987

Unnamed repository; edit this file 'description' to name the repository.

fmt(*): format
vi did:web:vt3e.cat
Thu, 14 May 2026 00:55:41 +0100
commit

46c9141b19d060ead1c0be82a1cc815aa92f2987

parent

552d3085196e3e448ec335236abc1139cf4fa671

4 files changed, 433 insertions(+), 448 deletions(-)

jump to
M package.jsonpackage.json

@@ -1,28 +1,28 @@

{ - "name": "arg-parser", - "module": "src/index.ts", - "type": "module", - "packageManager": "bun@1.1.13^", - "devDependencies": { - "@types/bun": "latest", - "oxfmt": "^0.49.0", - "oxlint": "^1.64.0" - }, - "peerDependencies": { - "typescript": "^5" - }, - "files": [ - "dist" - ], - "exports": { - ".": "./dist/index.js" - }, - "scripts": { - "build:types": "tsc --emitDeclarationOnly", - "build:js": "bun build ./src/index.ts --outdir ./dist --minify --target bun", - "build": "bun run build:types && bun run build:js", - "prepublishOnly": "bun run build", - "fmt": "bunx oxfmt fmt src/", - "lint": "bunx oxlint lint src/" - } + "name": "arg-parser", + "files": [ + "dist" + ], + "type": "module", + "module": "src/index.ts", + "exports": { + ".": "./dist/index.js" + }, + "scripts": { + "build:types": "tsc --emitDeclarationOnly", + "build:js": "bun build ./src/index.ts --outdir ./dist --minify --target bun", + "build": "bun run build:types && bun run build:js", + "prepublishOnly": "bun run build", + "fmt": "bunx oxfmt fmt src tests", + "lint": "bunx oxlint lint src tests" + }, + "devDependencies": { + "@types/bun": "latest", + "oxfmt": "^0.49.0", + "oxlint": "^1.64.0" + }, + "peerDependencies": { + "typescript": "^5" + }, + "packageManager": "bun@1.1.13^" }
M src/index.tssrc/index.ts

@@ -1,324 +1,313 @@

import type { ArgumentOptions, FlagOptions, Prettify } from "./types"; export class Command<TContext extends Record<string, unknown> = {}> { - public name?: string; - public description?: string; - private flags: Record<string, FlagOptions> = {}; - private args: Record<string, ArgumentOptions<unknown, unknown>> = {}; - private positionals: Array<{ - name: string; - options: { description?: string; required?: boolean } | undefined; - }> = []; - private subcommands: Record<string, Command<Record<string, unknown>>> = {}; - private actionFn?: (ctx: TContext) => void | Promise<void>; + public name?: string; + public description?: string; + private flags: Record<string, FlagOptions> = {}; + private args: Record<string, ArgumentOptions<unknown, unknown>> = {}; + private positionals: Array<{ + name: string; + options: { description?: string; required?: boolean } | undefined; + }> = []; + private subcommands: Record<string, Command<Record<string, unknown>>> = {}; + private actionFn?: (ctx: TContext) => void | Promise<void>; - private shortToFlag: Record<string, string> = {}; - private shortToArg: Record<string, string> = {}; + private shortToFlag: Record<string, string> = {}; + private shortToArg: Record<string, string> = {}; - constructor(name?: string) { - this.name = name; - } + constructor(name?: string) { + this.name = name; + } - setName(name: string): this { - this.name = name; - return this; - } + setName(name: string): this { + this.name = name; + return this; + } - setDescription(desc: string): this { - this.description = desc; - return this; - } + setDescription(desc: string): this { + this.description = desc; + return this; + } - addFlag<TName extends string>( - name: TName, - options?: FlagOptions, - ): Command<Prettify<TContext & { [K in TName]: boolean }>> { - this.flags[name] = options || {}; - if (options?.short) this.shortToFlag[options.short] = name; - return this as unknown as Command< - Prettify<TContext & { [K in TName]: boolean }> - >; - } + addFlag<TName extends string>( + name: TName, + options?: FlagOptions, + ): Command<Prettify<TContext & { [K in TName]: boolean }>> { + this.flags[name] = options || {}; + if (options?.short) this.shortToFlag[options.short] = name; + return this as unknown as Command<Prettify<TContext & { [K in TName]: boolean }>>; + } - addStringArgument< - TName extends string, - TReq extends boolean = false, - TDef extends string | undefined = undefined, - >( - name: TName, - options?: Omit<ArgumentOptions<"string", TDef>, "type"> & { - required?: TReq; - default?: TDef; - }, - ): Command< - Prettify< - TContext & { - [K in TName]: TDef extends string - ? string - : TReq extends true - ? string - : string | undefined; - } - > - > { - this.args[name] = { ...options, type: "string" }; - if (options?.short) this.shortToArg[options.short] = name; - return this as unknown as Command< - Prettify< - TContext & { - [K in TName]: TDef extends string - ? string - : TReq extends true - ? string - : string | undefined; - } - > - >; - } + addStringArgument< + TName extends string, + TReq extends boolean = false, + TDef extends string | undefined = undefined, + >( + name: TName, + options?: Omit<ArgumentOptions<"string", TDef>, "type"> & { + required?: TReq; + default?: TDef; + }, + ): Command< + Prettify< + TContext & { + [K in TName]: TDef extends string + ? string + : TReq extends true + ? string + : string | undefined; + } + > + > { + this.args[name] = { ...options, type: "string" }; + if (options?.short) this.shortToArg[options.short] = name; + return this as unknown as Command< + Prettify< + TContext & { + [K in TName]: TDef extends string + ? string + : TReq extends true + ? string + : string | undefined; + } + > + >; + } - addNumberArgument< - TName extends string, - TReq extends boolean = false, - TDef extends number | undefined = undefined, - >( - name: TName, - options?: Omit<ArgumentOptions<"number", TDef>, "type"> & { - required?: TReq; - default?: TDef; - }, - ): Command< - Prettify< - TContext & { - [K in TName]: TDef extends number - ? number - : TReq extends true - ? number - : number | undefined; - } - > - > { - this.args[name] = { ...options, type: "number" }; - if (options?.short) this.shortToArg[options.short] = name; - return this as unknown as Command< - Prettify< - TContext & { - [K in TName]: TDef extends number - ? number - : TReq extends true - ? number - : number | undefined; - } - > - >; - } + addNumberArgument< + TName extends string, + TReq extends boolean = false, + TDef extends number | undefined = undefined, + >( + name: TName, + options?: Omit<ArgumentOptions<"number", TDef>, "type"> & { + required?: TReq; + default?: TDef; + }, + ): Command< + Prettify< + TContext & { + [K in TName]: TDef extends number + ? number + : TReq extends true + ? number + : number | undefined; + } + > + > { + this.args[name] = { ...options, type: "number" }; + if (options?.short) this.shortToArg[options.short] = name; + return this as unknown as Command< + Prettify< + TContext & { + [K in TName]: TDef extends number + ? number + : TReq extends true + ? number + : number | undefined; + } + > + >; + } - addChoiceArgument< - TName extends string, - const TChoices extends readonly string[], - TReq extends boolean = false, - TDef extends TChoices[number] | undefined = undefined, - >( - name: TName, - options: { - choices: TChoices; - required?: TReq; - description?: string; - short?: string; - default?: TDef; - }, - ): Command< - Prettify< - TContext & { - [K in TName]: TDef extends TChoices[number] - ? TChoices[number] - : TReq extends true - ? TChoices[number] - : TChoices[number] | undefined; - } - > - > { - this.args[name] = { ...options, type: "choice" }; - if (options?.short) this.shortToArg[options.short] = name; - return this as unknown as Command< - Prettify< - TContext & { - [K in TName]: TDef extends TChoices[number] - ? TChoices[number] - : TReq extends true - ? TChoices[number] - : TChoices[number] | undefined; - } - > - >; - } + addChoiceArgument< + TName extends string, + const TChoices extends readonly string[], + TReq extends boolean = false, + TDef extends TChoices[number] | undefined = undefined, + >( + name: TName, + options: { + choices: TChoices; + required?: TReq; + description?: string; + short?: string; + default?: TDef; + }, + ): Command< + Prettify< + TContext & { + [K in TName]: TDef extends TChoices[number] + ? TChoices[number] + : TReq extends true + ? TChoices[number] + : TChoices[number] | undefined; + } + > + > { + this.args[name] = { ...options, type: "choice" }; + if (options?.short) this.shortToArg[options.short] = name; + return this as unknown as Command< + Prettify< + TContext & { + [K in TName]: TDef extends TChoices[number] + ? TChoices[number] + : TReq extends true + ? TChoices[number] + : TChoices[number] | undefined; + } + > + >; + } - addPositional<TName extends string, TReq extends boolean = false>( - name: TName, - options?: { description?: string; required?: TReq }, - ): Command< - Prettify< - TContext & { - [K in TName]: TReq extends true ? string : string | undefined; - } - > - > { - this.positionals = this.positionals || []; - this.positionals.push({ name, options }); - return this as unknown as Command< - Prettify< - TContext & { - [K in TName]: TReq extends true ? string : string | undefined; - } - > - >; - } + addPositional<TName extends string, TReq extends boolean = false>( + name: TName, + options?: { description?: string; required?: TReq }, + ): Command< + Prettify< + TContext & { + [K in TName]: TReq extends true ? string : string | undefined; + } + > + > { + this.positionals = this.positionals || []; + this.positionals.push({ name, options }); + return this as unknown as Command< + Prettify< + TContext & { + [K in TName]: TReq extends true ? string : string | undefined; + } + > + >; + } - addSubcommand<T extends Record<string, unknown>>(command: Command<T>): this { - if (!command.name) throw new Error("Subcommands must have a name"); + addSubcommand<T extends Record<string, unknown>>(command: Command<T>): this { + if (!command.name) throw new Error("Subcommands must have a name"); - this.subcommands[command.name] = command as unknown as Command< - Record<string, unknown> - >; - return this; - } + this.subcommands[command.name] = command as unknown as Command<Record<string, unknown>>; + return this; + } - setAction(fn: (ctx: TContext) => void | Promise<void>): this { - this.actionFn = fn; - return this; - } + setAction(fn: (ctx: TContext) => void | Promise<void>): this { + this.actionFn = fn; + return this; + } - async parse(argv: string[]): Promise<void> { - const ctx: Record<string, unknown> = {}; - const positionalsProvided: string[] = []; + async parse(argv: string[]): Promise<void> { + const ctx: Record<string, unknown> = {}; + const positionalsProvided: string[] = []; - // init boolean flags to their defaults, or false. - for (const [key, opts] of Object.entries(this.flags)) { - ctx[key] = opts.default ?? false; - } + // init boolean flags to their defaults, or false. + for (const [key, opts] of Object.entries(this.flags)) { + ctx[key] = opts.default ?? false; + } - // init args to their defaults - for (const [key, opts] of Object.entries(this.args)) { - if (opts.default !== undefined) ctx[key] = opts.default; - } + // init args to their defaults + for (const [key, opts] of Object.entries(this.args)) { + if (opts.default !== undefined) ctx[key] = opts.default; + } - let i = 0; - while (i < argv.length) { - const arg = argv[i] as string; + let i = 0; + while (i < argv.length) { + const arg = argv[i] as string; - // check for subcommands before evaluating positionals/flags - if (!arg.startsWith("-") && this.subcommands[arg]) { - return this.subcommands[arg]?.parse(argv.slice(i + 1)); - } + // check for subcommands before evaluating positionals/flags + if (!arg.startsWith("-") && this.subcommands[arg]) { + return this.subcommands[arg]?.parse(argv.slice(i + 1)); + } - const isLong = arg.startsWith("--"); - const isShort = arg.startsWith("-") && !isLong; + const isLong = arg.startsWith("--"); + const isShort = arg.startsWith("-") && !isLong; - if (isLong) { - let name = arg.slice(2); - let value: string | undefined; + if (isLong) { + let name = arg.slice(2); + let value: string | undefined; - if (name.includes("=")) { - const splitIdx = name.indexOf("="); - value = name.slice(splitIdx + 1); - name = name.slice(0, splitIdx); - } + if (name.includes("=")) { + const splitIdx = name.indexOf("="); + value = name.slice(splitIdx + 1); + name = name.slice(0, splitIdx); + } - if (this.flags[name]) { - ctx[name] = true; - } else { - const argOpts = this.args[name]; - if (argOpts) { - if ( - value === undefined && - i + 1 < argv.length && - !(argv[i + 1] as string).startsWith("-") - ) { - value = argv[i + 1]; - i++; - } - ctx[name] = this.parseValue(name, value, argOpts); - } else { - throw new Error(`Unknown option: --${name}`); - } - } - } else if (isShort) { - const shorts = arg.slice(1).split(""); - for (let j = 0; j < shorts.length; j++) { - const short = shorts[j] as string; - const flagName = this.shortToFlag[short]; - const argName = this.shortToArg[short]; + if (this.flags[name]) { + ctx[name] = true; + } else { + const argOpts = this.args[name]; + if (argOpts) { + if ( + value === undefined && + i + 1 < argv.length && + !(argv[i + 1] as string).startsWith("-") + ) { + value = argv[i + 1]; + i++; + } + ctx[name] = this.parseValue(name, value, argOpts); + } else { + throw new Error(`Unknown option: --${name}`); + } + } + } else if (isShort) { + const shorts = arg.slice(1).split(""); + for (let j = 0; j < shorts.length; j++) { + const short = shorts[j] as string; + const flagName = this.shortToFlag[short]; + const argName = this.shortToArg[short]; - if (flagName) { - ctx[flagName] = true; - } else if (argName) { - const opts = this.args[argName]; - let value: string | undefined; - if (j === shorts.length - 1) { - if ( - i + 1 < argv.length && - !(argv[i + 1] as string).startsWith("-") - ) { - value = argv[i + 1]; - i++; - } - } - ctx[argName] = this.parseValue(argName, value, opts!); - } else { - throw new Error(`Unknown short option: -${short}`); - } - } - } else { - positionalsProvided.push(arg); - } + if (flagName) { + ctx[flagName] = true; + } else if (argName) { + const opts = this.args[argName]; + let value: string | undefined; + if (j === shorts.length - 1) { + if (i + 1 < argv.length && !(argv[i + 1] as string).startsWith("-")) { + value = argv[i + 1]; + i++; + } + } + ctx[argName] = this.parseValue(argName, value, opts!); + } else { + throw new Error(`Unknown short option: -${short}`); + } + } + } else { + positionalsProvided.push(arg); + } - i++; - } + i++; + } - if (positionalsProvided.length > this.positionals.length) - throw new Error( - `Unknown positional argument: ${positionalsProvided[this.positionals.length]}`, - ); + if (positionalsProvided.length > this.positionals.length) + throw new Error( + `Unknown positional argument: ${positionalsProvided[this.positionals.length]}`, + ); - for (let p = 0; p < this.positionals.length; p++) { - const pos = this.positionals[p]!; - const val = positionalsProvided[p]; - if (val !== undefined) ctx[pos.name] = val; - else if (pos.options?.required) - throw new Error(`Missing required positional argument: <${pos.name}>`); - } + for (let p = 0; p < this.positionals.length; p++) { + const pos = this.positionals[p]!; + const val = positionalsProvided[p]; + if (val !== undefined) ctx[pos.name] = val; + else if (pos.options?.required) + throw new Error(`Missing required positional argument: <${pos.name}>`); + } - for (const [name, opts] of Object.entries(this.args)) { - if (opts.required && ctx[name] === undefined) - throw new Error(`Missing required argument: --${name}`); - } + for (const [name, opts] of Object.entries(this.args)) { + if (opts.required && ctx[name] === undefined) + throw new Error(`Missing required argument: --${name}`); + } - if (this.actionFn) await this.actionFn(ctx as unknown as TContext); - } + if (this.actionFn) await this.actionFn(ctx as unknown as TContext); + } - private parseValue( - name: string, - value: string | undefined, - opts: ArgumentOptions<unknown, unknown>, - ) { - if (value === undefined) - throw new Error(`Option --${name} requires a value`); + private parseValue( + name: string, + value: string | undefined, + opts: ArgumentOptions<unknown, unknown>, + ) { + if (value === undefined) throw new Error(`Option --${name} requires a value`); - if (opts.type === "number") { - const num = Number(value); - if (isNaN(num)) - throw new Error(`Option --${name} must be a valid number`); + if (opts.type === "number") { + const num = Number(value); + if (isNaN(num)) throw new Error(`Option --${name} must be a valid number`); - return num; - } - if (opts.type === "choice") { - const choices = (opts as unknown as { choices: string[] }).choices; - if (!choices.includes(value)) - throw new Error( - `Option --${name} must be one of: ${choices.join(", ")}`, - ); - } - return value; - } + return num; + } + if (opts.type === "choice") { + const choices = (opts as unknown as { choices: string[] }).choices; + if (!choices.includes(value)) + throw new Error(`Option --${name} must be one of: ${choices.join(", ")}`); + } + return value; + } } export * from "./types";
M src/types.tssrc/types.ts

@@ -1,17 +1,17 @@

export type Prettify<T> = { - [K in keyof T]: T[K]; + [K in keyof T]: T[K]; } & {}; export type FlagOptions = { - description?: string; - short?: string; - default?: boolean; + description?: string; + short?: string; + default?: boolean; }; export type ArgumentOptions<TType, TDef> = { - description?: string; - type: TType; - required?: boolean; - short?: string; - default?: TDef; + description?: string; + type: TType; + required?: boolean; + short?: string; + default?: TDef; };
M tests/index.test.tstests/index.test.ts

@@ -2,160 +2,156 @@ import { expect, test, describe } from "bun:test";

import { Command } from "../src/index"; describe("command parser", () => { - test("sets name and description", () => { - const cmd = new Command("test") - .setName("test") - .setDescription("Test command"); + test("sets name and description", () => { + const cmd = new Command("test").setName("test").setDescription("Test command"); - expect(cmd.name).toBe("test"); - expect(cmd.description).toBe("Test command"); - }); + expect(cmd.name).toBe("test"); + expect(cmd.description).toBe("Test command"); + }); - test("parses boolean flags", async () => { - let context: any; - const cmd = new Command("test") - .addFlag("verbose", { short: "v", default: false }) - .addFlag("force", { short: "f", default: true }) - .setAction((ctx) => { - context = ctx; - }); + test("parses boolean flags", async () => { + let context: any; + const cmd = new Command("test") + .addFlag("verbose", { short: "v", default: false }) + .addFlag("force", { short: "f", default: true }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse([]); - expect(context.verbose).toBe(false); - expect(context.force).toBe(true); + await cmd.parse([]); + expect(context.verbose).toBe(false); + expect(context.force).toBe(true); - await cmd.parse(["--verbose", "-f"]); - expect(context.verbose).toBe(true); - expect(context.force).toBe(true); - }); + await cmd.parse(["--verbose", "-f"]); + expect(context.verbose).toBe(true); + expect(context.force).toBe(true); + }); - test("groups short flags", async () => { - let context: any; - const cmd = new Command("test") - .addFlag("verbose", { short: "v" }) - .addFlag("force", { short: "f" }) - .addStringArgument("config", { short: "c" }) - .setAction((ctx) => { - context = ctx; - }); + test("groups short flags", async () => { + let context: any; + const cmd = new Command("test") + .addFlag("verbose", { short: "v" }) + .addFlag("force", { short: "f" }) + .addStringArgument("config", { short: "c" }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse(["-vfc", "config.json"]); - expect(context.verbose).toBe(true); - expect(context.force).toBe(true); - expect(context.config).toBe("config.json"); - }); + await cmd.parse(["-vfc", "config.json"]); + expect(context.verbose).toBe(true); + expect(context.force).toBe(true); + expect(context.config).toBe("config.json"); + }); - test("parses string arguments and defaults", async () => { - let context: any; - const cmd = new Command("test") - .addStringArgument("config", { short: "c", default: "default.json" }) - .setAction((ctx) => { - context = ctx; - }); + test("parses string arguments and defaults", async () => { + let context: any; + const cmd = new Command("test") + .addStringArgument("config", { short: "c", default: "default.json" }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse([]); - expect(context.config).toBe("default.json"); + await cmd.parse([]); + expect(context.config).toBe("default.json"); - await cmd.parse(["--config", "config.json"]); - expect(context.config).toBe("config.json"); + await cmd.parse(["--config", "config.json"]); + expect(context.config).toBe("config.json"); - await cmd.parse(["-c", "other.json"]); - expect(context.config).toBe("other.json"); + await cmd.parse(["-c", "other.json"]); + expect(context.config).toBe("other.json"); - await cmd.parse(["--config=inline.json"]); - expect(context.config).toBe("inline.json"); - }); + await cmd.parse(["--config=inline.json"]); + expect(context.config).toBe("inline.json"); + }); - test("throws on missing required arguments", async () => { - const cmd = new Command("test").addStringArgument("env", { - required: true, - }); + test("throws on missing required arguments", async () => { + const cmd = new Command("test").addStringArgument("env", { + required: true, + }); - expect(cmd.parse([])).rejects.toThrow("Missing required argument: --env"); - }); + expect(cmd.parse([])).rejects.toThrow("Missing required argument: --env"); + }); - test("parses number arguments and defaults", async () => { - let context: any; - const cmd = new Command("test") - .addNumberArgument("retries", { default: 5 }) - .setAction((ctx) => { - context = ctx; - }); + test("parses number arguments and defaults", async () => { + let context: any; + const cmd = new Command("test") + .addNumberArgument("retries", { default: 5 }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse([]); - expect(context.retries).toBe(5); + await cmd.parse([]); + expect(context.retries).toBe(5); - await cmd.parse(["--retries", "3"]); - expect(context.retries).toBe(3); + await cmd.parse(["--retries", "3"]); + expect(context.retries).toBe(3); - expect(cmd.parse(["--retries", "abc"])).rejects.toThrow( - "Option --retries must be a valid number", - ); - }); + expect(cmd.parse(["--retries", "abc"])).rejects.toThrow( + "Option --retries must be a valid number", + ); + }); - test("parses choice arguments and defaults", async () => { - let context: any; - const cmd = new Command("test") - .addChoiceArgument("format", { choices: ["json", "xml"], default: "xml" }) - .setAction((ctx) => { - context = ctx; - }); + test("parses choice arguments and defaults", async () => { + let context: any; + const cmd = new Command("test") + .addChoiceArgument("format", { choices: ["json", "xml"], default: "xml" }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse([]); - expect(context.format).toBe("xml"); + await cmd.parse([]); + expect(context.format).toBe("xml"); - await cmd.parse(["--format", "json"]); - expect(context.format).toBe("json"); + await cmd.parse(["--format", "json"]); + expect(context.format).toBe("json"); - expect(cmd.parse(["--format", "csv"])).rejects.toThrow( - "Option --format must be one of: json, xml", - ); - }); + expect(cmd.parse(["--format", "csv"])).rejects.toThrow( + "Option --format must be one of: json, xml", + ); + }); - test("parses positionals", async () => { - let context: any; - const cmd = new Command("test") - .addPositional("source", { required: true }) - .addPositional("dest", { required: false }) - .setAction((ctx) => { - context = ctx; - }); + test("parses positionals", async () => { + let context: any; + const cmd = new Command("test") + .addPositional("source", { required: true }) + .addPositional("dest", { required: false }) + .setAction((ctx) => { + context = ctx; + }); - await cmd.parse(["src-dir"]); - expect(context.source).toBe("src-dir"); - expect(context.dest).toBeUndefined(); + await cmd.parse(["src-dir"]); + expect(context.source).toBe("src-dir"); + expect(context.dest).toBeUndefined(); - await cmd.parse(["src-dir", "dest-dir"]); - expect(context.source).toBe("src-dir"); - expect(context.dest).toBe("dest-dir"); + await cmd.parse(["src-dir", "dest-dir"]); + expect(context.source).toBe("src-dir"); + expect(context.dest).toBe("dest-dir"); - expect(cmd.parse([])).rejects.toThrow( - "Missing required positional argument: <source>", - ); + expect(cmd.parse([])).rejects.toThrow("Missing required positional argument: <source>"); - expect(cmd.parse(["src-dir", "dest-dir", "extra-dir"])).rejects.toThrow( - "Unknown positional argument: extra-dir", - ); - }); + expect(cmd.parse(["src-dir", "dest-dir", "extra-dir"])).rejects.toThrow( + "Unknown positional argument: extra-dir", + ); + }); - test("handles subcommands", async () => { - let rootCalled = false; - let subCalled = false; - let subContext: any; + test("handles subcommands", async () => { + let rootCalled = false; + let subCalled = false; + let subContext: any; - const sub = new Command("push").addFlag("force").setAction((ctx) => { - subCalled = true; - subContext = ctx; - }); + const sub = new Command("push").addFlag("force").setAction((ctx) => { + subCalled = true; + subContext = ctx; + }); - const root = new Command("git").addSubcommand(sub).setAction(() => { - rootCalled = true; - }); + const root = new Command("git").addSubcommand(sub).setAction(() => { + rootCalled = true; + }); - await root.parse(["push", "--force"]); + await root.parse(["push", "--force"]); - expect(rootCalled).toBe(false); - expect(subCalled).toBe(true); - expect(subContext.force).toBe(true); - }); + expect(rootCalled).toBe(false); + expect(subCalled).toBe(true); + expect(subContext.force).toBe(true); + }); });