refactor(cmdline): make command providers ordinary
This commit is contained in:
@@ -13,30 +13,28 @@ A launcher calls `provideCmdline(ctx, host)` before any tree entry mounts, which
|
||||
|
||||
An embedding host with no command line provides an empty list; that is the honest answer, not a missing value.
|
||||
|
||||
## Startup rows, and the service their app reads
|
||||
## Ordinary providers and injected config
|
||||
|
||||
An app reads those arguments from its **startup row** — a Loader row and plugin that inject `cmdlineArgs` and calls `runStartup(ctx, service, program, plan)`:
|
||||
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:
|
||||
|
||||
```ts ignore
|
||||
export const name = 'web-startup'
|
||||
export const inject = ['cmdlineArgs']
|
||||
|
||||
export function apply(ctx: Context): void {
|
||||
runStartup(ctx, 'webStartup', webCommand(), planWebStartup)
|
||||
const values = parseCmdline(ctx, webCommand(), planWebStartup)
|
||||
if (values !== undefined) ctx.provide('webStartup', values)
|
||||
}
|
||||
```
|
||||
|
||||
The Loader-row injection is also its discovery declaration, so no bundle manifest field is needed:
|
||||
Its Loader row carries no launcher marker or special kind:
|
||||
|
||||
```yaml
|
||||
- id: web-startup
|
||||
name: '@deepseek-ai/dsh-web-app/startup'
|
||||
inject: [cmdlineArgs]
|
||||
```
|
||||
|
||||
The launcher uses that injection only to reject arguments for a composition with no command-line owner, and to reject a composition with multiple owners. Loader mounts the composition once and holds each row until its own injections are active.
|
||||
|
||||
Every row the app configures from flags then reads what the startup row resolved, naming the key it takes and the value it falls back to:
|
||||
Every row configured from those values uses ordinary service injection and direct lazy config access:
|
||||
|
||||
```yaml
|
||||
- id: webserver
|
||||
@@ -47,9 +45,7 @@ Every row the app configures from flags then reads what the startup row resolved
|
||||
port: !!js ctx.webStartup.port ?? 3080
|
||||
```
|
||||
|
||||
`runStartup` parses the arguments, asks `plan` for the values, and provides them as the service. On `--help`, `--version`, a parse error, or a `program.error(...)` from the plan, it writes commander's text and requests exit — nothing is provided, so rows that depend on the startup service never activate.
|
||||
|
||||
`plan` receives the startup context and the options of every row that injects the service, for a value that has to take the composition into account. Include still holds nested expressions raw at this point, so a plan that needs a composed fallback can interpolate the relevant row config against the pre-service startup context; the `/api` fence authorities are the shipped example.
|
||||
`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.
|
||||
|
||||
### How injection orders config
|
||||
|
||||
@@ -57,9 +53,9 @@ Loader defers a row's `!!js` interpolation until that row's declared injections
|
||||
|
||||
`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.
|
||||
|
||||
### One command line, one owner
|
||||
### Shared immutable arguments
|
||||
|
||||
A composition has exactly one command-line owner. An app that layers over another one disables the underlying startup row and provides every startup service its retained rows inject.
|
||||
`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.
|
||||
|
||||
An out-of-tree plugin brings its own commander copy, so commander's control-flow errors are detected structurally rather than by class identity; an identity check would rethrow a printed help as a fatal load failure.
|
||||
|
||||
@@ -74,5 +70,5 @@ None; this package neither assembles nor sends a provider request.
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
- **Launcher flags must precede app arguments.** The split is positional: the first token the launcher does not recognize starts the inner arguments, so `--patch` placed after an app flag belongs to the app. The launcher's parser consumes one `--`, so an app argument that must survive as a literal `--` needs `-- --`.
|
||||
- **A startup service has no declared owner.** Reading rows name it and a `cmdlineArgs` consumer provides it; nothing links those two injections statically, so a bundle that ships reading rows without its startup row fails at settlement (pending entries naming the service) rather than at load.
|
||||
- **An app-owned service has no statically declared provider.** Consumer rows name it through ordinary injection; a bundle that omits its provider fails at settlement with pending entries naming the service rather than at load.
|
||||
- **A user patch that replaces a row's whole `config` drops its expressions.** A flag beats the value written beside it, not a literal a user wrote in place of the expression; keeping the expression is what keeps the flag winning.
|
||||
|
||||
Reference in New Issue
Block a user