Skip to content

Commit 0e12f07

Browse files
committed
@dep/command@3.0.0
1 parent 3ada0de commit 0e12f07

24 files changed

Lines changed: 539 additions & 423 deletions

README.md

Lines changed: 65 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -38,82 +38,94 @@
3838

3939
## Usage 🎯
4040

41-
### CLI 💻 <!-- if available -->
41+
Use the `Command` class to build and run CLI applications with arguments,
42+
options, and subcommands.
4243

43-
This package is a library for building CLI tools. Once you've defined your
44-
command, you can run it from the command line using Deno or Node.js. For
45-
example, save the script below as `mycli.ts` and execute it with
46-
`deno run mycli.ts [args]` or `node mycli.js [args]`.
44+
```ts
45+
//cli.ts
46+
import { Command, CommandError } from "@dep/command";
4747

48-
Example command execution:
49-
50-
```bash
51-
deno run mycli.ts input.txt --output output.txt
52-
```
53-
54-
### API 🧩
55-
56-
Use the `Command` class to build and configure your CLI. Here's a basic example:
48+
const http = new Command()
49+
.name("http")
50+
.version("1.0.0")
51+
.description("A lightweight HTTP client CLI")
52+
.argument("url", "The target URL for the request")
53+
.option("--header", {
54+
shortFlag: "-H",
55+
description: 'Custom header (e.g., "Content-Type: application/json")',
56+
optional: true,
57+
});
5758

58-
```typescript
59-
import { Command } from "@dep/command";
59+
http.command("get", "Perform a GET request").handler(({ args, options }) => {
60+
console.log(`GET ${args.url}`);
61+
console.log(`Header: ${options.header}`);
62+
});
6063

61-
const cmd = new Command()
62-
.name("mycli")
63-
.description("A simple CLI tool example")
64-
.version("1.0.0")
65-
.argument("input", { description: "Input file path" })
66-
.option("--output", {
67-
kind: "value",
68-
description: "Output file path",
69-
shortFlag: "-o",
64+
http
65+
.command("post", "Perform a POST request")
66+
.option("--body", {
67+
shortFlag: "-B",
68+
description: "The JSON body string to send",
7069
})
7170
.handler(({ args, options }) => {
72-
console.log("Input file:", args.input);
73-
console.log("Output file:", options.output);
71+
console.log(`POST ${args.url}`);
72+
console.log(`Body: ${options.body}`);
7473
});
7574

7675
try {
77-
await clit.run(); // (defaults tokens Deno.args | `process.argv.slice(2)`)
76+
// uses Deno.args or process.argv.slice(2)
77+
await http.run();
7878
} catch (err) {
7979
if (err instanceof CommandError) {
8080
console.error(`\nError: ${err.message}\n`);
81-
cmd.help();
82-
Deno.exit(1); //or process.exit(1);
81+
http.help();
82+
Deno.exit(1); // or process.exit(1)
8383
}
8484
throw err;
8585
}
8686
```
8787

88-
For more advanced usage, including subcommands:
88+
### Running the CLI 💻
8989

90-
```typescript
91-
import { Command } from "@dep/command";
90+
```bash
91+
deno run cli.ts --help
92+
```
9293

93-
const cmd = new Command()
94-
.name("mycli")
95-
.description("CLI with subcommands")
96-
.command("sub", "Subcommand description")
97-
.argument("arg", "Subcommand argument")
98-
.handler(({ args }) => {
99-
console.log("Subcommand arg:", args.arg);
100-
});
94+
```text
95+
Usage: http <url> [options] [command]
10196
102-
try {
103-
await clit.run(); // (defaults tokens Deno.args | `process.argv.slice(2)`)
104-
} catch (err) {
105-
if (err instanceof CommandError) {
106-
console.error(`\nError: ${err.message}\n`);
107-
cmd.help();
108-
Deno.exit(1); //or process.exit(1);
109-
}
110-
throw err;
111-
}
97+
A lightweight HTTP client CLI
98+
99+
Arguments:
100+
<url> The target URL for the request
101+
102+
Options:
103+
--header, -H [header] Custom header (e.g., "Content-Type: application/json")
104+
--help, -h Show help
105+
--version, -v Show version
106+
107+
Commands:
108+
get Perform a GET request
109+
post Perform a POST request
112110
```
113111

114-
Run with `mycli sub value` to execute the subcommand.
112+
```bash
113+
deno run cli.ts get 'https://estarlincito.com' --header "Authorization: Kyumiu"
114+
```
115115

116-
---
116+
```text
117+
GET https://estarlincito.com
118+
Header: Authorization: Kyumiu
119+
```
120+
121+
```bash
122+
deno run cli.ts post 'https://estarlincito.com' --body "user: estarlincito"
123+
```
124+
125+
```text
126+
POST https://estarlincito.com
127+
Body: user: estarlincito
128+
```
117129

118130
## License 📄
119131

deno.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
22
"name": "@dep/command",
3-
"version": "2.5.1",
3+
"version": "3.0.0",
44
"exports": "./src/main.ts",
55
"compilerOptions": {
66
"strict": true
77
},
88
"imports": {
99
"@/": "./src/",
10+
"@dep/assert": "jsr:@dep/assert@^1.3.0",
1011
"@dep/table": "jsr:@dep/table@^1.0.1"
1112
},
1213
"tasks": {
@@ -18,7 +19,7 @@
1819
"lint": "deno lint",
1920
"fmt": "deno fmt",
2021
"clean": "rm -rf dist",
21-
"pack": "deno compile -A -o yaml-layer ./cli.ts",
22+
"pack": "deno compile -A -o command ./cli.ts",
2223
"prerelease": "deno lint && deno fmt",
2324
"release": "deno task prerelease && jsr publish ./"
2425
},

deno.lock

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dep/command",
3-
"version": "2.5.0",
3+
"version": "3.0.0",
44
"description": "A type-safe CLI command builder for Deno and Node.js, enabling easy creation of commands with arguments, options, subcommands, and handlers.",
55
"exports": "./src/main.ts",
66
"author": {

src/core/builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export class CommandBuilder<
122122
config?: K extends "flag" ? Partial<
123123
Pick<CommandOption<L, K, O>, "kind" | "shortFlag" | "description">
124124
>
125-
: Partial<Omit<CommandOption<L, K, O>, "shortFlag">>,
125+
: Partial<CommandOption<L, K, O>>,
126126
): With<[...Options, CommandOption<L, K, O>], Arguments>[Kind] {
127127
const option: CommandOption<L, "value", false> = {
128128
longFlag,

src/core/command.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Command } from "./command.ts";
2+
import { assertDeepEqual } from "@dep/assert";
3+
4+
const http = new Command()
5+
.name("http")
6+
.version("1.0.0")
7+
.description("A lightweight HTTP client CLI")
8+
.argument("url", "The target URL for the request")
9+
.option("--header", {
10+
shortFlag: "-H",
11+
description: 'Custom header (e.g., "Content-Type: application/json")',
12+
optional: true,
13+
});
14+
15+
http.command("get", "Perform a GET request").handler(({ args, options }) => {
16+
console.log(`GET ${args.url}`);
17+
console.log(`Header: ${options.header}`);
18+
});
19+
20+
http
21+
.command("post", "Perform a POST request")
22+
.option("--body", {
23+
shortFlag: "-B",
24+
description: "The JSON body string to send",
25+
})
26+
.handler(({ args, options }) => {
27+
console.log(`POST ${args.url}`);
28+
console.log(`Body: ${options.body}`);
29+
});
30+
31+
const url = "https://estarlincito.com";
32+
33+
Deno.test("http", () =>
34+
assertDeepEqual(http.parse([url]).args, {
35+
url,
36+
}));
37+
38+
Deno.test("get", () =>
39+
assertDeepEqual(http.parse(["get", url]).args, {
40+
url,
41+
}));
42+
43+
Deno.test("post", () => {
44+
assertDeepEqual(
45+
http.parse([
46+
"post",
47+
url,
48+
"--header",
49+
"Authorization: Kyumiu",
50+
"--body",
51+
"user: estarlincito",
52+
]).options,
53+
{
54+
header: "Authorization: Kyumiu",
55+
body: "user: estarlincito",
56+
},
57+
);
58+
});

src/core/command.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { showHelp } from "@/helpers/help.ts";
2-
import { parseRuntime } from "@/helpers/parse/index.ts";
2+
import { ParsedCommand, parser } from "@/helpers/parse/main.ts";
33
import { runner } from "@/helpers/runner.ts";
44
import { CommandBuilder } from "./builder.ts";
5+
import { getDefaultTokens } from "@/helpers/token.ts";
6+
import { type Config } from "@/helpers/utils.ts";
57

68
import type {
79
CommandArgument,
810
CommandHideKind,
9-
CommandInput,
1011
CommandOption,
1112
} from "./types.ts";
1213

@@ -26,13 +27,13 @@ import { $config } from "@/helpers/utils.ts";
2627
* .description('A simple CLI tool example')
2728
* .version('1.0.0')
2829
* .argument('input', { description: 'Input file path' })
29-
* .option('--output', { kind: 'value', description: 'Output file path', shortFlag: '-o' })
30+
* .option('--output', { description: 'Output file path', shortFlag: '-o' })
3031
* .handler(({ args, options }) => {
3132
* console.log('Input file:', args.input);
3233
* console.log('Output file:', options.output);
3334
* });
3435
*
35-
* cmd.run();
36+
* await cmd.run();
3637
* ```
3738
*/
3839
export class Command<
@@ -76,22 +77,19 @@ export class Command<
7677

7778
/**
7879
* Parses the provided tokens (or default CLI arguments) into a typed CommandInput.
79-
* @param tokens - Optional array of tokens to parse; defaults to process/Deno args.
80-
* @returns The parsed CommandInput with typed args, options, and unparsed tokens.
80+
* @param tokens - Optional array of tokens to run with; defaults to process/Deno args.
81+
* @returns The parsed CommandInput with typed args, options, handled, chains, and unparsed tokens.
8182
*/
82-
parse(tokens?: string[]): CommandInput<Options, Arguments> {
83-
return parseRuntime(this[$config](), tokens ?? []) as CommandInput<
84-
Options,
85-
Arguments
86-
>;
83+
parse(tokens: string[] = getDefaultTokens()): ParsedCommand {
84+
return parser(this[$config]() as Config, tokens);
8785
}
8886

8987
/**
9088
* Displays the help information for the command if not hidden.
9189
*/
9290
help() {
9391
if (!this[$config]().hidden.help) {
94-
showHelp(this[$config]());
92+
showHelp([this[$config]()]);
9593
}
9694
}
9795

@@ -102,7 +100,23 @@ export class Command<
102100
* @param tokens - Optional array of tokens to run with; defaults to process/Deno args.
103101
* @returns A Promise that resolves when the command execution completes.
104102
*/
105-
async run(tokens?: string[]): Promise<void> {
103+
async run(tokens: string[] = getDefaultTokens()): Promise<void> {
104+
// Auto-add --help / --version unless hidden
105+
if (!this[$config]().hidden.help) {
106+
this.option("--help", {
107+
kind: "flag",
108+
description: `Show help`,
109+
shortFlag: "-h",
110+
});
111+
}
112+
113+
if (!this[$config]().hidden.version) {
114+
this.option("--version", {
115+
kind: "flag",
116+
description: `Show version`,
117+
shortFlag: "-v",
118+
});
119+
}
106120
await runner(this[$config](), tokens);
107121
}
108122
}

0 commit comments

Comments
 (0)