Merge branch 'master' into claude/unified-environment-credentials-c8841a
Master removed the TUI package, the `meta` and `upgrade` subcommands, and `--config-replace`, and made raw `dsh` require a `--config` overlay. Resolved onto that shape: - Dropped this branch's TUI edits with the surface itself, including `tui.cordis.yml`, `runTui`, and the TUI keyless PTY smoke. - Dropped the `--config-replace` plumbing rather than reintroducing a flag master deliberately removed. The gap this branch fixed remains: `dsh -p` still could not name its composition, so it keeps `--config`. - Kept this branch's deletion of the personal `$DSH_HOME/config.yaml` layer, which master still carried, and provided the environment snapshot in the new raw `runConfig` surface alongside web and headless. - Ported the headless shutdown PTY test off the personal overlay onto a named `--config` file, which is what proves that flag now exists on `-p`.
This commit is contained in:
668 files changed
+10527
-30408
No files matched your search
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* AppCLIEntry — the pre-cordis boot glue the config-tree dsh surfaces share
|
||||
* (`dsh web` and `dsh -p`; the TUI composes dsh-app-boot directly).
|
||||
* (`dsh web` and `dsh -p`).
|
||||
* Everything here is what must exist before the Loader runs: the patch
|
||||
* composition over the shipped base and surface overlay (CLI flags + the
|
||||
* resolved frontend dist), and the fail-loud activation audit after the tree
|
||||
* settles. The environment is what the bin already loaded (ambient over the
|
||||
* invoking directory's `.env` over `$DSH_HOME/.env`); credentials live in
|
||||
* composition over the shipped base and Web overlay (CLI flags + the resolved
|
||||
* frontend dist), and the fail-loud activation audit after the tree settles.
|
||||
* The environment is what the bin already loaded (ambient over the invoking
|
||||
* directory's `.env` over `$DSH_HOME/.env`); credentials live in
|
||||
* `$DSH_HOME/.credentials.yaml` and are never hoisted into it.
|
||||
*/
|
||||
|
||||
@@ -80,7 +80,7 @@ export function resolveTelemetryPatch(disabledEnv: string | undefined, hasRow: b
|
||||
/**
|
||||
* Whether a config file carries the telemetry row, parsed under the same
|
||||
* `!!js`-tolerant dialect the boot uses — the `hasRow` input for launchers
|
||||
* that compose their patch lists outside {@link AppCLIEntry} (the TUI).
|
||||
* that compose their patch lists outside {@link AppCLIEntry} (raw `dsh`).
|
||||
* @param file - absolute path of the config or overlay file.
|
||||
* @returns true when a top-level (or inserted) row has the telemetry id.
|
||||
*/
|
||||
@@ -116,14 +116,6 @@ export interface AppCLIEntryOptions {
|
||||
overlayPath: string
|
||||
/** Optional `--config` overlay applied after {@link overlayPath} and before this entry's own flag patches. */
|
||||
extraOverlayPath?: string
|
||||
/**
|
||||
* Optional `--config-replace` tree: booted INSTEAD of {@link configPath},
|
||||
* {@link overlayPath}, {@link extraOverlayPath}, and every generated patch,
|
||||
* so the caller's file is the whole composition. It must still supply the
|
||||
* serving rows this entry needs — {@link run} rejects a settled tree with no
|
||||
* `httpServer`.
|
||||
*/
|
||||
configReplacePath?: string
|
||||
/** Whether to append client-bundle HMR (the Web surface's prod/dev difference). */
|
||||
dev: boolean
|
||||
/** --host when explicitly passed; undefined keeps the yml engineering default. */
|
||||
@@ -175,15 +167,8 @@ export class AppCLIEntry {
|
||||
await this.bootTree()
|
||||
this.assertBoot()
|
||||
const port = this.ctx.get('httpServer')?.port
|
||||
if (port === undefined) {
|
||||
// The shipped tree always carries the webserver row, so this is only
|
||||
// reachable through --config-replace: name the missing contract rather
|
||||
// than report a bare missing service.
|
||||
throw new Error(
|
||||
`dsh: no httpServer after booting ${this.bootConfigPath()}; this surface serves over HTTP, so a`
|
||||
+ ' --config-replace tree must mount a webserver row',
|
||||
)
|
||||
}
|
||||
/* v8 ignore next -- the sweep above guarantees an ACTIVE webserver row */
|
||||
if (port === undefined) throw new Error('dsh: httpServer service missing after settled boot')
|
||||
return { ctx: this.ctx, port }
|
||||
}
|
||||
|
||||
@@ -194,16 +179,6 @@ export class AppCLIEntry {
|
||||
*/
|
||||
private composePatches(): void {
|
||||
const rows = this.parseYmlRows()
|
||||
if (this.options.configReplacePath !== undefined) {
|
||||
// A replacement tree is the caller's whole composition: the generated
|
||||
// patches target shipped row ids this file cannot assume exist, and a
|
||||
// patch whose id is absent is a silent no-op rather than a diagnostic.
|
||||
// Telemetry stays, judged against the tree actually booting, because a
|
||||
// privacy switch that silently no-ops is worse than a loud one.
|
||||
const replaceTelemetry = resolveTelemetryPatch(process.env.DSH_TELEMETRY_DISABLED, rows.has(TELEMETRY_ROW_ID))
|
||||
this.patches = replaceTelemetry === undefined ? [] : [replaceTelemetry]
|
||||
return
|
||||
}
|
||||
const overrides = new Map<string, Record<string, unknown>>()
|
||||
const put = (entryId: string, key: string, value: unknown): void => {
|
||||
const bag = overrides.get(entryId) ?? {}
|
||||
@@ -247,17 +222,14 @@ export class AppCLIEntry {
|
||||
// list: patches never cross an include boundary, so nesting them would
|
||||
// silently stop reaching base rows. The surface overlay applies first, then
|
||||
// any --config overlay, then this entry's CLI-flag patches, which win.
|
||||
// --config-replace discards all three and boots the named file alone.
|
||||
const patches = this.options.configReplacePath !== undefined
|
||||
? this.patches
|
||||
: [
|
||||
...loadOverlayPatches('dsh', this.options.overlayPath),
|
||||
...this.options.extraOverlayPath === undefined
|
||||
? []
|
||||
: loadOverlayPatches('dsh', this.options.extraOverlayPath),
|
||||
...this.patches,
|
||||
]
|
||||
this.ctx = await boot('dsh', resolve(this.bootConfigPath()), patches, async (ctx) => {
|
||||
const patches = [
|
||||
...loadOverlayPatches('dsh', this.options.overlayPath),
|
||||
...this.options.extraOverlayPath === undefined
|
||||
? []
|
||||
: loadOverlayPatches('dsh', this.options.extraOverlayPath),
|
||||
...this.patches,
|
||||
]
|
||||
this.ctx = await boot('dsh', resolve(this.options.configPath), patches, async (ctx) => {
|
||||
// Before any config-tree entry mounts, so a plugin that resolves a
|
||||
// user-facing value at construction already sees this run's layers.
|
||||
ctx.provide(DSH_ENVIRONMENT_KEY, this.options.environment)
|
||||
@@ -266,10 +238,6 @@ export class AppCLIEntry {
|
||||
})
|
||||
}
|
||||
|
||||
/** The file the Loader includes: the replacement tree when named, otherwise the shared base. */
|
||||
private bootConfigPath(): string {
|
||||
return this.options.configReplacePath ?? this.options.configPath
|
||||
}
|
||||
|
||||
/** Install the diagnostic for plugin rejections that happen after settled boot. */
|
||||
private assertBoot(): void {
|
||||
@@ -284,17 +252,6 @@ export class AppCLIEntry {
|
||||
*/
|
||||
private parseYmlRows(): Map<string, { config?: unknown }> {
|
||||
const rows = new Map<string, { config?: unknown }>()
|
||||
// A replacement tree stands alone, so only its own rows are indexed —
|
||||
// the telemetry-row check must judge the tree that actually boots.
|
||||
if (this.options.configReplacePath !== undefined) {
|
||||
for (const row of this.parseRowList(this.options.configReplacePath)) {
|
||||
if (typeof row.id === 'string') rows.set(row.id, row)
|
||||
for (const inserted of row.insert ?? []) {
|
||||
if (typeof inserted.id === 'string') rows.set(inserted.id, inserted)
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
const files = [this.options.configPath, this.options.overlayPath]
|
||||
if (this.options.extraOverlayPath !== undefined) files.push(this.options.extraOverlayPath)
|
||||
for (const file of files) {
|
||||
|
||||
Reference in New Issue
Block a user