@rhythmjs/cli 0.0.16 → 0.0.17
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/README.md +61 -28
- package/dist/argv.d.ts +4 -0
- package/dist/argv.js +52 -4
- package/dist/context.d.ts +15 -12
- package/dist/context.js +3 -3
- package/dist/prompt.d.ts +5 -0
- package/dist/prompt.js +101 -4
- package/dist/rhythm-cli-3w09y8wk.js +26 -0
- package/dist/rhythm-cli.d.ts +28 -22
- package/dist/rhythm-cli.js +59 -136
- package/dist/run.d.ts +3 -8
- package/dist/run.js +14 -36
- package/package.json +5 -12
- package/dist/rhythm-cli-28bavedc.js +0 -72
- package/dist/rhythm-cli-abdgd67x.js +0 -21
- package/dist/rhythm-cli-qdf1cds9.js +0 -49
package/README.md
CHANGED
|
@@ -1,49 +1,82 @@
|
|
|
1
1
|
# @rhythmjs/cli
|
|
2
2
|
|
|
3
|
-
The command-line layer of Rhythm, the Bun-native backend framework:
|
|
3
|
+
The command-line layer of Rhythm, the Bun-native backend framework: command routing on top of the `@rhythmjs/rhythm` kernel. `RhythmCli` matches commands against the leading words of argv with [rou3](https://github.com/h3js/rou3), supports nested command groups (`db migrate`), optional and catch-all params, and runs per-command middleware chains.
|
|
4
4
|
|
|
5
|
-
`RhythmCli` is
|
|
5
|
+
`RhythmCli` is a `Pipeline`, like `Rhythm`: it has request-time `use()` middleware and commands, but no startup phase. It runs on its own through `toCliHandler(cli)`, or mounts into a `Rhythm` app with `mount(cli)` when you want startup work (`register`, `decorate`, `include`) around it.
|
|
6
6
|
|
|
7
7
|
## Example
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
|
-
import { Rhythm } from "@rhythmjs/rhythm";
|
|
11
10
|
import { RhythmCli } from "@rhythmjs/cli";
|
|
11
|
+
import { withParsedArgv } from "@rhythmjs/cli/argv";
|
|
12
12
|
import { toCliHandler } from "@rhythmjs/cli/run";
|
|
13
|
-
import type { RhythmCliContext } from "@rhythmjs/cli/context";
|
|
14
13
|
|
|
15
|
-
const cli = new RhythmCli(
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
const cli = new RhythmCli({ name: "tool" })
|
|
15
|
+
.use(withParsedArgv())
|
|
16
|
+
.cmd("greet :name?", (ctx) => {
|
|
17
|
+
const message = `hello ${ctx.params.name ?? "world"}`;
|
|
18
|
+
ctx.log(ctx.flags.shout ? message.toUpperCase() : message);
|
|
19
|
+
})
|
|
20
|
+
.cmd(
|
|
21
|
+
"deploy :env",
|
|
22
|
+
(ctx, next) => {
|
|
23
|
+
if (!["staging", "production"].includes(ctx.params.env))
|
|
24
|
+
return ctx.fail(`unknown environment '${ctx.params.env}'`);
|
|
25
|
+
return next();
|
|
26
|
+
},
|
|
27
|
+
(ctx) => ctx.log(`deployed to ${ctx.params.env}`),
|
|
28
|
+
);
|
|
18
29
|
|
|
19
|
-
|
|
20
|
-
process.exitCode = await toCliHandler(app)(process.argv.slice(2));
|
|
30
|
+
process.exitCode = await toCliHandler(cli)(Bun.argv.slice(2));
|
|
21
31
|
```
|
|
22
32
|
|
|
23
|
-
A fuller runnable version, including nested command groups and interactive prompts, is at [`examples/cli`](../../examples/cli).
|
|
24
|
-
|
|
25
33
|
## Concepts
|
|
26
34
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- **
|
|
32
|
-
- **
|
|
33
|
-
- **
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
- **Commands.** `cmd(command, ...handlers)` takes a space-separated command (`"db migrate :name"`). Words are literal unless they start with `:`. Handlers are `(ctx, next)` middleware and form a chain; the first may be `derive()`, which widens the context type for the handlers after it. Matching uses the words before the first flag, so `deploy staging --force` matches `deploy :env`.
|
|
36
|
+
- **Params.** `:name` is required, `:name?` optional (absent when not given), `:name+` / `:name*` / `**:name` capture the remaining words as a `string[]` (`*` and `**` alone land in `params["0"]`). Values are URI-decoded. Params are typed from the command string: `:name` is `string`, `:name?` is `string | undefined`, catch-alls are `string[]`, and reading a name that is not in the command is a compile error.
|
|
37
|
+
- **Strict context.** The context only has what was added: `ctx.nope` is a compile error, `ctx.flags` only exists after `withParsedArgv()`, `ctx.prompt` only after `withPrompt()`, and `derive()` widens it for the handlers after it. A command-level `derive` only affects that command. `toCliHandler(app)` rejects, at compile time, an app that requires input the runner cannot supply.
|
|
38
|
+
- **Request-time middleware.** `cli.use(middleware)` wraps commands and runs only when a command matched, so a guard never answers an unknown command. `derive()` extends the context type.
|
|
39
|
+
- **Running and exit codes.** `toCliHandler(app, io?)` returns `(argv) => Promise<number>`: the exit code. It reports an unmatched command as `error: unknown command 'x'` (exit code 2, or `no command given`), and a thrown error as an error line (exit code 1). `app` can be a `RhythmCli`, or a `Rhythm` app that mounts one. Register `cmd("", handler)` to handle the no-argument case yourself.
|
|
40
|
+
- **Output.** The context carries `argv`, `stdout`, `stderr`, `exitCode`, and the helpers `log(...)` (stdout), `error(...)` (stderr) and `fail(message, code = 1)` (prints `error: message` to stderr and sets the exit code). Pass `io: { stdout, stderr }` to `toCliHandler` to capture output, e.g. in tests.
|
|
41
|
+
- **Named failures.** `new RhythmCli({ name })` labels failures when mounted: `mounted cli "tool" failed` with the original error as `cause`.
|
|
42
|
+
|
|
43
|
+
## Opt-in extensions
|
|
44
|
+
|
|
45
|
+
Nothing is added to the context unless you ask for it:
|
|
46
|
+
|
|
47
|
+
- **`withParsedArgv()`** (`@rhythmjs/cli/argv`) adds `ctx.flags` and `ctx.positionals`. Parsing is schema-less: `--foo=bar` and `--foo bar` give a string, a bare `--foo` (or one followed by another flag) is `true`, `-f` works the same way, and everything after `--` is positional. Like `minimist`, `--force now` consumes `now` as the value. `parseArgv(argv)` is the same parser as a plain function.
|
|
48
|
+
- **`withPrompt()`** (`@rhythmjs/cli/prompt`) adds `ctx.prompt` with `text()`, `confirm()`, `select()` and `multiSelect()` (the last two accept `allowCustom` to add a "type your own" choice) and closes its input when the command finishes. By default it reads lines through Bun's async-iterable `console`, only when you first ask; pass `() => ({ ask, write, close? })` to supply your own IO. `createPrompt(io)` is the same thing without the middleware.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const cli = new RhythmCli().use(withPrompt()).cmd("init", async (ctx) => {
|
|
52
|
+
const name = await ctx.prompt.text("Project name?", { default: "my-app" });
|
|
53
|
+
if (await ctx.prompt.confirm("Install dependencies?", { default: true })) ctx.log(`installing for ${name}`);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## With a Rhythm app
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { Rhythm, decorate, mount } from "@rhythmjs/rhythm";
|
|
61
|
+
|
|
62
|
+
const app = new Rhythm({ name: "tool" })
|
|
63
|
+
.register(decorate(async () => ({ config: await loadConfig() })))
|
|
64
|
+
.use(mount(cli));
|
|
65
|
+
|
|
66
|
+
process.exitCode = await toCliHandler(app)(Bun.argv.slice(2));
|
|
67
|
+
```
|
|
36
68
|
|
|
37
69
|
## API
|
|
38
70
|
|
|
39
|
-
- `new RhythmCli(options?)`: `
|
|
40
|
-
- `.command
|
|
41
|
-
- `.use(
|
|
42
|
-
- `.
|
|
43
|
-
- `.
|
|
44
|
-
- `
|
|
45
|
-
- `
|
|
71
|
+
- `new RhythmCli<I, D>(options?)`: `I` is context the CLI needs from the parent (checked by `mount()`), `D` is what its own `derive()` calls add; both are usually inferred. `options.name` labels failures (`type` defaults to `"cli"`).
|
|
72
|
+
- `.cmd(command, ...handlers)`: register a command.
|
|
73
|
+
- `.use(middleware)`: command-level middleware; `derive()` extends the context type.
|
|
74
|
+
- `.callback()`: the CLI as a `(ctx) => Promise<ctx>` function, used by `mount()` and `toCliHandler()`.
|
|
75
|
+
- `.sources` / `.parent` / `.options`: inherited from `Pipeline`.
|
|
76
|
+
- `createCliContext(argv, io?)` (`@rhythmjs/cli/context`): the base context with `log`, `error`, `fail` and `exitCode`.
|
|
77
|
+
- `toCliHandler(app, io?)` (`@rhythmjs/cli/run`): bridges a CLI or `Rhythm` app to `(argv) => Promise<number>`.
|
|
78
|
+
- `parseArgv(argv)`, `withParsedArgv()` (`@rhythmjs/cli/argv`); `createPrompt(io)`, `createStdioPromptIO()`, `withPrompt(createIO?)` (`@rhythmjs/cli/prompt`).
|
|
46
79
|
|
|
47
80
|
## Running on Bun
|
|
48
81
|
|
|
49
|
-
|
|
82
|
+
The runtime half is coupled to Bun on purpose: prompts read through Bun's async-iterable `console`, and nothing is adapted for other runtimes.
|
package/dist/argv.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { type ExtensionMiddleware } from "@rhythmjs/rhythm";
|
|
1
2
|
export interface ParsedArgv {
|
|
2
3
|
positionals: string[];
|
|
3
4
|
flags: Record<string, string | boolean>;
|
|
4
5
|
}
|
|
5
6
|
export declare function parseArgv(argv: readonly string[]): ParsedArgv;
|
|
7
|
+
export declare function withParsedArgv(): ExtensionMiddleware<{
|
|
8
|
+
argv: readonly string[];
|
|
9
|
+
}, ParsedArgv>;
|
package/dist/argv.js
CHANGED
|
@@ -1,7 +1,55 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// src/argv.ts
|
|
3
|
+
import { derive } from "@rhythmjs/rhythm";
|
|
4
|
+
function parseArgv(argv) {
|
|
5
|
+
const positionals = [];
|
|
6
|
+
const flags = {};
|
|
7
|
+
let rawMode = false;
|
|
8
|
+
for (let i = 0;i < argv.length; i++) {
|
|
9
|
+
const token = argv[i];
|
|
10
|
+
if (rawMode) {
|
|
11
|
+
positionals.push(token);
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (token === "--") {
|
|
15
|
+
rawMode = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (token.startsWith("--")) {
|
|
19
|
+
const eqIndex = token.indexOf("=");
|
|
20
|
+
if (eqIndex !== -1) {
|
|
21
|
+
flags[token.slice(2, eqIndex)] = token.slice(eqIndex + 1);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const name = token.slice(2);
|
|
25
|
+
const next = argv[i + 1];
|
|
26
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
27
|
+
flags[name] = next;
|
|
28
|
+
i++;
|
|
29
|
+
} else {
|
|
30
|
+
flags[name] = true;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
35
|
+
const name = token.slice(1);
|
|
36
|
+
const next = argv[i + 1];
|
|
37
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
38
|
+
flags[name] = next;
|
|
39
|
+
i++;
|
|
40
|
+
} else {
|
|
41
|
+
flags[name] = true;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
positionals.push(token);
|
|
46
|
+
}
|
|
47
|
+
return { positionals, flags };
|
|
48
|
+
}
|
|
49
|
+
function withParsedArgv() {
|
|
50
|
+
return derive((ctx) => parseArgv(ctx.argv));
|
|
51
|
+
}
|
|
5
52
|
export {
|
|
6
|
-
|
|
53
|
+
parseArgv,
|
|
54
|
+
withParsedArgv
|
|
7
55
|
};
|
package/dist/context.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
1
|
+
export type CliWriter = {
|
|
2
|
+
write(text: string): unknown;
|
|
3
|
+
};
|
|
4
|
+
export type CliIO = {
|
|
5
|
+
stdout: CliWriter;
|
|
6
|
+
stderr: CliWriter;
|
|
7
|
+
};
|
|
9
8
|
export interface RhythmCliContext {
|
|
10
|
-
readonly argv:
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
|
|
9
|
+
readonly argv: string[];
|
|
10
|
+
readonly stdout: CliWriter;
|
|
11
|
+
readonly stderr: CliWriter;
|
|
12
|
+
exitCode: number;
|
|
13
|
+
log(...parts: unknown[]): void;
|
|
14
|
+
error(...parts: unknown[]): void;
|
|
15
|
+
fail(this: RhythmCliContext, message: string, code?: number): void;
|
|
14
16
|
}
|
|
17
|
+
export declare function createCliContext(argv: string[], io?: CliIO): RhythmCliContext;
|
package/dist/context.js
CHANGED
package/dist/prompt.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ExtensionMiddleware } from "@rhythmjs/rhythm";
|
|
1
2
|
export interface RhythmPrompt {
|
|
2
3
|
text(message: string, options?: {
|
|
3
4
|
default?: string;
|
|
@@ -21,3 +22,7 @@ export declare function createPrompt(io: RhythmPromptIO): {
|
|
|
21
22
|
prompt: RhythmPrompt;
|
|
22
23
|
close: () => void;
|
|
23
24
|
};
|
|
25
|
+
export declare function createStdioPromptIO(): RhythmPromptIO;
|
|
26
|
+
export declare function withPrompt(createIO?: () => RhythmPromptIO): ExtensionMiddleware<{}, {
|
|
27
|
+
prompt: RhythmPrompt;
|
|
28
|
+
}>;
|
package/dist/prompt.js
CHANGED
|
@@ -1,7 +1,104 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// src/prompt.ts
|
|
3
|
+
var CUSTOM_LABEL = "Other (type your own)";
|
|
4
|
+
function createPrompt(io) {
|
|
5
|
+
const { ask, write } = io;
|
|
6
|
+
const text = async (message, options) => {
|
|
7
|
+
const suffix = options?.default ? ` (${options.default})` : "";
|
|
8
|
+
const answer = (await ask(`${message}${suffix} `)).trim();
|
|
9
|
+
return answer || options?.default || "";
|
|
10
|
+
};
|
|
11
|
+
const confirm = async (message, options) => {
|
|
12
|
+
const suffix = options?.default === undefined ? "(y/n)" : options.default ? "(Y/n)" : "(y/N)";
|
|
13
|
+
while (true) {
|
|
14
|
+
const answer = (await ask(`${message} ${suffix} `)).trim().toLowerCase();
|
|
15
|
+
if (!answer && options?.default !== undefined)
|
|
16
|
+
return options.default;
|
|
17
|
+
if (answer === "y" || answer === "yes")
|
|
18
|
+
return true;
|
|
19
|
+
if (answer === "n" || answer === "no")
|
|
20
|
+
return false;
|
|
21
|
+
write(`Please answer y or n.
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function listChoices(message, choices, allowCustom) {
|
|
26
|
+
write(`${message}
|
|
27
|
+
`);
|
|
28
|
+
choices.forEach((choice, i) => write(` ${i + 1}) ${choice}
|
|
29
|
+
`));
|
|
30
|
+
const customIndex = allowCustom ? choices.length + 1 : -1;
|
|
31
|
+
if (allowCustom)
|
|
32
|
+
write(` ${customIndex}) ${CUSTOM_LABEL}
|
|
33
|
+
`);
|
|
34
|
+
return customIndex;
|
|
35
|
+
}
|
|
36
|
+
const select = async (message, choices, options) => {
|
|
37
|
+
const customIndex = listChoices(message, choices, options?.allowCustom);
|
|
38
|
+
while (true) {
|
|
39
|
+
const index = Number(await ask("Enter number: "));
|
|
40
|
+
if (Number.isInteger(index) && index >= 1 && index <= choices.length)
|
|
41
|
+
return choices[index - 1];
|
|
42
|
+
if (index === customIndex)
|
|
43
|
+
return text("Enter value:");
|
|
44
|
+
write(`Invalid choice, try again.
|
|
45
|
+
`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const multiSelect = async (message, choices, options) => {
|
|
49
|
+
const customIndex = listChoices(message, choices, options?.allowCustom);
|
|
50
|
+
const maxIndex = options?.allowCustom ? customIndex : choices.length;
|
|
51
|
+
write(`(comma-separated numbers, e.g. "1,3")
|
|
52
|
+
`);
|
|
53
|
+
while (true) {
|
|
54
|
+
const raw = await ask("Enter numbers: ");
|
|
55
|
+
const indices = raw.split(",").map((part) => Number(part.trim())).filter((n) => n !== 0 || raw.trim() === "0");
|
|
56
|
+
const valid = indices.length > 0 && indices.every((n) => Number.isInteger(n) && n >= 1 && n <= maxIndex);
|
|
57
|
+
if (!valid) {
|
|
58
|
+
write(`Invalid choices, try again.
|
|
59
|
+
`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const results = [];
|
|
63
|
+
for (const n of indices) {
|
|
64
|
+
results.push(n === customIndex ? await text("Enter value:") : choices[n - 1]);
|
|
65
|
+
}
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
return { prompt: { text, confirm, select, multiSelect }, close: () => io.close?.() };
|
|
70
|
+
}
|
|
71
|
+
function createStdioPromptIO() {
|
|
72
|
+
let lines;
|
|
73
|
+
return {
|
|
74
|
+
ask: async (query) => {
|
|
75
|
+
process.stdout.write(query);
|
|
76
|
+
lines ??= console[Symbol.asyncIterator]();
|
|
77
|
+
const { value } = await lines.next();
|
|
78
|
+
return value ?? "";
|
|
79
|
+
},
|
|
80
|
+
write: (text) => {
|
|
81
|
+
process.stdout.write(text);
|
|
82
|
+
},
|
|
83
|
+
close: () => {
|
|
84
|
+
lines?.return?.();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function withPrompt(createIO = createStdioPromptIO) {
|
|
89
|
+
const middleware = async (ctx, next) => {
|
|
90
|
+
const { prompt, close } = createPrompt(createIO());
|
|
91
|
+
ctx.prompt = prompt;
|
|
92
|
+
try {
|
|
93
|
+
await next();
|
|
94
|
+
} finally {
|
|
95
|
+
close();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
return middleware;
|
|
99
|
+
}
|
|
5
100
|
export {
|
|
6
|
-
|
|
101
|
+
createPrompt,
|
|
102
|
+
createStdioPromptIO,
|
|
103
|
+
withPrompt
|
|
7
104
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/context.ts
|
|
3
|
+
import { format } from "util";
|
|
4
|
+
function createCliContext2(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
|
|
5
|
+
return {
|
|
6
|
+
argv,
|
|
7
|
+
stdout: io.stdout,
|
|
8
|
+
stderr: io.stderr,
|
|
9
|
+
exitCode: 0,
|
|
10
|
+
log(...parts) {
|
|
11
|
+
io.stdout.write(`${format(...parts)}
|
|
12
|
+
`);
|
|
13
|
+
},
|
|
14
|
+
error(...parts) {
|
|
15
|
+
io.stderr.write(`${format(...parts)}
|
|
16
|
+
`);
|
|
17
|
+
},
|
|
18
|
+
fail(message, code = 1) {
|
|
19
|
+
io.stderr.write(`error: ${message}
|
|
20
|
+
`);
|
|
21
|
+
this.exitCode = code;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { createCliContext2 };
|
package/dist/rhythm-cli.d.ts
CHANGED
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type InferRouteParams } from "rou3";
|
|
2
|
+
import { Pipeline, type ExtensionMiddleware, type Middleware, type Next, type PipelineOptions } from "@rhythmjs/rhythm";
|
|
2
3
|
import type { RhythmCliContext } from "./context";
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
export type CliContext<T extends object> = T & RhythmCliContext;
|
|
5
|
+
export type UseContext<T extends object> = CliContext<T> & {
|
|
6
|
+
readonly params: Record<string, string | string[]>;
|
|
7
|
+
};
|
|
8
|
+
type ToPath<Command extends string> = Command extends `${infer Word} ${infer Rest}` ? `${Word}/${ToPath<Rest>}` : Command;
|
|
9
|
+
type CatchAllWord<Word extends string> = Word extends `**:${infer Name}` ? Name : Word extends `:${infer Name}+` ? Name : Word extends `:${infer Name}*` ? Name : Word extends "*" | "**" ? "0" : never;
|
|
10
|
+
type CatchAllKey<Command extends string> = Command extends `${infer Word} ${infer Rest}` ? CatchAllWord<Word> | CatchAllKey<Rest> : CatchAllWord<Command>;
|
|
11
|
+
type Flatten<T> = {
|
|
12
|
+
[K in keyof T]: T[K];
|
|
13
|
+
} & {};
|
|
14
|
+
export type CommandParams<C extends string> = Flatten<Omit<InferRouteParams<`/${ToPath<C>}`>, CatchAllKey<C> | "_"> & {
|
|
15
|
+
[K in CatchAllKey<C>]: string[];
|
|
16
|
+
}>;
|
|
17
|
+
export type CommandContext<T extends object, C extends string> = CliContext<T> & {
|
|
18
|
+
params: CommandParams<C>;
|
|
16
19
|
};
|
|
17
|
-
export
|
|
20
|
+
export type CommandHandler<T extends object, C extends string> = (ctx: CommandContext<T, C>, next: Next) => unknown | Promise<unknown>;
|
|
21
|
+
export type CommandHandlers<T extends object, C extends string> = [CommandHandler<T, C>, ...CommandHandler<T, C>[]];
|
|
22
|
+
export declare class RhythmCli<I extends object = {}, D extends object = {}> extends Pipeline<UseContext<I & D>> {
|
|
18
23
|
#private;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
use<
|
|
22
|
-
use(
|
|
23
|
-
|
|
24
|
-
command
|
|
25
|
-
|
|
24
|
+
readonly "~input"?: I;
|
|
25
|
+
constructor(options?: PipelineOptions);
|
|
26
|
+
use<U extends object>(middleware: ExtensionMiddleware<UseContext<I & D>, U>): RhythmCli<I, D & U>;
|
|
27
|
+
use(middleware: Middleware<UseContext<I & D>>): this;
|
|
28
|
+
cmd<C extends string, U extends object>(command: C, middleware: ExtensionMiddleware<CommandContext<I & D, C>, U>, ...handlers: CommandHandler<I & D & U, C>[]): this;
|
|
29
|
+
cmd<C extends string>(command: C, ...handlers: CommandHandlers<I & D, C>): this;
|
|
30
|
+
callback(): (input: CliContext<I>) => Promise<CliContext<I & D>>;
|
|
26
31
|
}
|
|
32
|
+
export {};
|
package/dist/rhythm-cli.js
CHANGED
|
@@ -1,154 +1,77 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
import {
|
|
3
|
-
parseArgv2
|
|
4
|
-
} from "./rhythm-cli-qdf1cds9.js";
|
|
5
|
-
|
|
6
2
|
// src/rhythm-cli.ts
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
function
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
for (let i = 0;i < fixed.length; i++) {
|
|
22
|
-
const segment = fixed[i];
|
|
23
|
-
const token = positionals[i];
|
|
24
|
-
if (segment.startsWith(":")) {
|
|
25
|
-
if (token !== undefined)
|
|
26
|
-
params[segment.slice(1, isOptional(segment) ? -1 : undefined)] = token;
|
|
27
|
-
} else if (segment !== token) {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (catchAll && positionals.length > fixed.length) {
|
|
32
|
-
params._ = positionals.slice(fixed.length).join(" ");
|
|
3
|
+
import { addRoute, createRouter, findRoute } from "rou3";
|
|
4
|
+
import {
|
|
5
|
+
Pipeline,
|
|
6
|
+
compose
|
|
7
|
+
} from "@rhythmjs/rhythm";
|
|
8
|
+
var isFlag = (token) => token.startsWith("-") && token !== "-";
|
|
9
|
+
var lookupPath = (words) => `/${words.map(encodeURIComponent).join("/")}`;
|
|
10
|
+
function catchAllKey(words) {
|
|
11
|
+
for (const word of words) {
|
|
12
|
+
const named = /^\*\*:(\w+)$/.exec(word) ?? /^:(\w+)[+*]$/.exec(word);
|
|
13
|
+
if (named)
|
|
14
|
+
return named[1];
|
|
15
|
+
if (word === "*" || word === "**")
|
|
16
|
+
return "0";
|
|
33
17
|
}
|
|
34
|
-
return
|
|
18
|
+
return;
|
|
35
19
|
}
|
|
36
|
-
function
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
20
|
+
function decodeParams(params, catchAll) {
|
|
21
|
+
const decoded = {};
|
|
22
|
+
try {
|
|
23
|
+
for (const [key, value] of Object.entries(params ?? {})) {
|
|
24
|
+
if (key === "_" && catchAll === "0")
|
|
25
|
+
continue;
|
|
26
|
+
decoded[key] = key === catchAll ? value.split("/").filter(Boolean).map(decodeURIComponent) : decodeURIComponent(value);
|
|
41
27
|
}
|
|
42
|
-
}
|
|
43
|
-
if (segments.filter(isCatchAll).length > 1) {
|
|
44
|
-
throw new TypeError(`command "${path}" has more than one catch-all`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function mountedCommands(cli, out, seen = new Set) {
|
|
48
|
-
if (seen.has(cli))
|
|
28
|
+
} catch {
|
|
49
29
|
return;
|
|
50
|
-
seen.add(cli);
|
|
51
|
-
for (const entry of cli.entries) {
|
|
52
|
-
if (entry.kind === "command")
|
|
53
|
-
out.push({ segments: [...entry.segments] });
|
|
54
|
-
else {
|
|
55
|
-
const child = sourceOf(entry.fn);
|
|
56
|
-
if (child instanceof RhythmCli)
|
|
57
|
-
mountedCommands(child, out, seen);
|
|
58
|
-
}
|
|
59
30
|
}
|
|
31
|
+
if (catchAll !== undefined)
|
|
32
|
+
decoded[catchAll] ??= [];
|
|
33
|
+
return decoded;
|
|
60
34
|
}
|
|
61
35
|
|
|
62
|
-
class RhythmCli {
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
this.#options = options;
|
|
67
|
-
}
|
|
68
|
-
get entries() {
|
|
69
|
-
return [...this.#entries];
|
|
70
|
-
}
|
|
71
|
-
get #prefixSegments() {
|
|
72
|
-
return this.#options.prefix ? toSegments(this.#options.prefix) : [];
|
|
36
|
+
class RhythmCli extends Pipeline {
|
|
37
|
+
#routes = createRouter();
|
|
38
|
+
constructor(options) {
|
|
39
|
+
super({ type: "cli", ...options });
|
|
73
40
|
}
|
|
74
|
-
use(
|
|
75
|
-
|
|
76
|
-
throw new TypeError("middleware must be a function!");
|
|
77
|
-
if (condition !== undefined && typeof condition !== "function")
|
|
78
|
-
throw new TypeError("condition must be a function!");
|
|
79
|
-
const source = sourceOf(fn);
|
|
80
|
-
const wrapped = condition ? gate(fn, condition) : fn;
|
|
81
|
-
this.#entries.push({ kind: "middleware", fn: condition && source ? withSource(wrapped, source) : wrapped });
|
|
82
|
-
return this;
|
|
41
|
+
use(middleware) {
|
|
42
|
+
return super.use(middleware);
|
|
83
43
|
}
|
|
84
|
-
command
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
this.#
|
|
44
|
+
cmd(command, ...handlers) {
|
|
45
|
+
const chain = compose(handlers);
|
|
46
|
+
const words = command.trim().split(/\s+/).filter(Boolean);
|
|
47
|
+
addRoute(this.#routes, "", `/${words.join("/")}`, {
|
|
48
|
+
run: (ctx) => chain(ctx),
|
|
49
|
+
catchAll: catchAllKey(words)
|
|
50
|
+
});
|
|
88
51
|
return this;
|
|
89
52
|
}
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const dispatchFor = (compiled) => {
|
|
101
|
-
return async (ctx, next) => {
|
|
102
|
-
const { positionals } = parseArgv2(ctx.argv);
|
|
103
|
-
for (const command of compiled) {
|
|
104
|
-
const args = matchCommand(command.segments, positionals);
|
|
105
|
-
if (!args)
|
|
106
|
-
continue;
|
|
107
|
-
await command.dispatch({ ...ctx, args }, next);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
await next();
|
|
111
|
-
};
|
|
53
|
+
callback() {
|
|
54
|
+
const run = this.chain();
|
|
55
|
+
return async (input) => {
|
|
56
|
+
const ctx = input;
|
|
57
|
+
const match = this.#match(ctx);
|
|
58
|
+
if (!match)
|
|
59
|
+
return ctx;
|
|
60
|
+
Object.assign(ctx, { params: match.params });
|
|
61
|
+
await run(ctx, () => match.route.run(ctx));
|
|
62
|
+
return ctx;
|
|
112
63
|
};
|
|
113
|
-
const stack = [];
|
|
114
|
-
let i = 0;
|
|
115
|
-
while (i < this.#entries.length) {
|
|
116
|
-
const entry = this.#entries[i];
|
|
117
|
-
if (entry.kind === "middleware") {
|
|
118
|
-
const { fn } = entry;
|
|
119
|
-
const source = sourceOf(fn);
|
|
120
|
-
if (source) {
|
|
121
|
-
if (source instanceof RhythmCli) {
|
|
122
|
-
const commands = [];
|
|
123
|
-
mountedCommands(source, commands);
|
|
124
|
-
groups.push(commands);
|
|
125
|
-
}
|
|
126
|
-
stack.push(fn);
|
|
127
|
-
} else {
|
|
128
|
-
const from = groups.length;
|
|
129
|
-
stack.push((ctx, next) => reaches(ctx, from) ? fn(ctx, next) : next());
|
|
130
|
-
}
|
|
131
|
-
i++;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
const compiled = [];
|
|
135
|
-
while (i < this.#entries.length) {
|
|
136
|
-
const command = this.#entries[i];
|
|
137
|
-
if (command.kind !== "command")
|
|
138
|
-
break;
|
|
139
|
-
compiled.push({ segments: command.segments, dispatch: compose(command.handlers) });
|
|
140
|
-
i++;
|
|
141
|
-
}
|
|
142
|
-
groups.push(compiled);
|
|
143
|
-
stack.push(dispatchFor(compiled));
|
|
144
|
-
}
|
|
145
|
-
return compose(stack);
|
|
146
64
|
}
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
65
|
+
#match(ctx) {
|
|
66
|
+
const end = ctx.argv.findIndex(isFlag);
|
|
67
|
+
const words = end === -1 ? ctx.argv : ctx.argv.slice(0, end);
|
|
68
|
+
const found = findRoute(this.#routes, "", lookupPath(words));
|
|
69
|
+
if (!found)
|
|
70
|
+
return;
|
|
71
|
+
const params = decodeParams(found.params, found.data.catchAll);
|
|
72
|
+
if (!params)
|
|
73
|
+
return;
|
|
74
|
+
return { route: found.data, params };
|
|
152
75
|
}
|
|
153
76
|
}
|
|
154
77
|
export {
|
package/dist/run.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { type
|
|
3
|
-
|
|
4
|
-
export declare function toCliHandler<TContext extends RhythmCliContext>(app: Rhythm<RhythmCliContext, any, TContext>): (argv: string[]) => Promise<number>;
|
|
5
|
-
export declare function createPrompt(): {
|
|
6
|
-
prompt: RhythmPrompt;
|
|
7
|
-
close: () => void;
|
|
8
|
-
};
|
|
1
|
+
import type { Mountable } from "@rhythmjs/rhythm";
|
|
2
|
+
import { type CliIO, type RhythmCliContext } from "./context";
|
|
3
|
+
export declare function toCliHandler<I extends object = any>(app: Mountable<I> & (RhythmCliContext extends I ? unknown : never), io?: CliIO): (argv: string[]) => Promise<number>;
|
package/dist/run.js
CHANGED
|
@@ -1,48 +1,26 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./rhythm-cli-
|
|
5
|
-
import {
|
|
6
|
-
createPrompt2
|
|
7
|
-
} from "./rhythm-cli-28bavedc.js";
|
|
8
|
-
import {
|
|
9
|
-
RhythmCliResponse2
|
|
10
|
-
} from "./rhythm-cli-abdgd67x.js";
|
|
3
|
+
createCliContext2
|
|
4
|
+
} from "./rhythm-cli-3w09y8wk.js";
|
|
11
5
|
|
|
12
6
|
// src/run.ts
|
|
13
|
-
function toCliHandler(app) {
|
|
7
|
+
function toCliHandler(app, io) {
|
|
14
8
|
const run = app.callback();
|
|
15
9
|
return async (argv) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const lines = console[Symbol.asyncIterator]();
|
|
28
|
-
return {
|
|
29
|
-
ask: async (query) => {
|
|
30
|
-
process.stdout.write(query);
|
|
31
|
-
const { value } = await lines.next();
|
|
32
|
-
return value ?? "";
|
|
33
|
-
},
|
|
34
|
-
write: (text) => {
|
|
35
|
-
process.stdout.write(text);
|
|
36
|
-
},
|
|
37
|
-
close: () => {
|
|
38
|
-
lines.return?.();
|
|
10
|
+
const base = createCliContext2(argv, io);
|
|
11
|
+
try {
|
|
12
|
+
const result = await run(base);
|
|
13
|
+
if (result?.params === undefined) {
|
|
14
|
+
base.fail(argv.length ? `unknown command '${argv[0]}'` : "no command given", 2);
|
|
15
|
+
return base.exitCode;
|
|
16
|
+
}
|
|
17
|
+
return result.exitCode ?? base.exitCode;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
base.fail(error instanceof Error ? error.message : String(error));
|
|
20
|
+
return base.exitCode;
|
|
39
21
|
}
|
|
40
22
|
};
|
|
41
23
|
}
|
|
42
|
-
function createPrompt() {
|
|
43
|
-
return createPrompt2(defaultPromptIO());
|
|
44
|
-
}
|
|
45
24
|
export {
|
|
46
|
-
createPrompt,
|
|
47
25
|
toCliHandler
|
|
48
26
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhythmjs/cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Command-line
|
|
3
|
+
"version": "0.0.17",
|
|
4
|
+
"description": "Command-line routing for Rhythm: RhythmCli commands, argv parsing, prompts and a Bun runner.",
|
|
5
5
|
"homepage": "https://rhythm.js.org/cli",
|
|
6
6
|
"license": "ISC",
|
|
7
7
|
"repository": {
|
|
@@ -35,21 +35,14 @@
|
|
|
35
35
|
"types": "./dist/run.d.ts",
|
|
36
36
|
"default": "./dist/run.js"
|
|
37
37
|
},
|
|
38
|
-
"./adapters/context": {
|
|
39
|
-
"types": "./dist/context.d.ts",
|
|
40
|
-
"default": "./dist/context.js"
|
|
41
|
-
},
|
|
42
|
-
"./adapters/bun": {
|
|
43
|
-
"types": "./dist/run.d.ts",
|
|
44
|
-
"default": "./dist/run.js"
|
|
45
|
-
},
|
|
46
38
|
"./package.json": "./package.json"
|
|
47
39
|
},
|
|
48
40
|
"publishConfig": {
|
|
49
41
|
"access": "public"
|
|
50
42
|
},
|
|
51
43
|
"dependencies": {
|
|
52
|
-
"@rhythmjs/rhythm": "0.0.
|
|
44
|
+
"@rhythmjs/rhythm": "0.0.17",
|
|
45
|
+
"rou3": "^1.0.0"
|
|
53
46
|
},
|
|
54
47
|
"devDependencies": {
|
|
55
48
|
"@types/bun": "^1.4.2",
|
|
@@ -60,7 +53,7 @@
|
|
|
60
53
|
"bun": ">=1.2.0"
|
|
61
54
|
},
|
|
62
55
|
"scripts": {
|
|
63
|
-
"build": "bun build src/rhythm-cli.ts src/argv.ts src/prompt.ts src/context.ts src/run.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
|
|
56
|
+
"build": "rm -rf dist && bun build src/rhythm-cli.ts src/argv.ts src/prompt.ts src/context.ts src/run.ts --outdir dist --root src --format esm --target bun --packages external --splitting && tsc -p tsconfig.build.json",
|
|
64
57
|
"typecheck": "tsc --noEmit",
|
|
65
58
|
"test": "bun test"
|
|
66
59
|
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/prompt.ts
|
|
3
|
-
var CUSTOM_LABEL = "Other (type your own)";
|
|
4
|
-
function createPrompt2(io) {
|
|
5
|
-
const { ask, write } = io;
|
|
6
|
-
const text = async (message, options) => {
|
|
7
|
-
const suffix = options?.default ? ` (${options.default})` : "";
|
|
8
|
-
const answer = (await ask(`${message}${suffix} `)).trim();
|
|
9
|
-
return answer || options?.default || "";
|
|
10
|
-
};
|
|
11
|
-
const confirm = async (message, options) => {
|
|
12
|
-
const suffix = options?.default === undefined ? "(y/n)" : options.default ? "(Y/n)" : "(y/N)";
|
|
13
|
-
while (true) {
|
|
14
|
-
const answer = (await ask(`${message} ${suffix} `)).trim().toLowerCase();
|
|
15
|
-
if (!answer && options?.default !== undefined)
|
|
16
|
-
return options.default;
|
|
17
|
-
if (answer === "y" || answer === "yes")
|
|
18
|
-
return true;
|
|
19
|
-
if (answer === "n" || answer === "no")
|
|
20
|
-
return false;
|
|
21
|
-
write(`Please answer y or n.
|
|
22
|
-
`);
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
function listChoices(message, choices, allowCustom) {
|
|
26
|
-
write(`${message}
|
|
27
|
-
`);
|
|
28
|
-
choices.forEach((choice, i) => write(` ${i + 1}) ${choice}
|
|
29
|
-
`));
|
|
30
|
-
const customIndex = allowCustom ? choices.length + 1 : -1;
|
|
31
|
-
if (allowCustom)
|
|
32
|
-
write(` ${customIndex}) ${CUSTOM_LABEL}
|
|
33
|
-
`);
|
|
34
|
-
return customIndex;
|
|
35
|
-
}
|
|
36
|
-
const select = async (message, choices, options) => {
|
|
37
|
-
const customIndex = listChoices(message, choices, options?.allowCustom);
|
|
38
|
-
while (true) {
|
|
39
|
-
const index = Number(await ask("Enter number: "));
|
|
40
|
-
if (Number.isInteger(index) && index >= 1 && index <= choices.length)
|
|
41
|
-
return choices[index - 1];
|
|
42
|
-
if (index === customIndex)
|
|
43
|
-
return text("Enter value:");
|
|
44
|
-
write(`Invalid choice, try again.
|
|
45
|
-
`);
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
const multiSelect = async (message, choices, options) => {
|
|
49
|
-
const customIndex = listChoices(message, choices, options?.allowCustom);
|
|
50
|
-
const maxIndex = options?.allowCustom ? customIndex : choices.length;
|
|
51
|
-
write(`(comma-separated numbers, e.g. "1,3")
|
|
52
|
-
`);
|
|
53
|
-
while (true) {
|
|
54
|
-
const raw = await ask("Enter numbers: ");
|
|
55
|
-
const indices = raw.split(",").map((part) => Number(part.trim())).filter((n) => n !== 0 || raw.trim() === "0");
|
|
56
|
-
const valid = indices.length > 0 && indices.every((n) => Number.isInteger(n) && n >= 1 && n <= maxIndex);
|
|
57
|
-
if (!valid) {
|
|
58
|
-
write(`Invalid choices, try again.
|
|
59
|
-
`);
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const results = [];
|
|
63
|
-
for (const n of indices) {
|
|
64
|
-
results.push(n === customIndex ? await text("Enter value:") : choices[n - 1]);
|
|
65
|
-
}
|
|
66
|
-
return results;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
return { prompt: { text, confirm, select, multiSelect }, close: () => io.close?.() };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export { createPrompt2 };
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/context.ts
|
|
3
|
-
class RhythmCliResponse2 {
|
|
4
|
-
exitCode = 0;
|
|
5
|
-
stdout = [];
|
|
6
|
-
stderr = [];
|
|
7
|
-
print(line) {
|
|
8
|
-
this.stdout.push(line);
|
|
9
|
-
return this;
|
|
10
|
-
}
|
|
11
|
-
printError(line) {
|
|
12
|
-
this.stderr.push(line);
|
|
13
|
-
return this;
|
|
14
|
-
}
|
|
15
|
-
exit(code) {
|
|
16
|
-
this.exitCode = code;
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export { RhythmCliResponse2 };
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// src/argv.ts
|
|
3
|
-
function parseArgv2(argv) {
|
|
4
|
-
const positionals = [];
|
|
5
|
-
const flags = {};
|
|
6
|
-
let rawMode = false;
|
|
7
|
-
for (let i = 0;i < argv.length; i++) {
|
|
8
|
-
const token = argv[i];
|
|
9
|
-
if (rawMode) {
|
|
10
|
-
positionals.push(token);
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
if (token === "--") {
|
|
14
|
-
rawMode = true;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
if (token.startsWith("--")) {
|
|
18
|
-
const eqIndex = token.indexOf("=");
|
|
19
|
-
if (eqIndex !== -1) {
|
|
20
|
-
flags[token.slice(2, eqIndex)] = token.slice(eqIndex + 1);
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const name = token.slice(2);
|
|
24
|
-
const next = argv[i + 1];
|
|
25
|
-
if (next !== undefined && !next.startsWith("-")) {
|
|
26
|
-
flags[name] = next;
|
|
27
|
-
i++;
|
|
28
|
-
} else {
|
|
29
|
-
flags[name] = true;
|
|
30
|
-
}
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (token.startsWith("-") && token.length > 1) {
|
|
34
|
-
const name = token.slice(1);
|
|
35
|
-
const next = argv[i + 1];
|
|
36
|
-
if (next !== undefined && !next.startsWith("-")) {
|
|
37
|
-
flags[name] = next;
|
|
38
|
-
i++;
|
|
39
|
-
} else {
|
|
40
|
-
flags[name] = true;
|
|
41
|
-
}
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
positionals.push(token);
|
|
45
|
-
}
|
|
46
|
-
return { positionals, flags };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export { parseArgv2 };
|