@deepseek-ai/dsh-cmdline 0.0.1-rc.1 → 0.0.1-rc.3

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.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/boot/cmdline/README.md
5
- README.md: 98335e901bdf8fe33e14c1ad4c1a320d77f30c96
6
- README.zh.md: 28ea749943c60089c6b4725cb61e121f82aa0114
5
+ README.md: 33125014539e801dbd2952a3b4513cafc80bdcee
6
+ README.zh.md: 7ef49a1027d3c17817c9171e1166ed6feecd8559
package/README.md CHANGED
@@ -15,15 +15,16 @@ An embedding host with no command line provides an empty list; that is the hones
15
15
 
16
16
  ## Ordinary providers and injected config
17
17
 
18
- Any app plugin may inject `cmdlineArgs`, parse it, and publish an ordinary app-owned service. `parseCmdline(ctx, program, plan)` is only a commander adapter; the caller owns the returned value and service:
18
+ Any app plugin may inject `cmdlineArgs`, parse it, and publish an ordinary app-owned service. `parseCmdline(ctx, program)` is only a commander adapter; the program's own action owns validation and the published service:
19
19
 
20
20
  ```ts ignore
21
21
  export const name = 'web-startup'
22
22
  export const inject = ['cmdlineArgs']
23
23
 
24
24
  export function apply(ctx: Context): void {
25
- const values = parseCmdline(ctx, webCommand(), planWebStartup)
26
- if (values !== undefined) ctx.provide('webStartup', values)
25
+ const program = webCommand()
26
+ program.action(() => ctx.provide('webStartup', webValuesFrom(program)))
27
+ parseCmdline(ctx, program)
27
28
  }
28
29
  ```
29
30
 
@@ -45,14 +46,12 @@ Every row configured from those values uses ordinary service injection and direc
45
46
  port: !!js ctx.webStartup.port ?? 3080
46
47
  ```
47
48
 
48
- `parseCmdline` parses the immutable arguments and asks `plan` for the app-owned value. On `--help`, `--version`, a parse error, or a `program.error(...)` from the plan, it writes commander's text, requests exit, and returns `undefined`; the provider publishes nothing, so dependent rows never activate.
49
+ `parseCmdline` refuses at load a program in which no command declares an action, routes every command's exit and output through the launcher (commander copies those settings into subcommands only at registration), and parses the immutable arguments; commander runs the invoked command's synchronous action on success. An action rejects an invalid invocation with `program.error(...)` — before publishing, since statements ahead of the rejection have already run. On `--help`, `--version`, a parse error, or that rejection, the helper writes commander's text and requests exit; the provider publishes nothing, so dependent rows never activate.
49
50
 
50
51
  ### How injection orders config
51
52
 
52
53
  Loader defers a row's `!!js` interpolation until that row's declared injections are active, then evaluates against the row's plugin context. The example above can therefore read `ctx.webStartup` directly: Cordis has already populated that injected service before Loader asks for `webserver`'s config. Include trees preserve nested expression nodes until each target row reaches this point. Provider replacement and live patch reload repeat interpolation against the current injected services, so a launch flag cannot be silently reset.
53
54
 
54
- `enableRow(ctx, id)` turns on a row a bundle ships disabled because only some invocations want it (`dsh web --dev` and its client-plugin reload chain). The activation is an in-memory override: it does not rewrite the row's configured `disabled` value and survives config reapplication for that mounted entry. Loader applies the enabled row's ordinary injection ordering.
55
-
56
55
  ### Shared immutable arguments
57
56
 
58
57
  `get()` does not consume or mutate argv. Multiple plugins can parse the same snapshot and independently provide services. The launcher does not inspect the composition for a command-line owner; a profile with no reader simply ignores its app arguments.
package/README.zh.md CHANGED
@@ -15,15 +15,16 @@ dsh 启动器交给它所引导应用的那条命令行。启动器只解析属
15
15
 
16
16
  ## 普通提供方与注入配置
17
17
 
18
- 任何应用插件都可以注入 `cmdlineArgs`、解析它,再发布一个普通的应用自有服务。`parseCmdline(ctx, program, plan)` 只适配 commander;返回值与服务都归调用方持有:
18
+ 任何应用插件都可以注入 `cmdlineArgs`、解析它,再发布一个普通的应用自有服务。`parseCmdline(ctx, program)` 只适配 commander;校验与发布的服务都归 program 自己的 action 持有:
19
19
 
20
20
  ```ts ignore
21
21
  export const name = 'web-startup'
22
22
  export const inject = ['cmdlineArgs']
23
23
 
24
24
  export function apply(ctx: Context): void {
25
- const values = parseCmdline(ctx, webCommand(), planWebStartup)
26
- if (values !== undefined) ctx.provide('webStartup', values)
25
+ const program = webCommand()
26
+ program.action(() => ctx.provide('webStartup', webValuesFrom(program)))
27
+ parseCmdline(ctx, program)
27
28
  }
28
29
  ```
29
30
 
@@ -45,14 +46,12 @@ export function apply(ctx: Context): void {
45
46
  port: !!js ctx.webStartup.port ?? 3080
46
47
  ```
47
48
 
48
- `parseCmdline` 解析不可变参数,再向 `plan` 索取应用自有取值。遇到 `--help`、`--version`、解析错误,或 `plan` 发出的 `program.error(...)` 时,它输出 commander 文本、请求退出并返回 `undefined`;提供方什么也不发布,因此依赖行不会激活。
49
+ `parseCmdline` 在加载时拒绝整棵命令树中没有任何命令声明 action 的 program,把每个命令的退出与输出都接到启动器上(commander 只在注册时把这些设置复制进子命令),再解析不可变参数;解析成功时 commander 运行被调用命令的同步 action。action 用 `program.error(...)` 拒绝无效调用——必须先拒绝后发布,因为写在拒绝之前的语句已经执行。遇到 `--help`、`--version`、解析错误或这种拒绝时,该适配器输出 commander 文本并请求退出;提供方什么也不发布,因此依赖行不会激活。
49
50
 
50
51
  ### 注入如何排列配置求值
51
52
 
52
53
  Loader 会把一行的 `!!js` 插值推迟到该行声明的注入全部激活之后,再基于该行的插件上下文求值。所以上例可以直接读取 `ctx.webStartup`:Loader 索取 `webserver` 的配置之前,Cordis 已经填入了这个注入服务。Include 树会保留嵌套表达式节点,直到各个目标行到达这一时点。提供方替换与活动 patch 重载都会针对当前注入服务重新插值,因此启动 flag 不会被悄悄重置。
53
54
 
54
- `enableRow(ctx, id)` 打开某个组合包以禁用状态交付、只有部分调用才需要的行(`dsh web --dev` 及其客户端插件重载链路)。该激活是内存中的覆盖:它不会改写行所配置的 `disabled` 值,并会在已挂载条目的配置重新应用后继续生效。Loader 会对启用后的行应用普通的注入顺序。
55
-
56
55
  ### 共享不可变参数
57
56
 
58
57
  `get()` 不会消费或修改 argv。多个插件可以解析同一份快照,并分别提供服务。启动器不会检查组合中的命令行所有者;没有读取方的 profile 只会忽略自己的应用参数。
package/lib/index.js CHANGED
@@ -9,7 +9,8 @@
9
9
  * text, and its parse errors instead of the launcher knowing them.
10
10
  *
11
11
  * Any app plugin can inject `cmdlineArgs` and call {@link parseCmdline}. A
12
- * provider may publish the parsed values as its own service, and ordinary rows
12
+ * provider may publish the parsed values as its own service from its program's
13
+ * commander action, and ordinary rows
13
14
  * can inject that service and read it from lazily resolved config —
14
15
  * `port: !!js ctx.webStartup.port ?? 3080` — so a flag beats the value written
15
16
  * beside it. No row has launcher-level command-line status.
@@ -34,55 +35,65 @@ const internals = {
34
35
  };
35
36
  /**
36
37
  * Parse the launcher's immutable argument snapshot with an app's commander
37
- * program. The caller decides whether and how to publish the returned value;
38
- * this helper has no Loader-row or service ownership semantics.
38
+ * program. Commander runs the program's own synchronous action handler on a
39
+ * successful parse; app code there publishes its service and rejects an
40
+ * invalid invocation with `program.error(...)`. This helper has no Loader-row
41
+ * or service ownership semantics.
39
42
  *
40
- * Help, version, and rejected arguments are terminal for the process: commander
41
- * writes the text, the helper requests `ctx.appExit`, and it returns
42
- * `undefined` so the caller publishes nothing.
43
+ * Help, version, and rejected arguments — from the grammar or from an action
44
+ * — are terminal for the process: commander writes the text and the helper
45
+ * requests `ctx.appExit`. The action never runs on help, version, or a
46
+ * grammar rejection; an action must reject before it publishes, because
47
+ * statements before its `program.error(...)` have already run.
43
48
  * @param ctx - plugin context carrying `cmdlineArgs` and `appExit`.
44
- * @param program - the app's commander program, with its flags and description already declared.
45
- * @param plan - this invocation's resolved value; omitted returns an empty object.
46
- * @returns the resolved value, or `undefined` when the app asked to exit.
47
- * @throws when the launcher did not provide the command line and exit request.
49
+ * @param program - the app's commander program, with its flags, description,
50
+ * actions, and any subcommands already declared.
51
+ * @throws when the launcher did not provide the command line and exit request,
52
+ * or when no command in the program declares an action.
48
53
  */
49
- function parseCmdline(ctx, program, plan = (() => ({}))) {
54
+ function parseCmdline(ctx, program) {
50
55
  const args = ctx.get("cmdlineArgs");
51
56
  const exit = ctx.get("appExit");
52
57
  if (args === void 0 || exit === void 0) throw new Error(`${program.name()}: the launcher must provide ctx.cmdlineArgs and ctx.appExit before the tree mounts`);
53
- program.exitOverride().configureOutput({
54
- writeOut: (text) => void internals.stdout.write(text),
55
- writeErr: (text) => void internals.stderr.write(text)
56
- });
58
+ if (!hasAction(program)) throw new Error(`${program.name()}: no command in the program declares an action; parseCmdline runs the invoked command's action on a successful parse, and app code there publishes its service`);
59
+ configureExitAndOutput(program);
57
60
  try {
58
61
  program.parse(args.get(), { from: "user" });
59
- return plan(program, ctx);
60
62
  } catch (error) {
61
63
  if (!isCommanderError(error)) throw error;
62
64
  exit(error.exitCode);
63
- return;
64
65
  }
65
66
  }
66
67
  /**
67
- * Turn on a row this composition ships disabled, because this invocation asked
68
- * for it (`dsh web --dev` and its client-plugin reload chain).
68
+ * Whether any command in the tree declares an action handler.
69
+ *
70
+ * The `Command` type cannot express the action precondition, so the handler is
71
+ * read structurally (as {@link isCommanderError} reads commander's control-flow
72
+ * errors): without this guard, a program that forgot its action would parse
73
+ * successfully, publish nothing, and surface only as dependent rows pending on
74
+ * the absent service.
75
+ * @param command - the command whose tree is inspected.
76
+ * @returns true when the command or any registered subcommand has an action.
77
+ */
78
+ function hasAction(command) {
79
+ if (typeof command._actionHandler === "function") return true;
80
+ return command.commands.some(hasAction);
81
+ }
82
+ /**
83
+ * Route every command's exit and output through the launcher adapter.
69
84
  *
70
- * A row cannot be inserted from inside a mounting plugin — the Loader returns a
71
- * prefixed id it then fails to resolve — so a conditional row ships disabled
72
- * and a row mounted beside it enables it after startup resolves the invocation.
73
- * The Loader keeps that activation in memory, separate from serialized options,
74
- * so reapplying the composition cannot restore the invocation's row to disabled.
75
- * @param ctx - plugin context whose Loader tree carries the row.
76
- * @param id - the row id.
77
- * @returns nothing once the row has started or is waiting for its dependencies.
78
- * @throws when the Loader or named row is absent.
85
+ * Commander copies `exitOverride` and output configuration into a subcommand
86
+ * only at registration, so a root-only override would let an
87
+ * already-registered subcommand's rejection write to the process streams and
88
+ * call `process.exit` directly, bypassing `ctx.appExit`.
89
+ * @param command - the root of the command tree to configure.
79
90
  */
80
- async function enableRow(ctx, id) {
81
- const loader = ctx.get("loader");
82
- if (loader === void 0) throw new Error("dsh-cmdline: enabling a row requires the Loader service");
83
- const entry = [...loader.entries()].find((candidate) => candidate.options.id === id);
84
- if (entry === void 0) throw new Error(`dsh-cmdline: the composition has no ${JSON.stringify(id)} row to enable`);
85
- await entry.enableRuntime();
91
+ function configureExitAndOutput(command) {
92
+ command.exitOverride().configureOutput({
93
+ writeOut: (text) => void internals.stdout.write(text),
94
+ writeErr: (text) => void internals.stderr.write(text)
95
+ });
96
+ for (const child of command.commands) configureExitAndOutput(child);
86
97
  }
87
98
  /**
88
99
  * Whether a thrown value is commander's own control-flow error (help, version,
@@ -101,4 +112,4 @@ function isCommanderError(error) {
101
112
  return typeof candidate.code === "string" && candidate.code.startsWith("commander.") && typeof candidate.exitCode === "number";
102
113
  }
103
114
  //#endregion
104
- export { enableRow, internals, parseCmdline, provideCmdline };
115
+ export { internals, parseCmdline, provideCmdline };
@@ -8,7 +8,8 @@
8
8
  * text, and its parse errors instead of the launcher knowing them.
9
9
  *
10
10
  * Any app plugin can inject `cmdlineArgs` and call {@link parseCmdline}. A
11
- * provider may publish the parsed values as its own service, and ordinary rows
11
+ * provider may publish the parsed values as its own service from its program's
12
+ * commander action, and ordinary rows
12
13
  * can inject that service and read it from lazily resolved config —
13
14
  * `port: !!js ctx.webStartup.port ?? 3080` — so a flag beats the value written
14
15
  * beside it. No row has launcher-level command-line status.
@@ -68,43 +69,23 @@ export declare const internals: {
68
69
  write(chunk: string): unknown;
69
70
  };
70
71
  };
71
- /**
72
- * Resolve parsed arguments into an app-owned value. Call
73
- * `program.error(...)` to reject the invocation with a usage message instead
74
- * of throwing.
75
- * @param program - the parsed commander program.
76
- * @param ctx - the plugin context that received the command line.
77
- * @returns the value an ordinary provider plugin may publish.
78
- */
79
- export type CmdlinePlan<T = unknown> = (program: Command, ctx: Context) => T;
80
72
  /**
81
73
  * Parse the launcher's immutable argument snapshot with an app's commander
82
- * program. The caller decides whether and how to publish the returned value;
83
- * this helper has no Loader-row or service ownership semantics.
74
+ * program. Commander runs the program's own synchronous action handler on a
75
+ * successful parse; app code there publishes its service and rejects an
76
+ * invalid invocation with `program.error(...)`. This helper has no Loader-row
77
+ * or service ownership semantics.
84
78
  *
85
- * Help, version, and rejected arguments are terminal for the process: commander
86
- * writes the text, the helper requests `ctx.appExit`, and it returns
87
- * `undefined` so the caller publishes nothing.
79
+ * Help, version, and rejected arguments — from the grammar or from an action
80
+ * — are terminal for the process: commander writes the text and the helper
81
+ * requests `ctx.appExit`. The action never runs on help, version, or a
82
+ * grammar rejection; an action must reject before it publishes, because
83
+ * statements before its `program.error(...)` have already run.
88
84
  * @param ctx - plugin context carrying `cmdlineArgs` and `appExit`.
89
- * @param program - the app's commander program, with its flags and description already declared.
90
- * @param plan - this invocation's resolved value; omitted returns an empty object.
91
- * @returns the resolved value, or `undefined` when the app asked to exit.
92
- * @throws when the launcher did not provide the command line and exit request.
93
- */
94
- export declare function parseCmdline<T>(ctx: Context, program: Command, plan?: CmdlinePlan<T>): T | undefined;
95
- /**
96
- * Turn on a row this composition ships disabled, because this invocation asked
97
- * for it (`dsh web --dev` and its client-plugin reload chain).
98
- *
99
- * A row cannot be inserted from inside a mounting plugin — the Loader returns a
100
- * prefixed id it then fails to resolve — so a conditional row ships disabled
101
- * and a row mounted beside it enables it after startup resolves the invocation.
102
- * The Loader keeps that activation in memory, separate from serialized options,
103
- * so reapplying the composition cannot restore the invocation's row to disabled.
104
- * @param ctx - plugin context whose Loader tree carries the row.
105
- * @param id - the row id.
106
- * @returns nothing once the row has started or is waiting for its dependencies.
107
- * @throws when the Loader or named row is absent.
85
+ * @param program - the app's commander program, with its flags, description,
86
+ * actions, and any subcommands already declared.
87
+ * @throws when the launcher did not provide the command line and exit request,
88
+ * or when no command in the program declares an action.
108
89
  */
109
- export declare function enableRow(ctx: Context, id: string): Promise<void>;
90
+ export declare function parseCmdline(ctx: Context, program: Command): void;
110
91
  //# sourceMappingURL=index.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-cmdline",
3
3
  "description": "Immutable command-line handoff from a dsh launcher to any app plugin that injects cmdlineArgs",
4
- "version": "0.0.1-rc.1",
4
+ "version": "0.0.1-rc.3",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
@@ -32,15 +32,15 @@
32
32
  ],
33
33
  "license": "BSD-3-Clause",
34
34
  "peerDependencies": {
35
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
35
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
36
36
  "@deepseek-ai/cordis": "^4.0.1-rc.1",
37
- "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1"
37
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "commander": "^15.0.0",
41
41
  "@deepseek-ai/cordis-plugin-include": "^1.0.5-rc.1",
42
42
  "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
43
- "@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
43
+ "@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
44
44
  "@deepseek-ai/cordis": "^4.0.1-rc.1"
45
45
  }
46
46
  }