@outfitter/cli 0.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,104 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Configuration for creating a CLI instance.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * const config: CLIConfig = {
8
+ * name: "waymark",
9
+ * version: "1.0.0",
10
+ * description: "A note management CLI",
11
+ * };
12
+ * ```
13
+ */
14
+ interface CLIConfig {
15
+ /** CLI name (used in help output and error messages) */
16
+ readonly name: string;
17
+ /** CLI version (displayed with --version) */
18
+ readonly version: string;
19
+ /** CLI description (displayed in help output) */
20
+ readonly description?: string;
21
+ /** Custom error handler */
22
+ readonly onError?: (error: Error) => void;
23
+ /** Custom exit handler (defaults to process.exit) */
24
+ readonly onExit?: (code: number) => never;
25
+ }
26
+ /**
27
+ * CLI instance returned by createCLI.
28
+ */
29
+ interface CLI {
30
+ /** Register a command with the CLI */
31
+ register(command: CommandBuilder | Command): this;
32
+ /** Parse arguments and execute the matched command */
33
+ parse(argv?: readonly string[]): Promise<void>;
34
+ /** Get the underlying Commander program */
35
+ readonly program: Command;
36
+ }
37
+ /**
38
+ * Action function executed when a command is invoked.
39
+ *
40
+ * @typeParam TFlags - Type of parsed command flags
41
+ */
42
+ type CommandAction<TFlags extends CommandFlags = CommandFlags> = (context: {
43
+ /** Parsed command-line arguments */
44
+ readonly args: readonly string[];
45
+ /** Parsed command flags */
46
+ readonly flags: TFlags;
47
+ /** Raw Commander command instance */
48
+ readonly command: Command;
49
+ }) => Promise<void> | void;
50
+ /**
51
+ * Base type for command flags.
52
+ * All flag types must extend this.
53
+ */
54
+ type CommandFlags = Record<string, unknown>;
55
+ /**
56
+ * Builder interface for constructing commands fluently.
57
+ */
58
+ interface CommandBuilder {
59
+ /** Set command description */
60
+ description(text: string): this;
61
+ /** Add a command option/flag */
62
+ option(flags: string, description: string, defaultValue?: unknown): this;
63
+ /** Add a required option */
64
+ requiredOption(flags: string, description: string, defaultValue?: unknown): this;
65
+ /** Add command aliases */
66
+ alias(alias: string): this;
67
+ /** Set the action handler */
68
+ action<TFlags extends CommandFlags = CommandFlags>(handler: CommandAction<TFlags>): this;
69
+ /** Build the underlying Commander command */
70
+ build(): Command;
71
+ }
72
+ /**
73
+ * Create a new CLI instance with the given configuration.
74
+ *
75
+ * The CLI wraps Commander.js with typed helpers, output contract enforcement,
76
+ * and pagination state management.
77
+ *
78
+ * @param config - CLI configuration options
79
+ * @returns A CLI instance ready for command registration
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * import { createCLI, command, output } from "@outfitter/cli";
84
+ *
85
+ * const cli = createCLI({
86
+ * name: "waymark",
87
+ * version: "1.0.0",
88
+ * description: "A note management CLI",
89
+ * });
90
+ *
91
+ * cli.register(
92
+ * command("list")
93
+ * .description("List all notes")
94
+ * .action(async () => {
95
+ * const notes = await getNotes();
96
+ * output(notes);
97
+ * })
98
+ * );
99
+ *
100
+ * await cli.parse();
101
+ * ```
102
+ */
103
+ declare function createCLI(config: CLIConfig): CLI;
104
+ export { createCLI };
package/dist/cli.js ADDED
@@ -0,0 +1,55 @@
1
+ // @bun
2
+ import"./shared/@outfitter/cli-4yy82cmp.js";
3
+
4
+ // packages/cli/src/cli.ts
5
+ import { Command } from "commander";
6
+ function isCommanderHelp(error) {
7
+ return error.code === "commander.helpDisplayed" || error.code === "commander.version" || error.code === "commander.help";
8
+ }
9
+ function createCLI(config) {
10
+ const program = new Command;
11
+ program.name(config.name).version(config.version);
12
+ if (config.description) {
13
+ program.description(config.description);
14
+ }
15
+ const exit = config.onExit ?? ((code) => process.exit(code));
16
+ program.exitOverride((error) => {
17
+ if (isCommanderHelp(error)) {
18
+ exit(0);
19
+ }
20
+ if (config.onError) {
21
+ config.onError(error);
22
+ }
23
+ const exitCode = typeof error.exitCode === "number" && Number.isFinite(error.exitCode) ? error.exitCode : 1;
24
+ exit(exitCode);
25
+ });
26
+ const parse = async (argv) => {
27
+ try {
28
+ await program.parseAsync(argv ?? process.argv);
29
+ } catch (error) {
30
+ const err = error instanceof Error ? error : new Error(String(error));
31
+ if (config.onError) {
32
+ config.onError(err);
33
+ }
34
+ const errorExitCode = error.exitCode;
35
+ const exitCode = typeof errorExitCode === "number" ? errorExitCode : 1;
36
+ exit(exitCode);
37
+ }
38
+ };
39
+ const cli = {
40
+ program,
41
+ register: (builderOrCommand) => {
42
+ if ("build" in builderOrCommand) {
43
+ program.addCommand(builderOrCommand.build());
44
+ } else {
45
+ program.addCommand(builderOrCommand);
46
+ }
47
+ return cli;
48
+ },
49
+ parse
50
+ };
51
+ return cli;
52
+ }
53
+ export {
54
+ createCLI
55
+ };
@@ -0,0 +1,74 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Action function executed when a command is invoked.
4
+ *
5
+ * @typeParam TFlags - Type of parsed command flags
6
+ */
7
+ type CommandAction<TFlags extends CommandFlags = CommandFlags> = (context: {
8
+ /** Parsed command-line arguments */
9
+ readonly args: readonly string[];
10
+ /** Parsed command flags */
11
+ readonly flags: TFlags;
12
+ /** Raw Commander command instance */
13
+ readonly command: Command;
14
+ }) => Promise<void> | void;
15
+ /**
16
+ * Base type for command flags.
17
+ * All flag types must extend this.
18
+ */
19
+ type CommandFlags = Record<string, unknown>;
20
+ /**
21
+ * Builder interface for constructing commands fluently.
22
+ */
23
+ interface CommandBuilder {
24
+ /** Set command description */
25
+ description(text: string): this;
26
+ /** Add a command option/flag */
27
+ option(flags: string, description: string, defaultValue?: unknown): this;
28
+ /** Add a required option */
29
+ requiredOption(flags: string, description: string, defaultValue?: unknown): this;
30
+ /** Add command aliases */
31
+ alias(alias: string): this;
32
+ /** Set the action handler */
33
+ action<TFlags extends CommandFlags = CommandFlags>(handler: CommandAction<TFlags>): this;
34
+ /** Build the underlying Commander command */
35
+ build(): Command;
36
+ }
37
+ /**
38
+ * Create a new command builder with the given name.
39
+ *
40
+ * The command builder provides a fluent API for defining CLI commands
41
+ * with typed flags, arguments, and actions.
42
+ *
43
+ * @param name - Command name and optional argument syntax (e.g., "list" or "get <id>")
44
+ * @returns A CommandBuilder instance for fluent configuration
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { command, output } from "@outfitter/cli";
49
+ *
50
+ * const list = command("list")
51
+ * .description("List all notes")
52
+ * .option("--limit <n>", "Max results", "20")
53
+ * .option("--json", "Output as JSON")
54
+ * .option("--next", "Continue from last position")
55
+ * .action(async ({ flags }) => {
56
+ * const results = await listNotes(flags);
57
+ * output(results);
58
+ * });
59
+ * ```
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * // Command with required argument
64
+ * const get = command("get <id>")
65
+ * .description("Get a note by ID")
66
+ * .action(async ({ args }) => {
67
+ * const [id] = args;
68
+ * const note = await getNote(id);
69
+ * output(note);
70
+ * });
71
+ * ```
72
+ */
73
+ declare function command(name: string): CommandBuilder;
74
+ export { command };
@@ -0,0 +1,46 @@
1
+ // @bun
2
+ import"./shared/@outfitter/cli-4yy82cmp.js";
3
+
4
+ // packages/cli/src/command.ts
5
+ import { Command } from "commander";
6
+
7
+ class CommandBuilderImpl {
8
+ command;
9
+ constructor(name) {
10
+ this.command = new Command(name);
11
+ }
12
+ description(text) {
13
+ this.command.description(text);
14
+ return this;
15
+ }
16
+ option(flags, description, defaultValue) {
17
+ this.command.option(flags, description, defaultValue);
18
+ return this;
19
+ }
20
+ requiredOption(flags, description, defaultValue) {
21
+ this.command.requiredOption(flags, description, defaultValue);
22
+ return this;
23
+ }
24
+ alias(alias) {
25
+ this.command.alias(alias);
26
+ return this;
27
+ }
28
+ action(handler) {
29
+ this.command.action(async (...args) => {
30
+ const command = args.at(-1);
31
+ const flags = command.optsWithGlobals?.() ?? command.opts();
32
+ const positional = command.args;
33
+ await handler({ args: positional, flags, command });
34
+ });
35
+ return this;
36
+ }
37
+ build() {
38
+ return this.command;
39
+ }
40
+ }
41
+ function command(name) {
42
+ return new CommandBuilderImpl(name);
43
+ }
44
+ export {
45
+ command
46
+ };