@rhythmjs/cli 0.0.10 → 0.0.12

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 CHANGED
@@ -1,16 +1,16 @@
1
1
  # @rhythmjs/cli
2
2
 
3
- CLI command routing on top of `@rhythmjs/rhythm`. `RhythmCli` matches commands against argv positional tokens with a simple linear scan (appropriate for the handful-to-dozens of commands a real CLI has), supports prefixes and nested command groups, and mounts flat into a parent `Rhythm` app via `.use(cli.commands())`, so an unmatched command correctly falls through to whatever's registered after it.
3
+ The command-line layer of Rhythm, the Bun-native backend framework: CLI command routing on top of the `@rhythmjs/rhythm` kernel. `RhythmCli` matches commands against argv positional tokens with a simple linear scan (appropriate for the handful-to-dozens of commands a real CLI has), supports prefixes and nested command groups, and mounts flat into a parent `Rhythm` app via `.use(cli.commands())`, so an unmatched command correctly falls through to whatever's registered after it.
4
4
 
5
- `RhythmCli` is not an app and does not extend `Rhythm` — it is a controller that compiles commands and middleware down to a single middleware (`.commands()`). It shares the core middleware contract (`compose`, `Middleware`, `next(extra)`), but has no `provide()` or `register()`, and it can't be served on its own: a `Rhythm` app is always the host that owns the lifecycle and the adapters.
5
+ `RhythmCli` is not an app and does not extend `Rhythm`; it is a controller that compiles commands and middleware down to a single middleware (`.commands()`). It shares the core middleware contract (`compose`, `Middleware`, `next(extra)`), but has no `provide()` or `register()`, and it can't be served on its own: a `Rhythm` app is always the host that owns the lifecycle.
6
6
 
7
7
  ## Example
8
8
 
9
9
  ```ts
10
10
  import { Rhythm } from "@rhythmjs/rhythm";
11
11
  import { RhythmCli } from "@rhythmjs/cli";
12
- import { toCliHandler } from "@rhythmjs/cli/adapters/bun";
13
- import type { RhythmCliContext } from "@rhythmjs/cli/adapters/context";
12
+ import { toCliHandler } from "@rhythmjs/cli/run";
13
+ import type { RhythmCliContext } from "@rhythmjs/cli/context";
14
14
 
15
15
  const cli = new RhythmCli().command("deploy :environment", (ctx) => {
16
16
  ctx.response.print(`deploying to ${ctx.args.environment}`);
@@ -24,28 +24,24 @@ A fuller runnable version, including nested command groups and interactive promp
24
24
 
25
25
  ## Concepts
26
26
 
27
- - **`ctx.response`** — `print(line)`, `printError(line)`, `exit(code)`, all chainable. Flushed to the console and returned as the process exit code by the adapter.
28
- - **`ctx.args`** — captured `:name` command tokens, added once a command matches.
29
- - **`ctx.flags`** — parsed `--foo` / `--foo=bar` / `-f` options. Schema-less: a bare `--foo` is boolean `true`, `--foo bar` takes the next token as its value unless the token itself looks like a flag — same default behavior as `minimist`.
30
- - **`ctx.stdin`** — a `ReadableStream`, or `null` when stdin is a TTY (nothing piped in).
31
- - **Interactive prompts** — `createPrompt()` gives `text()`/`confirm()`/`select()`/`multiSelect()` (the latter two support an `allowCustom` option that adds a "type your own" choice). Wire it in via a `.provide()` on the host `Rhythm` app — see the example.
32
- - **Prefixes compose across nesting** — a child cli mounted into a prefixed parent via `.use(child)` gets the parent's prefix segments joined onto every one of its commands, at any nesting depth. Mounting copies the child's commands and middleware at that moment; commands added to the child afterwards don't appear in the parent, and the child keeps working standalone.
33
- - **Registration order is execution order** — a `.use()` middleware wraps only the commands registered after it; commands registered before it are untouched, and a matched command that doesn't call `next()` returns without reaching anything registered later. An unmatched command falls through, entry by entry, to the outer `next()`.
34
- - **A cli is a controller, not a module** — it has no `provide()` or `register()`, and it cannot be `register()`ed into a `Rhythm` app either; `register()` composes `Rhythm` modules only. A cli mounts into an app exactly one way: koa-style, via `.use(cli.commands())`.
27
+ - **`ctx.response`**: `print(line)`, `printError(line)`, `exit(code)`, all chainable. Flushed to the console and returned as the process exit code by the adapter.
28
+ - **`ctx.args`**: captured `:name` command tokens, added once a command matches.
29
+ - **`ctx.flags`**: parsed `--foo` / `--foo=bar` / `-f` options. Schema-less: a bare `--foo` is boolean `true`, `--foo bar` takes the next token as its value unless the token itself looks like a flag, the same default behavior as `minimist`.
30
+ - **`ctx.stdin`**: a `ReadableStream`, or `null` when stdin is a TTY (nothing piped in).
31
+ - **Interactive prompts**: `createPrompt()` gives `text()`/`confirm()`/`select()`/`multiSelect()` (the latter two support an `allowCustom` option that adds a "type your own" choice). Wire it in via a `.provide()` on the host `Rhythm` app; see the example.
32
+ - **Prefixes compose across nesting**: a child cli mounted into a prefixed parent via `.use(child)` gets the parent's prefix segments joined onto every one of its commands, at any nesting depth. Mounting copies the child's commands and middleware at that moment; commands added to the child afterwards don't appear in the parent, and the child keeps working standalone.
33
+ - **Registration order is execution order**: a `.use()` middleware wraps only the commands registered after it; commands registered before it are untouched, and a matched command that doesn't call `next()` returns without reaching anything registered later. An unmatched command falls through, entry by entry, to the outer `next()`.
34
+ - **A cli is a controller, not a module**: it has no `provide()` or `register()`, and it cannot be `register()`ed into a `Rhythm` app either; `register()` composes `Rhythm` modules only. A cli mounts into an app exactly one way: koa-style, via `.use(cli.commands())`.
35
35
 
36
36
  ## API
37
37
 
38
- - `new RhythmCli(options?)` — `options.prefix` (space-separated, e.g. `"remote"`).
39
- - `.command(path, ...handlers)` — register a command; `path` is space-separated and may contain `:param` tokens (e.g. `"deploy :environment"`).
40
- - `.use(fn)` — plain middleware. `.use(child)` — mount a nested `RhythmCli` (prefixes compose).
41
- - `.commands()` — this CLI as a plain middleware, for mounting into a `Rhythm` app via `.use()`; the cli's only way onto a runtime. Note: mounting a _cli_ into a _cli_ must use `.use(child)`, not `.use(child.commands())` — an opaque middleware can't have the parent's prefix applied to its commands.
42
- - `toCliHandler(app)` — bridges a `Rhythm` app to `(argv: string[]) => Promise<number>`.
43
- - `createPrompt()` — a readline-backed `{ text, confirm, select, multiSelect }` prompt, for use inside a `.provide()` on the host app.
38
+ - `new RhythmCli(options?)`: `options.prefix` (space-separated, e.g. `"remote"`).
39
+ - `.command(path, ...handlers)`: register a command; `path` is space-separated and may contain `:param` tokens (e.g. `"deploy :environment"`).
40
+ - `.use(fn)`: plain middleware. `.use(child)`: mount a nested `RhythmCli` (prefixes compose).
41
+ - `.commands()`: this CLI as a plain middleware, for mounting into a `Rhythm` app via `.use()`; the cli's only way onto a runtime. Note: mounting a _cli_ into a _cli_ must use `.use(child)`, not `.use(child.commands())`, because an opaque middleware can't have the parent's prefix applied to its commands.
42
+ - `toCliHandler(app)`: bridges a `Rhythm` app to `(argv: string[]) => Promise<number>`.
43
+ - `createPrompt()`: a `{ text, confirm, select, multiSelect }` prompt reading lines through Bun's async-iterable `console`, for use inside a `.provide()` on the host app.
44
44
 
45
- ## Runtime adapters
45
+ ## Running on Bun
46
46
 
47
- `RhythmCli` itself is runtime-agnostic; only stdin access and the interactive prompt's I/O touch a specific runtime.
48
-
49
- - **`@rhythmjs/cli/adapters/bun`** — uses `Bun.stdin.stream()`, Bun's own native stdin API.
50
- - **`@rhythmjs/cli/adapters/node`** — the portable version: `process.stdin` + `node:stream`'s `Readable.toWeb()`, and `node:readline` for `createPrompt()`. No Bun-specific API at all.
51
- - **`@rhythmjs/cli/adapters/deno`** — re-exports the Node adapter unchanged. Deno's Node-compat layer implements `process.stdin`, `Readable.toWeb`/`fromWeb`, and `node:readline`, so the same code works correctly under Deno.
47
+ `@rhythmjs/cli/run` is the runtime half, coupled to Bun on purpose: `toCliHandler(app)` reads piped input through `Bun.stdin.stream()` (a TTY leaves `ctx.stdin` null), and `createPrompt()` reads answer lines through Bun's async-iterable `console`.
package/dist/argv.d.ts CHANGED
@@ -1,7 +1,5 @@
1
- //#region src/argv.d.ts
2
1
  export interface ParsedArgv {
3
- positionals: string[];
4
- flags: Record<string, string | boolean>;
2
+ positionals: string[];
3
+ flags: Record<string, string | boolean>;
5
4
  }
6
5
  export declare function parseArgv(argv: readonly string[]): ParsedArgv;
7
- //#endregion
package/dist/argv.js CHANGED
@@ -1,47 +1,7 @@
1
- //#region src/argv.ts
2
- function parseArgv(argv) {
3
- const positionals = [];
4
- const flags = {};
5
- let rawMode = false;
6
- for (let i = 0; i < argv.length; i++) {
7
- const token = argv[i];
8
- if (rawMode) {
9
- positionals.push(token);
10
- continue;
11
- }
12
- if (token === "--") {
13
- rawMode = true;
14
- continue;
15
- }
16
- if (token.startsWith("--")) {
17
- const eqIndex = token.indexOf("=");
18
- if (eqIndex !== -1) {
19
- flags[token.slice(2, eqIndex)] = token.slice(eqIndex + 1);
20
- continue;
21
- }
22
- const name = token.slice(2);
23
- const next = argv[i + 1];
24
- if (next !== void 0 && !next.startsWith("-")) {
25
- flags[name] = next;
26
- i++;
27
- } else flags[name] = true;
28
- continue;
29
- }
30
- if (token.startsWith("-") && token.length > 1) {
31
- const name = token.slice(1);
32
- const next = argv[i + 1];
33
- if (next !== void 0 && !next.startsWith("-")) {
34
- flags[name] = next;
35
- i++;
36
- } else flags[name] = true;
37
- continue;
38
- }
39
- positionals.push(token);
40
- }
41
- return {
42
- positionals,
43
- flags
44
- };
45
- }
46
- //#endregion
47
- export { parseArgv };
1
+ // @bun
2
+ import {
3
+ parseArgv2
4
+ } from "./rhythm-cli-qdf1cds9.js";
5
+ export {
6
+ parseArgv2 as parseArgv
7
+ };
@@ -0,0 +1,14 @@
1
+ export declare class RhythmCliResponse {
2
+ exitCode: number;
3
+ stdout: string[];
4
+ stderr: string[];
5
+ print(line: string): this;
6
+ printError(line: string): this;
7
+ exit(code: number): this;
8
+ }
9
+ export interface RhythmCliContext {
10
+ readonly argv: readonly string[];
11
+ readonly flags: Readonly<Record<string, string | boolean>>;
12
+ readonly stdin: ReadableStream<Uint8Array> | null;
13
+ readonly response: RhythmCliResponse;
14
+ }
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ RhythmCliResponse2
4
+ } from "./rhythm-cli-abdgd67x.js";
5
+ export {
6
+ RhythmCliResponse2 as RhythmCliResponse
7
+ };
package/dist/prompt.d.ts CHANGED
@@ -1,2 +1,23 @@
1
- import { n as RhythmPromptIO, r as createPrompt, t as RhythmPrompt } from "./prompt-DXqXd-xC.js";
2
- export { RhythmPrompt, RhythmPromptIO, createPrompt };
1
+ export interface RhythmPrompt {
2
+ text(message: string, options?: {
3
+ default?: string;
4
+ }): Promise<string>;
5
+ confirm(message: string, options?: {
6
+ default?: boolean;
7
+ }): Promise<boolean>;
8
+ select<T extends string>(message: string, choices: readonly T[], options?: {
9
+ allowCustom?: boolean;
10
+ }): Promise<T | string>;
11
+ multiSelect<T extends string>(message: string, choices: readonly T[], options?: {
12
+ allowCustom?: boolean;
13
+ }): Promise<(T | string)[]>;
14
+ }
15
+ export interface RhythmPromptIO {
16
+ ask(this: void, query: string): Promise<string>;
17
+ write(this: void, text: string): void;
18
+ close?(this: void): void;
19
+ }
20
+ export declare function createPrompt(io: RhythmPromptIO): {
21
+ prompt: RhythmPrompt;
22
+ close: () => void;
23
+ };
package/dist/prompt.js CHANGED
@@ -1,62 +1,7 @@
1
- //#region src/prompt.ts
2
- const CUSTOM_LABEL = "Other (type your own)";
3
- function createPrompt(io) {
4
- const { ask, write } = io;
5
- const text = async (message, options) => {
6
- const suffix = options?.default ? ` (${options.default})` : "";
7
- return (await ask(`${message}${suffix} `)).trim() || options?.default || "";
8
- };
9
- const confirm = async (message, options) => {
10
- const suffix = options?.default === void 0 ? "(y/n)" : options.default ? "(Y/n)" : "(y/N)";
11
- while (true) {
12
- const answer = (await ask(`${message} ${suffix} `)).trim().toLowerCase();
13
- if (!answer && options?.default !== void 0) return options.default;
14
- if (answer === "y" || answer === "yes") return true;
15
- if (answer === "n" || answer === "no") return false;
16
- write("Please answer y or n.\n");
17
- }
18
- };
19
- function listChoices(message, choices, allowCustom) {
20
- write(`${message}\n`);
21
- choices.forEach((choice, i) => write(` ${i + 1}) ${choice}\n`));
22
- const customIndex = allowCustom ? choices.length + 1 : -1;
23
- if (allowCustom) write(` ${customIndex}) ${CUSTOM_LABEL}\n`);
24
- return customIndex;
25
- }
26
- const select = async (message, choices, options) => {
27
- const customIndex = listChoices(message, choices, options?.allowCustom);
28
- while (true) {
29
- const index = Number(await ask("Enter number: "));
30
- if (Number.isInteger(index) && index >= 1 && index <= choices.length) return choices[index - 1];
31
- if (index === customIndex) return text("Enter value:");
32
- write("Invalid choice, try again.\n");
33
- }
34
- };
35
- const multiSelect = async (message, choices, options) => {
36
- const customIndex = listChoices(message, choices, options?.allowCustom);
37
- const maxIndex = options?.allowCustom ? customIndex : choices.length;
38
- write("(comma-separated numbers, e.g. \"1,3\")\n");
39
- while (true) {
40
- const raw = await ask("Enter numbers: ");
41
- const indices = raw.split(",").map((part) => Number(part.trim())).filter((n) => n !== 0 || raw.trim() === "0");
42
- if (!(indices.length > 0 && indices.every((n) => Number.isInteger(n) && n >= 1 && n <= maxIndex))) {
43
- write("Invalid choices, try again.\n");
44
- continue;
45
- }
46
- const results = [];
47
- for (const n of indices) results.push(n === customIndex ? await text("Enter value:") : choices[n - 1]);
48
- return results;
49
- }
50
- };
51
- return {
52
- prompt: {
53
- text,
54
- confirm,
55
- select,
56
- multiSelect
57
- },
58
- close: () => io.close?.()
59
- };
60
- }
61
- //#endregion
62
- export { createPrompt };
1
+ // @bun
2
+ import {
3
+ createPrompt2
4
+ } from "./rhythm-cli-28bavedc.js";
5
+ export {
6
+ createPrompt2 as createPrompt
7
+ };
@@ -0,0 +1,72 @@
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 };
@@ -0,0 +1,21 @@
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 };
@@ -0,0 +1,49 @@
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 };
@@ -1,19 +1,17 @@
1
- import { t as RhythmCliContext } from "./context-DyXPT9sP.js";
2
- import { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
3
- //#region src/rhythm-cli.d.ts
1
+ import type { DeriveMiddleware, Middleware } from "@rhythmjs/rhythm/types";
2
+ import type { RhythmCliContext } from "./context";
4
3
  export interface RhythmCliCommandContext {
5
- readonly args: Readonly<Record<string, string>>;
4
+ readonly args: Readonly<Record<string, string>>;
6
5
  }
7
6
  export interface RhythmCliOptions {
8
- prefix?: string;
7
+ prefix?: string;
9
8
  }
10
9
  export declare class RhythmCli<TContext extends RhythmCliContext = RhythmCliContext> {
11
- #private;
12
- constructor(options?: RhythmCliOptions);
13
- use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmCli<TContext & TExtra>;
14
- use(fn: Middleware<TContext>): this;
15
- command<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmCliCommandContext, TExtra>, ...handlers: Middleware<TContext & RhythmCliCommandContext & TExtra>[]): this;
16
- command(path: string, ...handlers: Middleware<TContext & RhythmCliCommandContext>[]): this;
17
- middleware(): Middleware<TContext>;
10
+ #private;
11
+ constructor(options?: RhythmCliOptions);
12
+ use<TExtra extends object>(fn: DeriveMiddleware<TContext, TExtra>): RhythmCli<TContext & TExtra>;
13
+ use(fn: Middleware<TContext>): this;
14
+ command<TExtra extends object>(path: string, middleware: DeriveMiddleware<TContext & RhythmCliCommandContext, TExtra>, ...handlers: Middleware<TContext & RhythmCliCommandContext & TExtra>[]): this;
15
+ command(path: string, ...handlers: Middleware<TContext & RhythmCliCommandContext>[]): this;
16
+ middleware(): Middleware<TContext>;
18
17
  }
19
- //#endregion
@@ -1,90 +1,90 @@
1
- import { parseArgv } from "./argv.js";
1
+ // @bun
2
+ import {
3
+ parseArgv2
4
+ } from "./rhythm-cli-qdf1cds9.js";
5
+
6
+ // src/rhythm-cli.ts
2
7
  import { compose } from "@rhythmjs/rhythm/compose";
3
- //#region src/rhythm-cli.ts
4
8
  function toSegments(command) {
5
- return command.trim().split(/\s+/).filter(Boolean);
9
+ return command.trim().split(/\s+/).filter(Boolean);
6
10
  }
7
11
  function matchCommand(pattern, positionals) {
8
- if (pattern.length !== positionals.length) return null;
9
- const params = {};
10
- for (let i = 0; i < pattern.length; i++) {
11
- const segment = pattern[i];
12
- const token = positionals[i];
13
- if (segment.startsWith(":")) params[segment.slice(1)] = token;
14
- else if (segment !== token) return null;
15
- }
16
- return params;
12
+ if (pattern.length !== positionals.length)
13
+ return null;
14
+ const params = {};
15
+ for (let i = 0;i < pattern.length; i++) {
16
+ const segment = pattern[i];
17
+ const token = positionals[i];
18
+ if (segment.startsWith(":")) {
19
+ params[segment.slice(1)] = token;
20
+ } else if (segment !== token) {
21
+ return null;
22
+ }
23
+ }
24
+ return params;
17
25
  }
18
- var RhythmCli = class {
19
- #options;
20
- #entries = [];
21
- constructor(options = {}) {
22
- this.#options = options;
23
- }
24
- get #prefixSegments() {
25
- return this.#options.prefix ? toSegments(this.#options.prefix) : [];
26
- }
27
- use(fn) {
28
- if (typeof fn !== "function") throw new TypeError("middleware must be a function!");
29
- this.#entries.push({
30
- kind: "middleware",
31
- fn
32
- });
33
- return this;
34
- }
35
- command(path, ...handlers) {
36
- this.#entries.push({
37
- kind: "command",
38
- segments: [...this.#prefixSegments, ...toSegments(path)],
39
- handlers
40
- });
41
- return this;
42
- }
43
- #compile() {
44
- const dispatchFor = (compiled) => {
45
- return async (ctx, next) => {
46
- const { positionals } = parseArgv(ctx.argv);
47
- for (const command of compiled) {
48
- const args = matchCommand(command.segments, positionals);
49
- if (!args) continue;
50
- await command.dispatch({
51
- ...ctx,
52
- args
53
- }, next);
54
- return;
55
- }
56
- await next();
57
- };
58
- };
59
- const stack = [];
60
- let i = 0;
61
- while (i < this.#entries.length) {
62
- const entry = this.#entries[i];
63
- if (entry.kind === "middleware") {
64
- stack.push(entry.fn);
65
- i++;
66
- continue;
67
- }
68
- const compiled = [];
69
- while (i < this.#entries.length) {
70
- const command = this.#entries[i];
71
- if (command.kind !== "command") break;
72
- compiled.push({
73
- segments: command.segments,
74
- dispatch: compose(command.handlers)
75
- });
76
- i++;
77
- }
78
- stack.push(dispatchFor(compiled));
79
- }
80
- return compose(stack);
81
- }
82
- middleware() {
83
- const fn = this.#compile();
84
- return async (ctx, next) => {
85
- await fn(ctx, next);
86
- };
87
- }
26
+
27
+ class RhythmCli {
28
+ #options;
29
+ #entries = [];
30
+ constructor(options = {}) {
31
+ this.#options = options;
32
+ }
33
+ get #prefixSegments() {
34
+ return this.#options.prefix ? toSegments(this.#options.prefix) : [];
35
+ }
36
+ use(fn) {
37
+ if (typeof fn !== "function")
38
+ throw new TypeError("middleware must be a function!");
39
+ this.#entries.push({ kind: "middleware", fn });
40
+ return this;
41
+ }
42
+ command(path, ...handlers) {
43
+ this.#entries.push({ kind: "command", segments: [...this.#prefixSegments, ...toSegments(path)], handlers });
44
+ return this;
45
+ }
46
+ #compile() {
47
+ const dispatchFor = (compiled) => {
48
+ return async (ctx, next) => {
49
+ const { positionals } = parseArgv2(ctx.argv);
50
+ for (const command of compiled) {
51
+ const args = matchCommand(command.segments, positionals);
52
+ if (!args)
53
+ continue;
54
+ await command.dispatch({ ...ctx, args }, next);
55
+ return;
56
+ }
57
+ await next();
58
+ };
59
+ };
60
+ const stack = [];
61
+ let i = 0;
62
+ while (i < this.#entries.length) {
63
+ const entry = this.#entries[i];
64
+ if (entry.kind === "middleware") {
65
+ stack.push(entry.fn);
66
+ i++;
67
+ continue;
68
+ }
69
+ const compiled = [];
70
+ while (i < this.#entries.length) {
71
+ const command = this.#entries[i];
72
+ if (command.kind !== "command")
73
+ break;
74
+ compiled.push({ segments: command.segments, dispatch: compose(command.handlers) });
75
+ i++;
76
+ }
77
+ stack.push(dispatchFor(compiled));
78
+ }
79
+ return compose(stack);
80
+ }
81
+ middleware() {
82
+ const fn = this.#compile();
83
+ return async (ctx, next) => {
84
+ await fn(ctx, next);
85
+ };
86
+ }
87
+ }
88
+ export {
89
+ RhythmCli
88
90
  };
89
- //#endregion
90
- export { RhythmCli };
package/dist/run.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { Rhythm } from "@rhythmjs/rhythm";
2
+ import { type RhythmPrompt } from "./prompt";
3
+ import { type RhythmCliContext } from "./context";
4
+ export declare function toCliHandler<TContext extends RhythmCliContext, TProviders extends object = {}>(app: Rhythm<RhythmCliContext, TContext, TProviders>): (argv: string[]) => Promise<number>;
5
+ export declare function createPrompt(): {
6
+ prompt: RhythmPrompt;
7
+ close: () => void;
8
+ };
package/dist/run.js ADDED
@@ -0,0 +1,48 @@
1
+ // @bun
2
+ import {
3
+ parseArgv2
4
+ } from "./rhythm-cli-qdf1cds9.js";
5
+ import {
6
+ createPrompt2
7
+ } from "./rhythm-cli-28bavedc.js";
8
+ import {
9
+ RhythmCliResponse2
10
+ } from "./rhythm-cli-abdgd67x.js";
11
+
12
+ // src/run.ts
13
+ function toCliHandler(app) {
14
+ const run = app.callback();
15
+ return async (argv) => {
16
+ const { flags } = parseArgv2(argv);
17
+ const stdin = process.stdin.isTTY ? null : Bun.stdin.stream();
18
+ const ctx = await run({ argv, flags, stdin, response: new RhythmCliResponse2 });
19
+ for (const line of ctx.response.stdout)
20
+ console.log(line);
21
+ for (const line of ctx.response.stderr)
22
+ console.error(line);
23
+ return ctx.response.exitCode;
24
+ };
25
+ }
26
+ function defaultPromptIO() {
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?.();
39
+ }
40
+ };
41
+ }
42
+ function createPrompt() {
43
+ return createPrompt2(defaultPromptIO());
44
+ }
45
+ export {
46
+ createPrompt,
47
+ toCliHandler
48
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rhythmjs/cli",
3
- "version": "0.0.10",
4
- "description": "Command-line argument parsing, prompts, and command routing for the Rhythm middleware kernel.",
3
+ "version": "0.0.12",
4
+ "description": "Command-line argument parsing, prompts, and command routing for the Rhythm middleware kernel, running on Bun.",
5
5
  "homepage": "https://rhythm.js.org/cli",
6
6
  "license": "ISC",
7
7
  "repository": {
@@ -27,21 +27,21 @@
27
27
  "types": "./dist/prompt.d.ts",
28
28
  "default": "./dist/prompt.js"
29
29
  },
30
- "./adapters/bun": {
31
- "types": "./dist/adapters/bun.d.ts",
32
- "default": "./dist/adapters/bun.js"
33
- },
34
- "./adapters/deno": {
35
- "types": "./dist/adapters/deno.d.ts",
36
- "default": "./dist/adapters/deno.js"
30
+ "./context": {
31
+ "types": "./dist/context.d.ts",
32
+ "default": "./dist/context.js"
37
33
  },
38
- "./adapters/node": {
39
- "types": "./dist/adapters/node.d.ts",
40
- "default": "./dist/adapters/node.js"
34
+ "./run": {
35
+ "types": "./dist/run.d.ts",
36
+ "default": "./dist/run.js"
41
37
  },
42
38
  "./adapters/context": {
43
- "types": "./dist/adapters/context.d.ts",
44
- "default": "./dist/adapters/context.js"
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
45
  },
46
46
  "./package.json": "./package.json"
47
47
  },
@@ -49,15 +49,19 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@rhythmjs/rhythm": "0.0.10"
52
+ "@rhythmjs/rhythm": "0.0.12"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/bun": "^1.4.2",
56
56
  "@types/node": "^26.6.3",
57
- "typescript": "^7.0.2",
58
- "vite-plus": "^1.0.0"
57
+ "typescript": "^7.0.2"
59
58
  },
60
59
  "engines": {
61
- "node": ">=20.19.0"
60
+ "bun": ">=1.2.0"
61
+ },
62
+ "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",
64
+ "typecheck": "tsc --noEmit",
65
+ "test": "bun test"
62
66
  }
63
67
  }
@@ -1,10 +0,0 @@
1
- import { t as RhythmPrompt } from "../prompt-DXqXd-xC.js";
2
- import { t as RhythmCliContext } from "../context-DyXPT9sP.js";
3
- import { Rhythm } from "@rhythmjs/rhythm";
4
- //#region src/adapters/bun.d.ts
5
- export declare function toCliHandler<TContext extends RhythmCliContext, TProviders extends object = {}>(app: Rhythm<RhythmCliContext, TContext, TProviders>): (argv: string[]) => Promise<number>;
6
- export declare function createPrompt(): {
7
- prompt: RhythmPrompt;
8
- close: () => void;
9
- };
10
- //#endregion
@@ -1,39 +0,0 @@
1
- import { parseArgv } from "../argv.js";
2
- import { createPrompt as createPrompt$1 } from "../prompt.js";
3
- import { RhythmCliResponse } from "./context.js";
4
- import * as readline from "node:readline";
5
- //#region src/adapters/bun.ts
6
- function toCliHandler(app) {
7
- const run = app.callback();
8
- return async (argv) => {
9
- const { flags } = parseArgv(argv);
10
- const stdin = process.stdin.isTTY ? null : Bun.stdin.stream();
11
- const ctx = await run({
12
- argv,
13
- flags,
14
- stdin,
15
- response: new RhythmCliResponse()
16
- });
17
- for (const line of ctx.response.stdout) console.log(line);
18
- for (const line of ctx.response.stderr) console.error(line);
19
- return ctx.response.exitCode;
20
- };
21
- }
22
- function defaultPromptIO(input = process.stdin, output = process.stdout) {
23
- const rl = readline.createInterface({
24
- input,
25
- output
26
- });
27
- return {
28
- ask: (query) => new Promise((resolve) => rl.question(query, resolve)),
29
- write: (text) => {
30
- output.write(text);
31
- },
32
- close: () => rl.close()
33
- };
34
- }
35
- function createPrompt() {
36
- return createPrompt$1(defaultPromptIO());
37
- }
38
- //#endregion
39
- export { createPrompt, toCliHandler };
@@ -1,2 +0,0 @@
1
- import { n as RhythmCliResponse, t as RhythmCliContext } from "../context-DyXPT9sP.js";
2
- export { RhythmCliContext, RhythmCliResponse };
@@ -1,20 +0,0 @@
1
- //#region src/adapters/context.ts
2
- var RhythmCliResponse = class {
3
- exitCode = 0;
4
- stdout = [];
5
- stderr = [];
6
- print(line) {
7
- this.stdout.push(line);
8
- return this;
9
- }
10
- printError(line) {
11
- this.stderr.push(line);
12
- return this;
13
- }
14
- exit(code) {
15
- this.exitCode = code;
16
- return this;
17
- }
18
- };
19
- //#endregion
20
- export { RhythmCliResponse };
@@ -1,2 +0,0 @@
1
- import { createPrompt, toCliHandler } from "./node.js";
2
- export { createPrompt, toCliHandler };
@@ -1,2 +0,0 @@
1
- import { createPrompt, toCliHandler } from "./node.js";
2
- export { createPrompt, toCliHandler };
@@ -1,10 +0,0 @@
1
- import { t as RhythmPrompt } from "../prompt-DXqXd-xC.js";
2
- import { t as RhythmCliContext } from "../context-DyXPT9sP.js";
3
- import { Rhythm } from "@rhythmjs/rhythm";
4
- //#region src/adapters/node.d.ts
5
- export declare function toCliHandler<TContext extends RhythmCliContext, TProviders extends object = {}>(app: Rhythm<RhythmCliContext, TContext, TProviders>): (argv: string[]) => Promise<number>;
6
- export declare function createPrompt(): {
7
- prompt: RhythmPrompt;
8
- close: () => void;
9
- };
10
- //#endregion
@@ -1,40 +0,0 @@
1
- import { parseArgv } from "../argv.js";
2
- import { createPrompt as createPrompt$1 } from "../prompt.js";
3
- import { RhythmCliResponse } from "./context.js";
4
- import * as readline from "node:readline";
5
- import { Readable } from "node:stream";
6
- //#region src/adapters/node.ts
7
- function toCliHandler(app) {
8
- const run = app.callback();
9
- return async (argv) => {
10
- const { flags } = parseArgv(argv);
11
- const stdin = process.stdin.isTTY ? null : Readable.toWeb(process.stdin);
12
- const ctx = await run({
13
- argv,
14
- flags,
15
- stdin,
16
- response: new RhythmCliResponse()
17
- });
18
- for (const line of ctx.response.stdout) console.log(line);
19
- for (const line of ctx.response.stderr) console.error(line);
20
- return ctx.response.exitCode;
21
- };
22
- }
23
- function defaultPromptIO(input = process.stdin, output = process.stdout) {
24
- const rl = readline.createInterface({
25
- input,
26
- output
27
- });
28
- return {
29
- ask: (query) => new Promise((resolve) => rl.question(query, resolve)),
30
- write: (text) => {
31
- output.write(text);
32
- },
33
- close: () => rl.close()
34
- };
35
- }
36
- function createPrompt() {
37
- return createPrompt$1(defaultPromptIO());
38
- }
39
- //#endregion
40
- export { createPrompt, toCliHandler };
@@ -1,17 +0,0 @@
1
- //#region src/adapters/context.d.ts
2
- declare class RhythmCliResponse {
3
- exitCode: number;
4
- stdout: string[];
5
- stderr: string[];
6
- print(line: string): this;
7
- printError(line: string): this;
8
- exit(code: number): this;
9
- }
10
- interface RhythmCliContext {
11
- readonly argv: readonly string[];
12
- readonly flags: Readonly<Record<string, string | boolean>>;
13
- readonly stdin: ReadableStream<Uint8Array> | null;
14
- readonly response: RhythmCliResponse;
15
- }
16
- //#endregion
17
- export { RhythmCliResponse as n, RhythmCliContext as t };
@@ -1,26 +0,0 @@
1
- //#region src/prompt.d.ts
2
- interface RhythmPrompt {
3
- text(message: string, options?: {
4
- default?: string;
5
- }): Promise<string>;
6
- confirm(message: string, options?: {
7
- default?: boolean;
8
- }): Promise<boolean>;
9
- select<T extends string>(message: string, choices: readonly T[], options?: {
10
- allowCustom?: boolean;
11
- }): Promise<T | string>;
12
- multiSelect<T extends string>(message: string, choices: readonly T[], options?: {
13
- allowCustom?: boolean;
14
- }): Promise<(T | string)[]>;
15
- }
16
- interface RhythmPromptIO {
17
- ask(this: void, query: string): Promise<string>;
18
- write(this: void, text: string): void;
19
- close?(this: void): void;
20
- }
21
- declare function createPrompt(io: RhythmPromptIO): {
22
- prompt: RhythmPrompt;
23
- close: () => void;
24
- };
25
- //#endregion
26
- export { RhythmPromptIO as n, createPrompt as r, RhythmPrompt as t };