@stackstackstack/dsh-app-boot 0.1.5
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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +60 -0
- package/README.zh.md +60 -0
- package/lib/index.js +1215 -0
- package/lib/invariant.js +23 -0
- package/lib/types/index.d.ts +267 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/profile.d.ts +172 -0
- package/package.json +66 -0
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@stackstackstack/dsh-app-boot`.
|
|
4
|
+
* @module @stackstackstack/dsh-app-boot/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@stackstackstack/dsh-app-boot";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "app-boot-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: this presentation adapter owns no durable package-local event stream;
|
|
13
|
+
* boundary and replay tests cover its protocol mapping.
|
|
14
|
+
*/
|
|
15
|
+
const install = () => {};
|
|
16
|
+
/**
|
|
17
|
+
* Register this package's invariant companion.
|
|
18
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
19
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
20
|
+
*/
|
|
21
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
22
|
+
//#endregion
|
|
23
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared boot glue for the app bins (`dsh`, `dsh-acp-demo`): load the gitignored
|
|
3
|
+
* `.env`, install the fail-loud Loader guards, resolve the config path (snapshot-aware), load the
|
|
4
|
+
* optional user patch layers from the Harness home (`~/.dsh`), expose its path resolver to
|
|
5
|
+
* config expressions, and drive the Cordis Loader against a leaf `cordis.yml` until the tree settles.
|
|
6
|
+
* @module @stackstackstack/dsh-app-boot
|
|
7
|
+
*/
|
|
8
|
+
import { Context } from '@deepseek-ai/cordis';
|
|
9
|
+
import { type Entry } from '@deepseek-ai/cordis-plugin-loader';
|
|
10
|
+
import { type PatchOptions } from '@deepseek-ai/cordis-plugin-include';
|
|
11
|
+
import { dshHomePath } from '@stackstackstack/dsh-home-paths';
|
|
12
|
+
import { type LaunchEnvironmentSnapshot } from '@stackstackstack/dsh-launch-environment';
|
|
13
|
+
declare module '@deepseek-ai/cordis' {
|
|
14
|
+
interface Context {
|
|
15
|
+
/** Harness-home path resolver available to Loader `!!js` config expressions. */
|
|
16
|
+
dshHomePath?: typeof dshHomePath;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export { composeEntries, DEFAULT_PROFILE_BUNDLES, healProfilesModuleFallback, initProfile, loadProfile, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PROFILES_DIR, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest, type DshBundleManifest, type DshManifestSection, type DshProfileManifest, type Profile, type ProfileLayer, type ProfileManifest, } from './profile.ts';
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the config to boot. Replay swaps a `cordis.yml` basename for
|
|
22
|
+
* `cordis.snapshot.yml` in the same directory; every other mode keeps the path.
|
|
23
|
+
* @param configPath - the requested config path (absolute, or relative to `cwd`).
|
|
24
|
+
* @param snapshotMode - the bin's `$DSH_SNAPSHOT` value; only `'replay'` swaps the
|
|
25
|
+
* basename.
|
|
26
|
+
* @param cwd - the base a relative `configPath` resolves against.
|
|
27
|
+
* @returns the absolute path of the config to boot.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveConfigPath(configPath: string, snapshotMode: string | undefined, cwd?: string): string;
|
|
30
|
+
/**
|
|
31
|
+
* Load the optional gitignored `.env` from `dir`. Missing files fall back to the
|
|
32
|
+
* ambient environment; other read failures are reported through `warn`.
|
|
33
|
+
* @param binName - the diagnostic prefix on the warn line.
|
|
34
|
+
* @param dir - the directory whose `.env` to load.
|
|
35
|
+
* @param warn - sink for the one-line misconfiguration diagnostic.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadEnv(binName: string, dir?: string, warn?: (line: string) => void): void;
|
|
38
|
+
/**
|
|
39
|
+
* Load the product CLI's inherited > invoking-directory `.env` > Harness-home
|
|
40
|
+
* `.env` snapshot. The Harness home resolves before either file; both files
|
|
41
|
+
* are checked before either is applied, and accepted values are materialized
|
|
42
|
+
* without replacing inherited ones. The snapshot preserves which layer supplied each value.
|
|
43
|
+
* @param binName - the diagnostic prefix on the diagnostics.
|
|
44
|
+
* @param cwd - the invoking directory whose `.env` is the project layer.
|
|
45
|
+
* @param warn - sink for the one-line misconfiguration diagnostics.
|
|
46
|
+
* @returns this run's frozen environment snapshot.
|
|
47
|
+
* @throws when either file declares a bootstrap-only variable.
|
|
48
|
+
*/
|
|
49
|
+
export declare function loadLayeredEnv(binName: string, cwd?: string, warn?: (line: string) => void): LaunchEnvironmentSnapshot;
|
|
50
|
+
/** Options for live user patch-layer reconciliation. */
|
|
51
|
+
export interface UserPatchWatchOptions {
|
|
52
|
+
/** Diagnostic prefix used by {@link loadOptionalPatches}. */
|
|
53
|
+
binName: string;
|
|
54
|
+
/** Absolute path of the watched patch file (a profile's `cordis.patch.yml`). */
|
|
55
|
+
filename: string;
|
|
56
|
+
/**
|
|
57
|
+
* Compose the full patch list for a fresh user-layer generation —
|
|
58
|
+
* the same composition the app booted with, so a reload can interleave the
|
|
59
|
+
* new user patches between app-owned layers (bundle layers below,
|
|
60
|
+
* overlays above). Identity when omitted: the user layer
|
|
61
|
+
* is the whole patch list.
|
|
62
|
+
*/
|
|
63
|
+
compose?: (userPatches: PatchOptions[]) => PatchOptions[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Watch the user patch layer through Cordis HMR and transactionally reapply it to the boot include.
|
|
67
|
+
* @param ctx - settled app context containing the root Include and an active HMR service.
|
|
68
|
+
* @param options - diagnostic, file, and patch-composition inputs.
|
|
69
|
+
* @returns an asynchronous disposer after the exact-path watcher is ready.
|
|
70
|
+
* @throws when HMR or the root Include is absent, watcher setup fails, or initial path resolution fails.
|
|
71
|
+
*/
|
|
72
|
+
export declare function watchUserPatches(ctx: Context, options: UserPatchWatchOptions): Promise<() => Promise<void>>;
|
|
73
|
+
/**
|
|
74
|
+
* Load an optional patch-list file: a top-level YAML array of loader patch
|
|
75
|
+
* entries (`@deepseek-ai/cordis-plugin-include`'s `PatchOptions`): id-targeted config
|
|
76
|
+
* overrides and `insert` lists, with `!!js` expressions allowed. A missing
|
|
77
|
+
* file means "no layer"; an unreadable, unparsable, or non-array file throws —
|
|
78
|
+
* a present patch file that cannot apply is a misconfiguration and must fail
|
|
79
|
+
* loud at boot, never be silently skipped.
|
|
80
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
81
|
+
* @param file - absolute path of the patch file.
|
|
82
|
+
* @returns the parsed patches, or `undefined` when the file does not exist.
|
|
83
|
+
*/
|
|
84
|
+
export declare function loadOptionalPatches(binName: string, file: string): PatchOptions[] | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Load a required overlay patch list: a bundle's `cordis.patch.yml` or a
|
|
87
|
+
* `--patch <path>` overlay. Same file format as {@link loadOptionalPatches},
|
|
88
|
+
* but a missing file throws, because the caller named this file — its absence
|
|
89
|
+
* is a misconfiguration, not "no overlay".
|
|
90
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
91
|
+
* @param file - absolute path of the overlay file.
|
|
92
|
+
* @returns the parsed patch list.
|
|
93
|
+
*/
|
|
94
|
+
export declare function loadOverlayPatches(binName: string, file: string): PatchOptions[];
|
|
95
|
+
/** One overlay patch list with the source label printed in dump comments. */
|
|
96
|
+
export interface ConfigDumpLayer {
|
|
97
|
+
/** Source name shown in dump comments (a file basename or path). */
|
|
98
|
+
label: string;
|
|
99
|
+
/** The layer's patches, from {@link loadOverlayPatches} / {@link loadOptionalPatches}. */
|
|
100
|
+
patches: PatchOptions[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Compose the effective entry list exactly as `boot()` would mount it: parse
|
|
104
|
+
* the base config file with the include's entry-list dialect, apply every
|
|
105
|
+
* layer's patches as ONE flattened list through the include's own patch
|
|
106
|
+
* algorithm (`applyEntryPatches`) — the same single call `boot()` makes, so
|
|
107
|
+
* even patch-visibility corner cases (a later layer targeting a group child a
|
|
108
|
+
* plain config replacement introduced, which the single-pass id index never
|
|
109
|
+
* sees) compose identically — then render the result as YAML in the same
|
|
110
|
+
* dialect (`!!js` expressions print verbatim, unevaluated).
|
|
111
|
+
*
|
|
112
|
+
* Every run of rows from the same file and patch layers is preceded by a `# ==` comment
|
|
113
|
+
* naming the file that contributed the rows and any layers that patched them,
|
|
114
|
+
* so the output stays a loadable YAML document while showing which section
|
|
115
|
+
* comes from which file. The file and patch labels are derived from single-call prefix
|
|
116
|
+
* snapshots (base + layers 1..k), diffed positionally: the patch algorithm
|
|
117
|
+
* only rewrites rows in place or appends, so a top-level index identifies one
|
|
118
|
+
* row across snapshots, and a layer whose addition changes the row (config
|
|
119
|
+
* replacement, disable, group insert) is listed as having patched it.
|
|
120
|
+
*
|
|
121
|
+
* A patch that matches no row is reported through `warn` with its layer
|
|
122
|
+
* label, mirroring the Loader's boot-time warning. Earlier layers' patches
|
|
123
|
+
* see an identical preceding state in every snapshot that includes them, so
|
|
124
|
+
* each snapshot's warning list extends the previous one and the new tail
|
|
125
|
+
* belongs to the added layer.
|
|
126
|
+
* @param binName - the diagnostic prefix on read/parse errors.
|
|
127
|
+
* @param absoluteConfigPath - the base config file `boot()` would include.
|
|
128
|
+
* @param layers - overlay layers in application order (later wins).
|
|
129
|
+
* @param warn - sink for skipped-patch diagnostics; defaults to stderr.
|
|
130
|
+
* @returns the composed entry list rendered as a YAML document with
|
|
131
|
+
* source comment separators.
|
|
132
|
+
*/
|
|
133
|
+
export declare function renderConfigDump(binName: string, absoluteConfigPath: string, layers: ConfigDumpLayer[], warn?: (line: string) => void): string;
|
|
134
|
+
/**
|
|
135
|
+
* Mount and remember the exact root Include entry used by app boot and user patch-layer HMR.
|
|
136
|
+
* @param ctx - context carrying an initialized Loader service.
|
|
137
|
+
* @param absoluteConfigPath - absolute YAML or JSON configuration path.
|
|
138
|
+
* @param patches - initial app and user patches, applied in order.
|
|
139
|
+
* @param bareModuleBaseUrl - optional installed-host base for bare package
|
|
140
|
+
* names; relative names continue to resolve beside the configuration file.
|
|
141
|
+
* @returns the created root Include entry, or `undefined` when a surface
|
|
142
|
+
* disposed the whole tree (taking the Loader service with it) while the
|
|
143
|
+
* transactional create was still settling entry lifecycle.
|
|
144
|
+
*/
|
|
145
|
+
export declare function mountRootInclude(ctx: Context, absoluteConfigPath: string, patches?: readonly PatchOptions[], bareModuleBaseUrl?: string): Promise<Entry | undefined>;
|
|
146
|
+
/**
|
|
147
|
+
* The slice of `process` {@link installFailLoud} needs — injectable so tests
|
|
148
|
+
* exercise the handler without registering on (or exiting) the real process.
|
|
149
|
+
*/
|
|
150
|
+
export interface FailLoudProcess {
|
|
151
|
+
on(event: 'unhandledRejection', handler: (err: unknown) => void): unknown;
|
|
152
|
+
off(event: 'unhandledRejection', handler: (err: unknown) => void): unknown;
|
|
153
|
+
stderr: {
|
|
154
|
+
write(chunk: string): unknown;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Terminate the process. Callers treat this as the end of the run, as
|
|
158
|
+
* `process.exit` is; a fake that returns lets the caller continue, which only
|
|
159
|
+
* a test observes.
|
|
160
|
+
*/
|
|
161
|
+
exit(code: number): void;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* How long {@link installFailLoud} waits for its `release` hook before exiting
|
|
165
|
+
* anyway. A wedged disposer must delay the fatal exit, never cancel it.
|
|
166
|
+
*/
|
|
167
|
+
export declare const FAIL_LOUD_RELEASE_TIMEOUT_MS = 2000;
|
|
168
|
+
/**
|
|
169
|
+
* Install before boot to turn a late unhandled plugin-init rejection into one
|
|
170
|
+
* labelled stderr diagnostic and `exit(1)`. A rejection already included by
|
|
171
|
+
* {@link assertEntriesActivated} is ignored during its process checkpoint;
|
|
172
|
+
* every other rejection remains fatal. Stdout remains untouched for ACP; the
|
|
173
|
+
* returned function removes the handler.
|
|
174
|
+
*
|
|
175
|
+
* The Loader mounts entries concurrently, so a surface that owns the terminal
|
|
176
|
+
* can already hold it when a sibling entry rejects. Exiting straight from the
|
|
177
|
+
* handler would strand raw mode, bracketed paste, and the keyboard protocol on
|
|
178
|
+
* the user's shell, and leave an in-flight terminal query's reply to land as
|
|
179
|
+
* literal text at the next prompt. `release` is the terminal owner's chance to
|
|
180
|
+
* hand it back; it is awaited under {@link FAIL_LOUD_RELEASE_TIMEOUT_MS}, whose
|
|
181
|
+
* timer stays referenced so a never-settling disposer cannot let Node reach an
|
|
182
|
+
* empty event loop and exit 0 instead of failing.
|
|
183
|
+
*
|
|
184
|
+
* The diagnostic is written before the release so a hanging or failing disposer
|
|
185
|
+
* cannot swallow the reason. The handler stays installed while the release runs
|
|
186
|
+
* — removing it would let a second concurrent rejection become uncaught and kill
|
|
187
|
+
* the process mid-teardown, stranding exactly the terminal state this restores —
|
|
188
|
+
* so a latch keeps the first rejection the reported one and lets later
|
|
189
|
+
* rejections (including the release's own) fall through to the pending exit.
|
|
190
|
+
* @param binName - the diagnostic prefix on the fatal-failure line.
|
|
191
|
+
* @param proc - the process slice to register on; tests inject a fake.
|
|
192
|
+
* @param release - optional teardown awaited before exit, used by a
|
|
193
|
+
* terminal-owning surface to restore the terminal. Its own failure is
|
|
194
|
+
* swallowed because the pending fatal exit already owns the outcome.
|
|
195
|
+
* @returns the uninstaller that removes the rejection handler.
|
|
196
|
+
*/
|
|
197
|
+
export declare function installFailLoud(binName: string, proc?: FailLoudProcess, release?: () => Promise<void> | void): () => void;
|
|
198
|
+
/**
|
|
199
|
+
* After the tree settles, reject entries with no fiber and name every plugin
|
|
200
|
+
* whose module failed to resolve. Disabled entries are the only valid
|
|
201
|
+
* fiber-less state.
|
|
202
|
+
* @param ctx - the settled context whose loader entries to audit.
|
|
203
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
204
|
+
*/
|
|
205
|
+
export declare function assertEntriesLoaded(ctx: Context, binName: string): void;
|
|
206
|
+
/**
|
|
207
|
+
* Reject a settled Loader tree when an enabled entry failed or remains inactive.
|
|
208
|
+
* Plugin failures include the original thrown stack; pending entries name their
|
|
209
|
+
* unresolved services because no plugin error exists for that state. Active
|
|
210
|
+
* entries require no further wait; only failed fibers are awaited to recover
|
|
211
|
+
* their private rejection reason.
|
|
212
|
+
* @param ctx - the settled context whose Loader entries to audit.
|
|
213
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
214
|
+
* @returns nothing when every enabled entry is active.
|
|
215
|
+
* @throws after one process rejection checkpoint when an entry failed to
|
|
216
|
+
* import, rejected during activation, or did not become active.
|
|
217
|
+
*/
|
|
218
|
+
export declare function assertEntriesActivated(ctx: Context, binName: string): Promise<void>;
|
|
219
|
+
/**
|
|
220
|
+
* Boot the Loader against `absoluteConfigPath` and return only after the whole
|
|
221
|
+
* tree settles. Relative entry names resolve against the config directory;
|
|
222
|
+
* bare package names resolve there by default or against an explicit
|
|
223
|
+
* `bareModuleBaseUrl` for closed packaged runtimes. The bootstrap include
|
|
224
|
+
* is statically imported and mounted as the `cordis:include` builtin, loading
|
|
225
|
+
* through the ambient module pipeline (vite/tsx/plain ESM). The package build
|
|
226
|
+
* embeds Include while leaving Loader external, so the built include tree and
|
|
227
|
+
* host share one Loader peer. Loader
|
|
228
|
+
* settlement rejects startup failures, which `boot` wraps after disposing the
|
|
229
|
+
* partial context; a missing fiber or never-activating entry is rejected by
|
|
230
|
+
* the final audit, {@link assertEntriesActivated}, which rethrows a plugin's
|
|
231
|
+
* init rejection with its original stack; later unhandled rejections remain
|
|
232
|
+
* covered by {@link installFailLoud}. Built bins need the Loader's native
|
|
233
|
+
* helper for bare plugin specifiers; relative specifiers do not.
|
|
234
|
+
* @param binName - the diagnostic prefix for load-failure errors.
|
|
235
|
+
* @param absoluteConfigPath - the config to include; must already be absolute
|
|
236
|
+
* (see {@link resolveConfigPath}).
|
|
237
|
+
* @param patches - optional overlay patches applied over the included tree
|
|
238
|
+
* (see {@link loadOptionalPatches}); an empty list mounts none.
|
|
239
|
+
* @param prepare - optional host setup run after Loader installation and before any config-tree entry mounts.
|
|
240
|
+
* @param bareModuleBaseUrl - optional installed-host base for bare package
|
|
241
|
+
* names; use it when the host, rather than the configuration project, owns the
|
|
242
|
+
* complete plugin set.
|
|
243
|
+
* @returns the root context once every entry has started, or as soon as a
|
|
244
|
+
* surface disposed the tree while startup was still in flight.
|
|
245
|
+
* @throws a labelled error after disposing the partial context — `host
|
|
246
|
+
* preparation failed` when `prepare` threw before any config-tree entry
|
|
247
|
+
* mounted, `plugin tree failed to load` afterwards.
|
|
248
|
+
*/
|
|
249
|
+
export declare function boot(binName: string, absoluteConfigPath: string, patches?: PatchOptions[], prepare?: (ctx: Context) => Promise<void> | void, bareModuleBaseUrl?: string): Promise<Context>;
|
|
250
|
+
/** Prompt-section name for the harness-source location line an app bin adds after boot. */
|
|
251
|
+
export declare const HARNESS_SOURCE_SECTION = "harness:source";
|
|
252
|
+
/**
|
|
253
|
+
* Add a global prompt section naming the on-disk harness source checkout while
|
|
254
|
+
* explicitly distinguishing it from the task workspace and current working
|
|
255
|
+
* directory. The self-referential `dsh-tool-cordis` toolset reads and edits this
|
|
256
|
+
* checkout. Call once on the settled boot context ({@link boot}); the section
|
|
257
|
+
* orders just after the harness identity opener (`-100`) and before the deployment
|
|
258
|
+
* persona (`0`). A booted tree with no `systemPrompt` service has no prompt to
|
|
259
|
+
* augment, so this is then a no-op that returns `undefined`. The section is
|
|
260
|
+
* registered against the `systemPrompt` service's fiber, so a dev HMR reload of
|
|
261
|
+
* that plugin drops it until the next boot.
|
|
262
|
+
* @param ctx - the settled boot context whose global system prompt to augment.
|
|
263
|
+
* @param sourceRoot - the absolute path to the harness checkout root.
|
|
264
|
+
* @returns the section disposer, or `undefined` when no `systemPrompt` service is mounted.
|
|
265
|
+
*/
|
|
266
|
+
export declare function addHarnessSourceSection(ctx: Context, sourceRoot: string): (() => void) | undefined;
|
|
267
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@stackstackstack/dsh-app-boot`.
|
|
3
|
+
* @module @stackstackstack/dsh-app-boot/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "app-boot-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile discovery, initialization, and patch-layer composition for the
|
|
3
|
+
* `dsh --profile` launcher family.
|
|
4
|
+
*
|
|
5
|
+
* A profile is a directory under `$DSH_HOME/profiles/<name>` holding a
|
|
6
|
+
* `package.json` (out-of-tree plugin dependencies plus the profile manifest
|
|
7
|
+
* `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml`
|
|
8
|
+
* (the user's own patch layer, applied after every bundle layer). Bundles are
|
|
9
|
+
* npm packages whose manifest declares
|
|
10
|
+
* `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`; the tree is
|
|
11
|
+
* composed by applying each bundle's patch list in `dsh.profile.bundles` order over
|
|
12
|
+
* an empty entry list, then the profile's own patches, then any launcher
|
|
13
|
+
* layers (`--patch` files and flag-derived patches).
|
|
14
|
+
*
|
|
15
|
+
* Module resolution is two-anchor by construction: a bundle name resolves
|
|
16
|
+
* first from the dsh installation (the launcher's own package), then from the
|
|
17
|
+
* profile directory. The Loader's `baseUrl` is the profile directory, whose
|
|
18
|
+
* `node_modules` pnpm manages for out-of-tree plugins, while the maintained
|
|
19
|
+
* flat fallback directory `$DSH_HOME/profiles/node_modules` (one symlink per
|
|
20
|
+
* package the installation's app and bundles depend on) makes every in-box
|
|
21
|
+
* plugin Node-resolvable from any profile through the ordinary parent-walk.
|
|
22
|
+
* @module @stackstackstack/dsh-app-boot/profile
|
|
23
|
+
*/
|
|
24
|
+
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader';
|
|
25
|
+
import { type PatchOptions } from '@deepseek-ai/cordis-plugin-include';
|
|
26
|
+
/** Directory under the Harness home holding every profile. */
|
|
27
|
+
export declare const PROFILES_DIR = "profiles";
|
|
28
|
+
/** The user patch layer inside a profile directory (hot-reloaded on long-lived surfaces). */
|
|
29
|
+
export declare const PROFILE_PATCH_FILENAME = "cordis.patch.yml";
|
|
30
|
+
/** The bundle half of the `dsh` manifest section: what a bundle package exports. */
|
|
31
|
+
export interface DshBundleManifest {
|
|
32
|
+
/** The patch layer this bundle exports, relative to its package root. */
|
|
33
|
+
patch: string;
|
|
34
|
+
}
|
|
35
|
+
/** The profile half of the `dsh` manifest section: what a profile directory composes. */
|
|
36
|
+
export interface DshProfileManifest {
|
|
37
|
+
/** Ordered bundle layer list (package names). */
|
|
38
|
+
bundles?: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The profile-launcher slice of the `dsh`-owned package.json section. A
|
|
42
|
+
* manifest may declare both roles; other consumers own additional keys.
|
|
43
|
+
*/
|
|
44
|
+
export interface DshManifestSection {
|
|
45
|
+
/** Bundle metadata consumed by the profile launcher. */
|
|
46
|
+
bundle?: DshBundleManifest;
|
|
47
|
+
/** Profile metadata consumed by the profile launcher. */
|
|
48
|
+
profile?: DshProfileManifest;
|
|
49
|
+
}
|
|
50
|
+
/** The slice of package.json both profiles and bundles use. */
|
|
51
|
+
export interface ProfileManifest {
|
|
52
|
+
name?: string;
|
|
53
|
+
dependencies?: Record<string, string>;
|
|
54
|
+
peerDependencies?: Record<string, string>;
|
|
55
|
+
dsh?: DshManifestSection;
|
|
56
|
+
}
|
|
57
|
+
/** One resolved bundle layer of a profile. */
|
|
58
|
+
export interface ProfileLayer {
|
|
59
|
+
/** The bundle's package name, as listed in `dsh.profile.bundles`. */
|
|
60
|
+
packageName: string;
|
|
61
|
+
/** Absolute directory of the resolved bundle package. */
|
|
62
|
+
packageDir: string;
|
|
63
|
+
/** Absolute path of the bundle's patch file. */
|
|
64
|
+
patchPath: string;
|
|
65
|
+
/** The parsed patch list. */
|
|
66
|
+
patches: PatchOptions[];
|
|
67
|
+
}
|
|
68
|
+
/** A loaded profile: resolved bundle layers plus the user's own patch layer. */
|
|
69
|
+
export interface Profile {
|
|
70
|
+
/** The profile name (its directory basename). */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Absolute profile directory. */
|
|
73
|
+
dir: string;
|
|
74
|
+
/** Bundle layers in `dsh.profile.bundles` order. */
|
|
75
|
+
layers: ProfileLayer[];
|
|
76
|
+
/** Absolute path of the profile's own patch file. */
|
|
77
|
+
patchPath: string;
|
|
78
|
+
/** The profile's own patches; empty when the file is absent. */
|
|
79
|
+
patches: PatchOptions[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolve a profile's directory under the Harness home.
|
|
83
|
+
* @param name - the profile name (`dsh --profile <name>`).
|
|
84
|
+
* @param home - the Harness home; defaults to {@link resolveDshHome}.
|
|
85
|
+
* @returns the absolute profile directory (which may not exist yet).
|
|
86
|
+
*/
|
|
87
|
+
export declare function resolveProfileDir(name: string, home?: string): string;
|
|
88
|
+
/** The shipped profile templates auto-initialized on first use, by name. */
|
|
89
|
+
export declare const PROFILE_TEMPLATES: Record<string, readonly string[]>;
|
|
90
|
+
/** The bundle list a `dsh plugin` init uses for a name with no shipped template. */
|
|
91
|
+
export declare const DEFAULT_PROFILE_BUNDLES: readonly string[];
|
|
92
|
+
/**
|
|
93
|
+
* Initialize a profile directory: manifest, empty user patch layer, and the
|
|
94
|
+
* pnpm settings out-of-tree plugins need. Existing files are never touched,
|
|
95
|
+
* so re-running is a no-op on an initialized profile.
|
|
96
|
+
* @param dir - the profile directory from {@link resolveProfileDir}.
|
|
97
|
+
* @param bundles - the initial `dsh.profile.bundles` layer list.
|
|
98
|
+
*/
|
|
99
|
+
export declare function initProfile(dir: string, bundles: readonly string[]): void;
|
|
100
|
+
/**
|
|
101
|
+
* Maintain the flat module fallback `$DSH_HOME/profiles/node_modules`: one
|
|
102
|
+
* symlink per package in the dsh app's resolvable dependency CLOSURE (BFS
|
|
103
|
+
* over `dependencies` from the app manifest), each resolved from its own
|
|
104
|
+
* real location. Node's parent-directory walk from any profile finds this
|
|
105
|
+
* directory after the profile's own `node_modules`, so every in-box plugin
|
|
106
|
+
* resolves without pnpm ever managing it — the exact "bundles come from the
|
|
107
|
+
* installation" contract. The closure (not just direct dependencies) is
|
|
108
|
+
* required for out-of-tree plugins: their peer dependencies name Service
|
|
109
|
+
* Definition packages (`dsh-compaction`, `dsh-invariants`, ...) that the app
|
|
110
|
+
* reaches only through its Service Provider packages. Symlinked packages
|
|
111
|
+
* resolve their own dependencies from their real directories (Node's default
|
|
112
|
+
* symlink-following), so each package needs only its one flat link.
|
|
113
|
+
* Idempotent: correct links are kept and moved installations are
|
|
114
|
+
* re-pointed; a stale link to a vanished package stays until its name is
|
|
115
|
+
* reused (dangling links are invisible to resolution).
|
|
116
|
+
* @param installAnchor - absolute path of the dsh app's package.json.
|
|
117
|
+
* @param home - the Harness home; defaults to {@link resolveDshHome}.
|
|
118
|
+
*/
|
|
119
|
+
export declare function healProfilesModuleFallback(installAnchor: string, home?: string): void;
|
|
120
|
+
/**
|
|
121
|
+
* Read a profile's manifest.
|
|
122
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
123
|
+
* @param dir - the profile directory.
|
|
124
|
+
* @returns the parsed manifest.
|
|
125
|
+
*/
|
|
126
|
+
export declare function readProfileManifest(binName: string, dir: string): ProfileManifest;
|
|
127
|
+
/**
|
|
128
|
+
* Write a profile's manifest back (2-space JSON, trailing newline).
|
|
129
|
+
* @param dir - the profile directory.
|
|
130
|
+
* @param manifest - the manifest value to persist.
|
|
131
|
+
*/
|
|
132
|
+
export declare function writeProfileManifest(dir: string, manifest: ProfileManifest): void;
|
|
133
|
+
/**
|
|
134
|
+
* Resolve one bundle package's directory: installation anchor first, then the
|
|
135
|
+
* profile directory. The installation-first order is the contract that
|
|
136
|
+
* `@stackstackstack/dsh-base` (and every other in-box bundle) always comes from
|
|
137
|
+
* the same installation as the running dsh, never from a profile-local copy.
|
|
138
|
+
* Resolution does not require the package to export `./package.json`.
|
|
139
|
+
* @param binName - the diagnostic prefix on the thrown error.
|
|
140
|
+
* @param packageName - the bundle's package name from `dsh.profile.bundles`.
|
|
141
|
+
* @param installAnchor - absolute path of a file inside the dsh app package (its package.json).
|
|
142
|
+
* @param profileDir - the profile directory (second anchor).
|
|
143
|
+
* @returns the bundle package's absolute directory.
|
|
144
|
+
*/
|
|
145
|
+
export declare function resolveBundleDir(binName: string, packageName: string, installAnchor: string, profileDir: string): string;
|
|
146
|
+
/**
|
|
147
|
+
* Load a profile: resolve every `dsh.profile.bundles` entry to its patch
|
|
148
|
+
* layer and parse the profile's own patch file. A listed bundle without a
|
|
149
|
+
* `dsh.bundle` manifest fails loud — naming a bundle-less package as a layer
|
|
150
|
+
* is a misconfiguration, not "no patches".
|
|
151
|
+
* @param binName - the diagnostic prefix on thrown errors.
|
|
152
|
+
* @param name - the profile name.
|
|
153
|
+
* @param installAnchor - absolute path of the dsh app's package.json (first resolution anchor).
|
|
154
|
+
* @param home - the Harness home; defaults to {@link resolveDshHome}.
|
|
155
|
+
* @param options - `userLayer: false` skips reading `cordis.patch.yml`, so a
|
|
156
|
+
* bundles-only consumer (`--dump-default-config`, a recovery diagnostic)
|
|
157
|
+
* cannot fail on a broken user layer.
|
|
158
|
+
* @returns the loaded profile (empty `patches` when the user layer is skipped).
|
|
159
|
+
*/
|
|
160
|
+
export declare function loadProfile(binName: string, name: string, installAnchor: string, home?: string, options?: {
|
|
161
|
+
userLayer?: boolean;
|
|
162
|
+
}): Profile;
|
|
163
|
+
/**
|
|
164
|
+
* Compose patch layers into the effective entry list over an empty root —
|
|
165
|
+
* the same single `applyEntryPatches` call the boot include makes, so flag
|
|
166
|
+
* derivation and config dumps see exactly what mounts.
|
|
167
|
+
* @param layers - patch lists in application order.
|
|
168
|
+
* @param warn - sink for skipped-patch diagnostics; defaults to silent (boot repeats them).
|
|
169
|
+
* @returns the composed entry list.
|
|
170
|
+
*/
|
|
171
|
+
export declare function composeEntries(layers: readonly PatchOptions[][], warn?: (message: string) => void): EntryOptions[];
|
|
172
|
+
//# sourceMappingURL=profile.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stackstackstack/dsh-app-boot",
|
|
3
|
+
"description": "Shared boot glue for the app bins: .env loading, fail-loud Loader guards, snapshot-aware config resolution, and the Loader boot sequence",
|
|
4
|
+
"version": "0.1.5",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/boot/app-boot"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"js-yaml": "^4.2.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/cordis-plugin-hmr": "^1.0.16",
|
|
39
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.6",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
41
|
+
"@deepseek-ai/cordis-plugin-group": "^1.0.1",
|
|
42
|
+
"@stackstackstack/dsh-launch-environment": "^0.1.5",
|
|
43
|
+
"@stackstackstack/dsh-invariants": "^0.1.5",
|
|
44
|
+
"@stackstackstack/dsh-home-paths": "^0.1.5",
|
|
45
|
+
"@stackstackstack/dsh-system-prompt": "^0.1.5",
|
|
46
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
47
|
+
},
|
|
48
|
+
"peerDependenciesMeta": {
|
|
49
|
+
"@deepseek-ai/cordis-plugin-hmr": {
|
|
50
|
+
"optional": true
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/js-yaml": "^4.0.9",
|
|
55
|
+
"@deepseek-ai/cordis-plugin-group": "^1.0.1",
|
|
56
|
+
"@deepseek-ai/cordis-plugin-hmr": "^1.0.16",
|
|
57
|
+
"@stackstackstack/dsh-launch-environment": "^0.1.5",
|
|
58
|
+
"@deepseek-ai/cordis-plugin-include": "^1.0.6",
|
|
59
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
60
|
+
"@deepseek-ai/cordis-plugin-timer": "^1.1.3",
|
|
61
|
+
"@stackstackstack/dsh-invariants": "^0.1.5",
|
|
62
|
+
"@stackstackstack/dsh-home-paths": "^0.1.5",
|
|
63
|
+
"@stackstackstack/dsh-system-prompt": "^0.1.5",
|
|
64
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
65
|
+
}
|
|
66
|
+
}
|