@deepseek-ai/dsh-app-boot 0.1.6-alpha.1 → 0.1.6-alpha.2
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 +2 -2
- package/README.md +18 -8
- package/README.zh.md +17 -7
- package/lib/index.js +359 -198
- package/lib/types/index.d.ts +61 -28
- package/lib/types/profile-context.d.ts +51 -0
- package/lib/types/profile-plugins.d.ts +60 -0
- package/lib/types/profile-sanitize.d.ts +13 -0
- package/lib/types/profile.d.ts +25 -9
- package/package.json +11 -19
- package/lib/types/watch-config.d.ts +0 -13
package/lib/index.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { createRequire, isBuiltin } from "node:module";
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { parseEnv } from "node:util";
|
|
5
5
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
6
|
import * as yaml from "js-yaml";
|
|
7
7
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
8
8
|
import Loader, { EntryGroup, EntryTree, isJsExpr } from "@deepseek-ai/cordis-plugin-loader";
|
|
9
|
-
import { access, constants, readFile,
|
|
9
|
+
import { access, constants, readFile, rename, writeFile } from "node:fs/promises";
|
|
10
10
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
11
11
|
import Group from "@deepseek-ai/cordis-plugin-group";
|
|
12
12
|
import { dshHomePath, resolveDshHome } from "@deepseek-ai/dsh-home-paths";
|
|
13
13
|
import { createLaunchEnvironmentSnapshot } from "@deepseek-ai/dsh-launch-environment";
|
|
14
|
-
import { watch } from "chokidar";
|
|
15
14
|
import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
|
|
16
15
|
import { imports, resolve as resolve$1 } from "resolve.exports";
|
|
17
16
|
import { getEnvironmentData, setEnvironmentData } from "node:worker_threads";
|
|
@@ -247,100 +246,6 @@ var Include = class extends EntryTree {
|
|
|
247
246
|
}
|
|
248
247
|
};
|
|
249
248
|
//#endregion
|
|
250
|
-
//#region lib/types/watch-config.js
|
|
251
|
-
/** Exact-path watching for live profile patch files outside Cordis module roots. */
|
|
252
|
-
const registrations = /* @__PURE__ */ new WeakMap();
|
|
253
|
-
async function findWatchRoot(filename) {
|
|
254
|
-
let root = dirname(filename);
|
|
255
|
-
let depth = 0;
|
|
256
|
-
while (true) try {
|
|
257
|
-
if (!(await stat(root)).isDirectory()) throw new Error(`config watch parent is not a directory: ${root}`);
|
|
258
|
-
const canonicalRoot = await realpath(root);
|
|
259
|
-
return {
|
|
260
|
-
filename: resolve(canonicalRoot, relative(root, filename)),
|
|
261
|
-
root: canonicalRoot,
|
|
262
|
-
depth
|
|
263
|
-
};
|
|
264
|
-
} catch (error) {
|
|
265
|
-
if (error.code !== "ENOENT") throw error;
|
|
266
|
-
const parent = dirname(root);
|
|
267
|
-
if (parent === root) throw error;
|
|
268
|
-
root = parent;
|
|
269
|
-
depth += 1;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Watch one patch path, including missing parents, and serialize refresh callbacks.
|
|
274
|
-
* @param ctx Context that owns watcher disposal and receives refresh failures.
|
|
275
|
-
* @param filename Absolute patch-file path.
|
|
276
|
-
* @param options Deployment watcher options inherited from the HMR configuration.
|
|
277
|
-
* @param refresh Callback for additions, changes, and removals.
|
|
278
|
-
* @returns A disposer that closes the watcher and drains its current refresh.
|
|
279
|
-
* @throws When path resolution, watcher startup, or effect registration fails.
|
|
280
|
-
*/
|
|
281
|
-
async function watchConfig(ctx, filename, options, refresh) {
|
|
282
|
-
const target = await findWatchRoot(filename);
|
|
283
|
-
const paths = registrations.get(ctx) ?? /* @__PURE__ */ new Set();
|
|
284
|
-
registrations.set(ctx, paths);
|
|
285
|
-
if (paths.has(target.filename)) throw new Error(`config path already registered: ${filename}`);
|
|
286
|
-
const { cwd: _cwd, ignored: _ignored, ...watchOptions } = options;
|
|
287
|
-
const watcher = watch(target.root, {
|
|
288
|
-
...watchOptions,
|
|
289
|
-
depth: target.depth,
|
|
290
|
-
ignoreInitial: false
|
|
291
|
-
});
|
|
292
|
-
paths.add(target.filename);
|
|
293
|
-
const state = { dirty: false };
|
|
294
|
-
let running;
|
|
295
|
-
const onChange = (path) => {
|
|
296
|
-
const observed = resolve(path);
|
|
297
|
-
if (observed !== filename && observed !== target.filename) return;
|
|
298
|
-
state.dirty = true;
|
|
299
|
-
if (running) return;
|
|
300
|
-
running = (async () => {
|
|
301
|
-
while (state.dirty) {
|
|
302
|
-
state.dirty = false;
|
|
303
|
-
try {
|
|
304
|
-
await refresh();
|
|
305
|
-
} catch (reason) {
|
|
306
|
-
const error = reason instanceof Error ? reason : new Error(String(reason), { cause: reason });
|
|
307
|
-
ctx.logger.warn("config reload at %C failed", filename);
|
|
308
|
-
ctx.logger.warn(error);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
})().finally(() => {
|
|
312
|
-
running = void 0;
|
|
313
|
-
});
|
|
314
|
-
};
|
|
315
|
-
watcher.on("add", onChange);
|
|
316
|
-
watcher.on("change", onChange);
|
|
317
|
-
watcher.on("unlink", onChange);
|
|
318
|
-
const ready = Promise.withResolvers();
|
|
319
|
-
let pending = true;
|
|
320
|
-
watcher.once("ready", () => {
|
|
321
|
-
pending = false;
|
|
322
|
-
ready.resolve();
|
|
323
|
-
});
|
|
324
|
-
watcher.on("error", (error) => {
|
|
325
|
-
if (pending) {
|
|
326
|
-
pending = false;
|
|
327
|
-
ready.reject(error);
|
|
328
|
-
} else ctx.logger.warn(error);
|
|
329
|
-
});
|
|
330
|
-
const dispose = async () => {
|
|
331
|
-
await watcher.close();
|
|
332
|
-
paths.delete(target.filename);
|
|
333
|
-
await running;
|
|
334
|
-
};
|
|
335
|
-
try {
|
|
336
|
-
await ready.promise;
|
|
337
|
-
return ctx.effect(() => dispose, "app-boot.watchConfig()");
|
|
338
|
-
} catch (error) {
|
|
339
|
-
await dispose();
|
|
340
|
-
throw error;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
//#endregion
|
|
344
249
|
//#region lib/types/profile-resolution/legacy-links.js
|
|
345
250
|
/** Legacy profile-link inspection shared by the disk materializer and runtime resolver. */
|
|
346
251
|
/** Profile-private package links projected into its pnpm-managed node_modules. */
|
|
@@ -446,26 +351,11 @@ function resolveProfileDir(name, home = resolveDshHome()) {
|
|
|
446
351
|
}
|
|
447
352
|
/** The shipped profile templates auto-initialized on first use, by name. */
|
|
448
353
|
const PROFILE_TEMPLATES = {
|
|
449
|
-
acp: {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
},
|
|
453
|
-
|
|
454
|
-
bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"],
|
|
455
|
-
patchReload: "live"
|
|
456
|
-
},
|
|
457
|
-
headless: {
|
|
458
|
-
bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"],
|
|
459
|
-
patchReload: "startup"
|
|
460
|
-
},
|
|
461
|
-
sdk: {
|
|
462
|
-
bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-sdk-app"],
|
|
463
|
-
patchReload: "startup"
|
|
464
|
-
},
|
|
465
|
-
"sdk-minimal": {
|
|
466
|
-
bundles: ["@deepseek-ai/dsh-sdk-minimal"],
|
|
467
|
-
patchReload: "startup"
|
|
468
|
-
}
|
|
354
|
+
acp: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-acp-app"] },
|
|
355
|
+
web: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"] },
|
|
356
|
+
headless: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-headless"] },
|
|
357
|
+
sdk: { bundles: ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-sdk-app"] },
|
|
358
|
+
"sdk-minimal": { bundles: ["@deepseek-ai/dsh-sdk-minimal"] }
|
|
469
359
|
};
|
|
470
360
|
/** Installation-owned bundle tuples normalized to the shipped template. */
|
|
471
361
|
const INSTALLATION_OWNED_PROFILE_TUPLES = { headless: [
|
|
@@ -475,8 +365,13 @@ const INSTALLATION_OWNED_PROFILE_TUPLES = { headless: [
|
|
|
475
365
|
] };
|
|
476
366
|
/** The bundle list a `dsh plugin` init uses for a name with no shipped template. */
|
|
477
367
|
const DEFAULT_PROFILE_BUNDLES = ["@deepseek-ai/dsh-base"];
|
|
478
|
-
/**
|
|
479
|
-
|
|
368
|
+
/**
|
|
369
|
+
* The bundles the dsh installation ships for a person to switch on: each a
|
|
370
|
+
* runtime dependency of the installation that declares `dsh.bundle.patch`,
|
|
371
|
+
* selected by no shipped template, and offered switched off by the plugin
|
|
372
|
+
* manager ([rationale](../../../../.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md)).
|
|
373
|
+
*/
|
|
374
|
+
const OPTIONAL_BUNDLES = ["@deepseek-ai/dsh-experimental-agent-team-profile", "@deepseek-ai/dsh-experimental-agent-team-web-profile"];
|
|
480
375
|
const PROFILE_PATCH_TEMPLATE = `# Your patch layer for this dsh profile, applied after every bundle layer:
|
|
481
376
|
# a top-level YAML array of loader patch entries (id-targeted config
|
|
482
377
|
# overrides, disables, and insert lists; \`!!js\` expressions allowed).
|
|
@@ -494,9 +389,8 @@ autoInstallPeers: false
|
|
|
494
389
|
* so re-running is a no-op on an initialized profile.
|
|
495
390
|
* @param dir - the profile directory from {@link resolveProfileDir}.
|
|
496
391
|
* @param bundles - the initial `dsh.profile.bundles` layer list.
|
|
497
|
-
* @param patchReload - user patch-file lifecycle; custom profiles default to live reload.
|
|
498
392
|
*/
|
|
499
|
-
function initProfile(dir, bundles
|
|
393
|
+
function initProfile(dir, bundles) {
|
|
500
394
|
mkdirSync(dir, { recursive: true });
|
|
501
395
|
const manifestPath = join(dir, "package.json");
|
|
502
396
|
if (!existsSync(manifestPath)) {
|
|
@@ -504,10 +398,7 @@ function initProfile(dir, bundles, patchReload = DEFAULT_PROFILE_PATCH_RELOAD) {
|
|
|
504
398
|
name: `dsh-profile-${basename(dir)}`,
|
|
505
399
|
private: true,
|
|
506
400
|
dependencies: {},
|
|
507
|
-
dsh: { profile: {
|
|
508
|
-
bundles: [...bundles],
|
|
509
|
-
patchReload
|
|
510
|
-
} }
|
|
401
|
+
dsh: { profile: { bundles: [...bundles] } }
|
|
511
402
|
};
|
|
512
403
|
writeFileSync(manifestPath, JSON.stringify(manifest, void 0, 2) + "\n");
|
|
513
404
|
}
|
|
@@ -823,6 +714,26 @@ function createProfileResolutionGeneration(options) {
|
|
|
823
714
|
materialize: false
|
|
824
715
|
});
|
|
825
716
|
}
|
|
717
|
+
/**
|
|
718
|
+
* Supply an application-owned profile with filesystem packages from its installation and selected bundles.
|
|
719
|
+
* All fallback links belong to the profile; no shared Harness-home directory is written.
|
|
720
|
+
* Existing pnpm-managed packages remain authoritative. The caller serializes profile mutations.
|
|
721
|
+
* @param options - owning installation package.json and the loaded application profile.
|
|
722
|
+
*/
|
|
723
|
+
function healIsolatedProfileModuleFallback(options) {
|
|
724
|
+
const installationLinks = resolveModuleFallbackEntries(options.installAnchor, false).packageDirs;
|
|
725
|
+
healProfileModuleFallback(options.profile, new Set(installationLinks.keys()), true, void 0, void 0, installationLinks);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Detach this profile's fallback links before a package-manager mutation.
|
|
729
|
+
* Installed packages and links replaced by pnpm remain untouched; the next profile launch restores fallbacks.
|
|
730
|
+
* @param profileDir - profile directory whose package mutation is serialized by the caller.
|
|
731
|
+
*/
|
|
732
|
+
function unlinkProfileModuleFallback(profileDir) {
|
|
733
|
+
const ownedModulesDir = join(profileDir, PROFILE_MODULE_FALLBACK_DIR, "node_modules");
|
|
734
|
+
if (!existsSync(ownedModulesDir)) return;
|
|
735
|
+
for (const name of ownedPackageNames(ownedModulesDir)) removeProfileSymlink(join(profileDir, "node_modules"), ownedModulesDir, name);
|
|
736
|
+
}
|
|
826
737
|
/** Heal one module-fallback generation while the cross-process writer lock is held. */
|
|
827
738
|
function healProfilesModuleFallbackLocked(entries, modulesDir) {
|
|
828
739
|
for (const entry of entries) {
|
|
@@ -872,7 +783,7 @@ function dependencyClosure(anchors, reserved, exclude, declarers, versions) {
|
|
|
872
783
|
return links;
|
|
873
784
|
}
|
|
874
785
|
/** Reconcile packages carried only by selected bundles into one profile. */
|
|
875
|
-
function healProfileModuleFallback(profile, installationPackageNames, materialize = true, declarers, versions) {
|
|
786
|
+
function healProfileModuleFallback(profile, installationPackageNames, materialize = true, declarers, versions, installationLinks = /* @__PURE__ */ new Map()) {
|
|
876
787
|
const profileModulesDir = join(profile.dir, "node_modules");
|
|
877
788
|
const ownedModulesDir = join(profile.dir, PROFILE_MODULE_FALLBACK_DIR, "node_modules");
|
|
878
789
|
if (materialize) {
|
|
@@ -892,9 +803,10 @@ function healProfileModuleFallback(profile, installationPackageNames, materializ
|
|
|
892
803
|
}
|
|
893
804
|
}, declarers, versions);
|
|
894
805
|
for (const layer of profile.layers) bundleLinks.delete(layer.packageName);
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
for (const
|
|
806
|
+
const links = new Map([...installationLinks, ...bundleLinks]);
|
|
807
|
+
if (!materialize) return links;
|
|
808
|
+
for (const packageName of ownedPackageNames(ownedModulesDir)) if (!links.has(packageName)) removeProfileSymlink(profileModulesDir, ownedModulesDir, packageName);
|
|
809
|
+
for (const [packageName, target] of links) {
|
|
898
810
|
const ownedLink = join(ownedModulesDir, packageName);
|
|
899
811
|
mkdirSync(dirname(ownedLink), { recursive: true });
|
|
900
812
|
ensureSymlink(ownedLink, target);
|
|
@@ -902,7 +814,7 @@ function healProfileModuleFallback(profile, installationPackageNames, materializ
|
|
|
902
814
|
mkdirSync(dirname(profileLink), { recursive: true });
|
|
903
815
|
ensureProfileSymlink(profileLink, ownedLink);
|
|
904
816
|
}
|
|
905
|
-
return
|
|
817
|
+
return links;
|
|
906
818
|
}
|
|
907
819
|
/**
|
|
908
820
|
* Read a profile's manifest.
|
|
@@ -936,27 +848,21 @@ function sameBundles(left, right) {
|
|
|
936
848
|
}
|
|
937
849
|
/**
|
|
938
850
|
* Normalize an exact installation-owned bundle tuple to its shipped template,
|
|
939
|
-
*
|
|
940
|
-
* is written back during profile loading while every other manifest field is
|
|
941
|
-
* preserved; any other bundle list is user-owned and remains untouched.
|
|
851
|
+
* preserving all other manifest fields. Other bundle lists remain untouched.
|
|
942
852
|
*/
|
|
943
853
|
function normalizeShippedProfile(name, dir, manifest) {
|
|
944
854
|
const installationOwned = INSTALLATION_OWNED_PROFILE_TUPLES[name];
|
|
945
855
|
const template = PROFILE_TEMPLATES[name];
|
|
946
856
|
const bundles = manifest.dsh?.profile?.bundles;
|
|
947
857
|
if (template === void 0 || bundles === void 0) return manifest;
|
|
948
|
-
|
|
949
|
-
const isCurrentTuple = sameBundles(bundles, template.bundles);
|
|
950
|
-
const needsReloadDefault = manifest.dsh?.profile?.patchReload === void 0 && isCurrentTuple;
|
|
951
|
-
if (!isRetiredTuple && !needsReloadDefault) return manifest;
|
|
858
|
+
if (!(installationOwned !== void 0 && sameBundles(bundles, installationOwned))) return manifest;
|
|
952
859
|
const normalized = {
|
|
953
860
|
...manifest,
|
|
954
861
|
dsh: {
|
|
955
862
|
...manifest.dsh,
|
|
956
863
|
profile: {
|
|
957
864
|
...manifest.dsh?.profile,
|
|
958
|
-
bundles: [...template.bundles]
|
|
959
|
-
patchReload: manifest.dsh?.profile?.patchReload ?? template.patchReload
|
|
865
|
+
bundles: [...template.bundles]
|
|
960
866
|
}
|
|
961
867
|
}
|
|
962
868
|
};
|
|
@@ -1008,12 +914,7 @@ function resolveBundleDir(binName, packageName, installAnchor, profileDir) {
|
|
|
1008
914
|
* @returns the resolved bundle layers and optional user patch layer.
|
|
1009
915
|
*/
|
|
1010
916
|
function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
|
|
1011
|
-
const
|
|
1012
|
-
const bundles = manifest.dsh?.profile?.bundles ?? [];
|
|
1013
|
-
const rawPatchReload = manifest.dsh?.profile?.patchReload;
|
|
1014
|
-
if (rawPatchReload !== void 0 && rawPatchReload !== "live" && rawPatchReload !== "startup") throw new Error(`${binName}: profile manifest ${join(dir, "package.json")} dsh.profile.patchReload must be "live" or "startup"`);
|
|
1015
|
-
const patchReload = rawPatchReload ?? "live";
|
|
1016
|
-
const layers = bundles.map((packageName) => {
|
|
917
|
+
const layers = (readProfileManifest(binName, dir).dsh?.profile?.bundles ?? []).map((packageName) => {
|
|
1017
918
|
const packageDir = resolveBundleDir(binName, packageName, installAnchor, dir);
|
|
1018
919
|
const declared = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8")).dsh?.bundle?.patch;
|
|
1019
920
|
if (declared === void 0) throw new Error(`${binName}: profile bundle ${JSON.stringify(packageName)} declares no dsh.bundle in its package.json`);
|
|
@@ -1032,8 +933,7 @@ function loadProfileDirectory(binName, dir, installAnchor, options = {}) {
|
|
|
1032
933
|
dir,
|
|
1033
934
|
layers,
|
|
1034
935
|
patchPath,
|
|
1035
|
-
patches
|
|
1036
|
-
patchReload
|
|
936
|
+
patches
|
|
1037
937
|
};
|
|
1038
938
|
}
|
|
1039
939
|
/**
|
|
@@ -1055,7 +955,7 @@ function loadProfile(binName, name, installAnchor, home = resolveDshHome(), opti
|
|
|
1055
955
|
if (!existsSync(join(dir, "package.json"))) {
|
|
1056
956
|
const template = PROFILE_TEMPLATES[name];
|
|
1057
957
|
if (template === void 0) throw new Error(`${binName}: profile ${JSON.stringify(name)} does not exist; create it with 'dsh plugin --profile ${name} add <package>'`);
|
|
1058
|
-
initProfile(dir, template.bundles
|
|
958
|
+
initProfile(dir, template.bundles);
|
|
1059
959
|
}
|
|
1060
960
|
normalizeShippedProfile(name, dir, readProfileManifest(binName, dir));
|
|
1061
961
|
return loadProfileDirectory(binName, dir, installAnchor, options);
|
|
@@ -1075,6 +975,166 @@ function composeEntries(layers, warn = () => {}) {
|
|
|
1075
975
|
});
|
|
1076
976
|
}
|
|
1077
977
|
//#endregion
|
|
978
|
+
//#region lib/types/profile-context.js
|
|
979
|
+
/** Launcher-owned profile locations and composition inputs. */
|
|
980
|
+
const TELEMETRY_ROW_ID = "session-telemetry-otel";
|
|
981
|
+
/**
|
|
982
|
+
* Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
|
|
983
|
+
* value (including `'0'`/`'false'`) disables: a privacy switch prefers
|
|
984
|
+
* off-by-mistake over on-by-mistake. A composition without the telemetry row
|
|
985
|
+
* exports nothing, so the switch is then trivially satisfied and no patch is
|
|
986
|
+
* generated — custom profiles need not mount telemetry to run with the
|
|
987
|
+
* switch set.
|
|
988
|
+
* @param disabledEnv - the raw `DSH_TELEMETRY_DISABLED` value (`undefined` when unset).
|
|
989
|
+
* @param hasRow - whether the composition carries the telemetry row.
|
|
990
|
+
* @returns the disable patch, or `undefined` when no hard-disable patch is required.
|
|
991
|
+
*/
|
|
992
|
+
function resolveTelemetryPatch(disabledEnv, hasRow) {
|
|
993
|
+
if ((disabledEnv ?? "") === "" || !hasRow) return void 0;
|
|
994
|
+
return {
|
|
995
|
+
id: TELEMETRY_ROW_ID,
|
|
996
|
+
disabled: true
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
/** Read current bundle and user layers with the launch-time overlays.
|
|
1000
|
+
* @param binName Diagnostic prefix for malformed or missing configuration.
|
|
1001
|
+
* @param context Data supplied by the profile launcher.
|
|
1002
|
+
* @param initialProfile Already loaded startup profile; omitted reads the current files.
|
|
1003
|
+
* @returns Detached ordered patches; this function does not update the Loader.
|
|
1004
|
+
*/
|
|
1005
|
+
function readProfilePatches(binName, context, initialProfile) {
|
|
1006
|
+
const profile = initialProfile ?? loadProfileDirectory(binName, context.dir, context.installAnchor, { userLayer: false });
|
|
1007
|
+
const patches = structuredClone([
|
|
1008
|
+
...profile.layers.flatMap((layer) => layer.patches),
|
|
1009
|
+
...initialProfile?.patches ?? loadOptionalPatches(binName, context.patchPath) ?? [],
|
|
1010
|
+
...loadOptionalPatches(binName, join(context.home, "cordis.patch.yml")) ?? [],
|
|
1011
|
+
...context.overlays
|
|
1012
|
+
]);
|
|
1013
|
+
const telemetryPatch = resolveTelemetryPatch(context.telemetryDisabledEnv, composeEntries([patches]).some((row) => row.id === TELEMETRY_ROW_ID));
|
|
1014
|
+
if (telemetryPatch !== void 0) patches.push(telemetryPatch);
|
|
1015
|
+
return patches;
|
|
1016
|
+
}
|
|
1017
|
+
//#endregion
|
|
1018
|
+
//#region lib/types/profile-plugins.js
|
|
1019
|
+
/** Installed profile dependencies and their bundle activation after package-manager operations. */
|
|
1020
|
+
/** Unavailable installed metadata must not prevent listing or removing dependencies. */
|
|
1021
|
+
function optionalManifest(binName, packageDir) {
|
|
1022
|
+
try {
|
|
1023
|
+
return readProfileManifest(binName, packageDir);
|
|
1024
|
+
} catch {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/** Resolve activation metadata with the same installation precedence as profile loading. */
|
|
1029
|
+
function bundleManifest(location, name) {
|
|
1030
|
+
let packageDir;
|
|
1031
|
+
try {
|
|
1032
|
+
packageDir = resolveBundleDir(location.binName, name, location.installAnchor, location.profileDir);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
return optionalManifest(location.binName, packageDir);
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Read installed versions and bundle declarations without requiring loadable plugin code.
|
|
1040
|
+
* Missing or unreadable installed metadata leaves the dependency visible for repair or removal.
|
|
1041
|
+
* @param location - profile and installation resolution inputs.
|
|
1042
|
+
* @returns dependency records in package.json order and the profile manifest.
|
|
1043
|
+
*/
|
|
1044
|
+
function readProfilePlugins(location) {
|
|
1045
|
+
const manifest = readProfileManifest(location.binName, location.profileDir);
|
|
1046
|
+
const bundles = manifest.dsh?.profile?.bundles ?? [];
|
|
1047
|
+
return {
|
|
1048
|
+
manifest,
|
|
1049
|
+
dependencies: Object.entries(manifest.dependencies ?? {}).map(([name, spec]) => {
|
|
1050
|
+
const installed = optionalManifest(location.binName, join(location.profileDir, "node_modules", name));
|
|
1051
|
+
return {
|
|
1052
|
+
name,
|
|
1053
|
+
version: typeof installed?.version === "string" ? installed.version : spec,
|
|
1054
|
+
bundle: bundleManifest(location, name)?.dsh?.bundle?.patch !== void 0,
|
|
1055
|
+
enabled: bundles.includes(name)
|
|
1056
|
+
};
|
|
1057
|
+
})
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Write a bundle list while preserving the supplied profile's other metadata.
|
|
1062
|
+
* @param profileDir - directory whose package.json is updated.
|
|
1063
|
+
* @param manifest - current profile manifest, read after the package operation when applicable.
|
|
1064
|
+
* @param bundles - ordered active bundle names, including any template entries.
|
|
1065
|
+
* @returns the written manifest.
|
|
1066
|
+
*/
|
|
1067
|
+
function writeProfileBundles(profileDir, manifest, bundles) {
|
|
1068
|
+
const updated = {
|
|
1069
|
+
...manifest,
|
|
1070
|
+
dsh: {
|
|
1071
|
+
...manifest.dsh,
|
|
1072
|
+
profile: {
|
|
1073
|
+
...manifest.dsh?.profile,
|
|
1074
|
+
bundles: [...bundles]
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
writeProfileManifest(profileDir, updated);
|
|
1079
|
+
return updated;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Reconcile installed bundle declarations after a successful package-manager operation.
|
|
1083
|
+
* Dependency-managed entries disappear when removed or when their package loses its declaration;
|
|
1084
|
+
* template entries retain their order and duplicates. New bundle dependencies activate automatically.
|
|
1085
|
+
* @param options - location, inventory captured before pnpm, and whether explicitly disabled bundles remain disabled.
|
|
1086
|
+
* @returns updated inventory and newly added ordinary dependencies for caller-owned warnings.
|
|
1087
|
+
*/
|
|
1088
|
+
function reconcileProfilePlugins(options) {
|
|
1089
|
+
const after = readProfilePlugins(options);
|
|
1090
|
+
const beforeNames = new Set(options.before.dependencies.map((dependency) => dependency.name));
|
|
1091
|
+
const afterNames = new Set(after.dependencies.map((dependency) => dependency.name));
|
|
1092
|
+
const bundleNames = new Set(after.dependencies.filter((dependency) => dependency.bundle).map((dependency) => dependency.name));
|
|
1093
|
+
const disabled = new Set(options.preserveDisabled ? options.before.dependencies.filter((dependency) => dependency.bundle && !dependency.enabled).map((dependency) => dependency.name) : []);
|
|
1094
|
+
const previous = after.manifest.dsh?.profile?.bundles ?? [];
|
|
1095
|
+
const bundles = previous.filter((name) => !(beforeNames.has(name) || afterNames.has(name)) || bundleNames.has(name));
|
|
1096
|
+
for (const dependency of after.dependencies) if (dependency.bundle && !disabled.has(dependency.name) && !bundles.includes(dependency.name)) bundles.push(dependency.name);
|
|
1097
|
+
return {
|
|
1098
|
+
plugins: {
|
|
1099
|
+
manifest: bundles.length !== previous.length || bundles.some((name, index) => name !== previous[index]) ? writeProfileBundles(options.profileDir, after.manifest, bundles) : after.manifest,
|
|
1100
|
+
dependencies: after.dependencies.map((dependency) => ({
|
|
1101
|
+
...dependency,
|
|
1102
|
+
enabled: bundles.includes(dependency.name)
|
|
1103
|
+
}))
|
|
1104
|
+
},
|
|
1105
|
+
addedPlainDependencies: after.dependencies.filter((dependency) => !dependency.bundle && !beforeNames.has(dependency.name)).map((dependency) => dependency.name)
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region lib/types/profile-sanitize.js
|
|
1110
|
+
/** Filesystem recovery for callers that own profile shutdown and write exclusion. */
|
|
1111
|
+
/**
|
|
1112
|
+
* Back up the profile patch and retain only the caller's recovery bundles.
|
|
1113
|
+
* The caller must stop the profile and exclude concurrent profile writes.
|
|
1114
|
+
* Installed packages and other manifest fields are preserved; patches are never parsed.
|
|
1115
|
+
* Failures propagate and may leave completed changes in place for a retry.
|
|
1116
|
+
* @param binName - Diagnostic prefix for invalid profile manifests.
|
|
1117
|
+
* @param profileDir - Profile directory to recover without loading its plugins.
|
|
1118
|
+
* @param bundles - Ordered bundles to enable after recovery.
|
|
1119
|
+
* @returns Backup path with a Unix millisecond timestamp and optional collision ordinal, or undefined if absent.
|
|
1120
|
+
*/
|
|
1121
|
+
function sanitizeProfile(binName, profileDir, bundles) {
|
|
1122
|
+
const manifest = existsSync(join(profileDir, "package.json")) ? readProfileManifest(binName, profileDir) : void 0;
|
|
1123
|
+
const patchPath = join(profileDir, PROFILE_PATCH_FILENAME);
|
|
1124
|
+
const backupBase = `${patchPath}.bak-${Date.now()}`;
|
|
1125
|
+
let backupPath = backupBase;
|
|
1126
|
+
let ordinal = 0;
|
|
1127
|
+
while (existsSync(backupPath)) backupPath = `${backupBase}-${++ordinal}`;
|
|
1128
|
+
try {
|
|
1129
|
+
renameSync(patchPath, backupPath);
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
1132
|
+
backupPath = void 0;
|
|
1133
|
+
}
|
|
1134
|
+
if (manifest !== void 0) writeProfileBundles(profileDir, manifest, bundles);
|
|
1135
|
+
return backupPath;
|
|
1136
|
+
}
|
|
1137
|
+
//#endregion
|
|
1078
1138
|
//#region lib/types/profile-resolution/resolver.js
|
|
1079
1139
|
/** In-memory profile package routing for Node's default ESM and CommonJS loaders. */
|
|
1080
1140
|
const WORKER_RESOLUTION_KEY = "@deepseek-ai/dsh-app-boot/profile-resolution";
|
|
@@ -2061,37 +2121,38 @@ function loadLayeredEnv(binName, cwd = process.cwd(), warn = (line) => void proc
|
|
|
2061
2121
|
}
|
|
2062
2122
|
const bootstrapIncludes = /* @__PURE__ */ new WeakMap();
|
|
2063
2123
|
const userPatchesSchema = entryListSchema;
|
|
2064
|
-
/**
|
|
2065
|
-
*
|
|
2066
|
-
* @param
|
|
2067
|
-
* @param
|
|
2068
|
-
* @
|
|
2069
|
-
* @
|
|
2124
|
+
/** Apply one complete patch generation and wait for Loader activation diagnostics.
|
|
2125
|
+
* @param ctx Booted root context.
|
|
2126
|
+
* @param patches Complete ordered patch list.
|
|
2127
|
+
* @param binName Diagnostic prefix.
|
|
2128
|
+
* @param requiredIds Explicit enablement targets whose existing failures also reject reconciliation.
|
|
2129
|
+
* @returns Diagnostics for unchanged pre-existing inactive entries; new or changed failures reject.
|
|
2070
2130
|
*/
|
|
2071
|
-
async function
|
|
2072
|
-
const { binName, filename, compose = (patches) => patches } = options;
|
|
2073
|
-
const hmr = ctx.get("hmr");
|
|
2074
|
-
if (hmr === void 0) throw new Error(`${binName}: user patch-layer watching requires the Cordis HMR service`);
|
|
2131
|
+
async function reconcileProfilePatches(ctx, patches, binName, requiredIds = []) {
|
|
2075
2132
|
const entry = bootstrapIncludes.get(ctx);
|
|
2076
|
-
if (entry === void 0) throw new Error(`${binName}:
|
|
2077
|
-
const
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2133
|
+
if (entry === void 0) throw new Error(`${binName}: profile reload requires the root Include entry`);
|
|
2134
|
+
const previousFailures = (await inactiveEntries(ctx)).map((failure) => ({
|
|
2135
|
+
...failure,
|
|
2136
|
+
diagnostic: inactiveDiagnostic(failure),
|
|
2137
|
+
fiber: failure.entry.fiber,
|
|
2138
|
+
options: JSON.stringify(failure.entry.options)
|
|
2139
|
+
}));
|
|
2140
|
+
const previousFibers = [...ctx.loader.entries()].flatMap((row) => row.fiber === void 0 ? [] : [{
|
|
2141
|
+
fiber: row.fiber,
|
|
2142
|
+
failed: row.fiber.state === FIBER_FAILED || row.fiber.state === FIBER_DISPOSED
|
|
2143
|
+
}]);
|
|
2144
|
+
const { patches: _previous, ...includeConfig } = entry.options.config;
|
|
2145
|
+
await entry.update({ config: {
|
|
2146
|
+
...includeConfig,
|
|
2147
|
+
patches
|
|
2148
|
+
} });
|
|
2149
|
+
const results = await Promise.allSettled(previousFibers.map(({ fiber }) => fiber.await()));
|
|
2150
|
+
await ctx.loader.await();
|
|
2151
|
+
const failures = await inactiveEntries(ctx);
|
|
2152
|
+
const introduced = failures.filter((failure) => requiredIds.includes(failure.entry.options.id) || !previousFailures.some((previous) => previous.entry === failure.entry && previous.fiber === failure.entry.fiber && previous.options === JSON.stringify(failure.entry.options) && previous.diagnostic === inactiveDiagnostic(failure)));
|
|
2153
|
+
if (introduced.length > 0) throw new Error(activationDiagnostic(binName, introduced).trimEnd());
|
|
2154
|
+
for (const [index, result] of results.entries()) if (result.status === "rejected" && !previousFibers[index]?.failed) throw result.reason;
|
|
2155
|
+
return failures.map(inactiveDiagnostic);
|
|
2095
2156
|
}
|
|
2096
2157
|
/**
|
|
2097
2158
|
* Load an optional patch-list file: a top-level YAML array of loader patch
|
|
@@ -2392,12 +2453,12 @@ function installFailLoud(binName, proc = process, release) {
|
|
|
2392
2453
|
}
|
|
2393
2454
|
/**
|
|
2394
2455
|
* Value mirrors used because Cordis's const enum has no runtime object to import.
|
|
2395
|
-
* Keep aligned with `packages/
|
|
2396
|
-
* `packages/client/web/src/loader-status.ts`.
|
|
2456
|
+
* Keep aligned with `packages/client/web/src/loader-status.ts`.
|
|
2397
2457
|
*/
|
|
2398
2458
|
const FIBER_PENDING = 0;
|
|
2399
2459
|
const FIBER_ACTIVE = 2;
|
|
2400
2460
|
const FIBER_FAILED = 3;
|
|
2461
|
+
const FIBER_DISPOSED = 4;
|
|
2401
2462
|
/**
|
|
2402
2463
|
* Entry ids whose presence defines a usable DSH application.
|
|
2403
2464
|
*
|
|
@@ -2432,6 +2493,25 @@ function formatActivationError(error) {
|
|
|
2432
2493
|
visit(error);
|
|
2433
2494
|
return details.join("\n");
|
|
2434
2495
|
}
|
|
2496
|
+
/** Startup audit failure with non-enumerable metadata and original failures as its cause. */
|
|
2497
|
+
var StartupError = class extends Error {
|
|
2498
|
+
entries;
|
|
2499
|
+
/** Root configuration and startup logs, attached by boot after disposal. */
|
|
2500
|
+
startup;
|
|
2501
|
+
/**
|
|
2502
|
+
* @param message - concise terminal diagnostic.
|
|
2503
|
+
* @param entries - inactive plugin metadata and original failure values.
|
|
2504
|
+
*/
|
|
2505
|
+
constructor(message, entries) {
|
|
2506
|
+
const failures = entries.flatMap(({ outcome }) => outcome.kind === "failed" ? [outcome.error] : []);
|
|
2507
|
+
super(message, failures.length > 0 ? { cause: new AggregateError(failures, "Plugin activation failures") } : void 0);
|
|
2508
|
+
this.entries = entries;
|
|
2509
|
+
Object.defineProperties(this, {
|
|
2510
|
+
entries: { enumerable: false },
|
|
2511
|
+
startup: { enumerable: false }
|
|
2512
|
+
});
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2435
2515
|
/**
|
|
2436
2516
|
* Collect Loader activation failures and disabled-expression errors. Failed
|
|
2437
2517
|
* fibers are awaited to recover their recorded rejection reason and coalesce
|
|
@@ -2441,13 +2521,16 @@ async function inactiveEntries(ctx) {
|
|
|
2441
2521
|
const failures = [];
|
|
2442
2522
|
const rejectionReasons = [];
|
|
2443
2523
|
for (const entry of ctx.loader.entries()) {
|
|
2444
|
-
const subject = `${entry.options.id} (${entry.options.name})`;
|
|
2445
2524
|
try {
|
|
2446
2525
|
if (entry.disabled) continue;
|
|
2447
2526
|
} catch (error) {
|
|
2448
2527
|
failures.push({
|
|
2449
2528
|
entry,
|
|
2450
|
-
|
|
2529
|
+
outcome: {
|
|
2530
|
+
kind: "failed",
|
|
2531
|
+
error,
|
|
2532
|
+
phase: "disabled expression failed"
|
|
2533
|
+
}
|
|
2451
2534
|
});
|
|
2452
2535
|
continue;
|
|
2453
2536
|
}
|
|
@@ -2455,7 +2538,10 @@ async function inactiveEntries(ctx) {
|
|
|
2455
2538
|
if (fiber === void 0) {
|
|
2456
2539
|
failures.push({
|
|
2457
2540
|
entry,
|
|
2458
|
-
|
|
2541
|
+
outcome: {
|
|
2542
|
+
kind: "failed",
|
|
2543
|
+
error: "failed to import"
|
|
2544
|
+
}
|
|
2459
2545
|
});
|
|
2460
2546
|
continue;
|
|
2461
2547
|
}
|
|
@@ -2468,7 +2554,10 @@ async function inactiveEntries(ctx) {
|
|
|
2468
2554
|
rejectionReasons.push(error);
|
|
2469
2555
|
failures.push({
|
|
2470
2556
|
entry,
|
|
2471
|
-
|
|
2557
|
+
outcome: {
|
|
2558
|
+
kind: "failed",
|
|
2559
|
+
error
|
|
2560
|
+
}
|
|
2472
2561
|
});
|
|
2473
2562
|
}
|
|
2474
2563
|
continue;
|
|
@@ -2477,42 +2566,90 @@ async function inactiveEntries(ctx) {
|
|
|
2477
2566
|
const missing = Object.keys(fiber.inject).filter((service) => fiber.ctx.get(service) === void 0);
|
|
2478
2567
|
failures.push({
|
|
2479
2568
|
entry,
|
|
2480
|
-
|
|
2569
|
+
outcome: {
|
|
2570
|
+
kind: "pending",
|
|
2571
|
+
missing
|
|
2572
|
+
}
|
|
2481
2573
|
});
|
|
2482
2574
|
} else failures.push({
|
|
2483
2575
|
entry,
|
|
2484
|
-
|
|
2576
|
+
outcome: {
|
|
2577
|
+
kind: "failed",
|
|
2578
|
+
error: `fiber state ${String(state)}`
|
|
2579
|
+
}
|
|
2485
2580
|
});
|
|
2486
2581
|
}
|
|
2487
2582
|
if (rejectionReasons.length > 0) await observeLoaderRejectionCheckpoint(rejectionReasons);
|
|
2488
2583
|
return failures;
|
|
2489
2584
|
}
|
|
2490
|
-
/** Render
|
|
2491
|
-
function
|
|
2585
|
+
/** Render one failed plugin's original error and activation phase. */
|
|
2586
|
+
function failureDetail(outcome) {
|
|
2587
|
+
return `${outcome.phase === void 0 ? "" : `${outcome.phase}: `}${formatActivationError(outcome.error)}`;
|
|
2588
|
+
}
|
|
2589
|
+
/** Render optional-only warnings without changing startup policy. */
|
|
2590
|
+
function activationDiagnostic(binName, failures) {
|
|
2492
2591
|
const noun = failures.length === 1 ? "entry" : "entries";
|
|
2493
|
-
return `${binName
|
|
2592
|
+
return `${binName}: warning: ${String(failures.length)} ${noun} did not activate\n${failures.map(inactiveDiagnostic).join("\n")}\n`;
|
|
2593
|
+
}
|
|
2594
|
+
/** Stable per-entry text for reload comparisons and optional warnings. */
|
|
2595
|
+
function inactiveDiagnostic({ entry, outcome }) {
|
|
2596
|
+
const detail = outcome.kind === "failed" ? failureDetail(outcome) : `pending (waiting for ${outcome.missing.length === 1 ? "service" : "services"}: ${outcome.missing.join(", ") || "unknown"})`;
|
|
2597
|
+
return `${entry.options.id} (${entry.options.name}): ${detail}`;
|
|
2598
|
+
}
|
|
2599
|
+
/** Group startup failures and pending services, marking every required entry. */
|
|
2600
|
+
function startupDiagnostic(binName, failures, required) {
|
|
2601
|
+
const lines = [`${binName}: startup failed: ${String(required.size)} required ${required.size === 1 ? "plugin" : "plugins"} did not activate`];
|
|
2602
|
+
const failed = failures.flatMap(({ entry, outcome }) => outcome.kind === "failed" ? [{
|
|
2603
|
+
entry,
|
|
2604
|
+
outcome
|
|
2605
|
+
}] : []);
|
|
2606
|
+
const pending = failures.flatMap(({ entry, outcome }) => outcome.kind === "pending" ? [{
|
|
2607
|
+
entry,
|
|
2608
|
+
outcome
|
|
2609
|
+
}] : []);
|
|
2610
|
+
pending.sort((left, right) => Number(required.has(right.entry)) - Number(required.has(left.entry)));
|
|
2611
|
+
const label = (entry) => `${entry.options.id}${required.has(entry) ? " (required)" : ""}`;
|
|
2612
|
+
if (failed.length > 0) {
|
|
2613
|
+
lines.push("", `Failed plugins (${String(failed.length)}):`);
|
|
2614
|
+
for (const { entry, outcome } of failed) {
|
|
2615
|
+
lines.push(` ${label(entry)}`, ` Package: ${entry.options.name}`);
|
|
2616
|
+
lines.push(...failureDetail(outcome).split("\n").map((line) => ` ${line}`));
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
if (pending.length > 0) {
|
|
2620
|
+
const width = Math.max(6, ...pending.map(({ entry }) => label(entry).length)) + 2;
|
|
2621
|
+
lines.push("", `Plugins waiting for services (${String(pending.length)}):`, ` ${"Plugin".padEnd(width)}Missing services`);
|
|
2622
|
+
for (const { entry, outcome } of pending) lines.push(` ${label(entry).padEnd(width)}${outcome.missing.join(", ") || "unknown"}`);
|
|
2623
|
+
}
|
|
2624
|
+
return lines.join("\n");
|
|
2494
2625
|
}
|
|
2495
2626
|
/**
|
|
2496
2627
|
* Apply DSH startup policy to a settled Loader tree.
|
|
2497
2628
|
*
|
|
2498
2629
|
* Inactive entries from the global required list reject startup. Other
|
|
2499
|
-
* inactive entries
|
|
2630
|
+
* inactive entries join that failure diagnostic, or produce one warning when
|
|
2631
|
+
* no required entry failed and leave successful siblings running.
|
|
2500
2632
|
* Required ids absent from the tree, and disabled required entries, are ignored.
|
|
2501
2633
|
* A throwing disabled expression is an entry failure, not a disabled entry.
|
|
2502
2634
|
* The bootstrap Include must activate so unreadable or invalid root config is fatal.
|
|
2503
2635
|
* @param ctx - the settled context whose Loader entries to audit.
|
|
2504
|
-
* @param binName - the
|
|
2636
|
+
* @param binName - the prefix on startup diagnostics.
|
|
2505
2637
|
* @param warn - sink for optional-entry warnings.
|
|
2506
2638
|
* @returns after optional warnings if required startup checks pass.
|
|
2507
|
-
* @throws when the bootstrap Include or a required entry is inactive or its disabled expression throws
|
|
2639
|
+
* @throws {@link StartupError} when the bootstrap Include or a required entry is inactive or its disabled expression throws;
|
|
2640
|
+
* its message includes optional failures too.
|
|
2508
2641
|
*/
|
|
2509
2642
|
async function auditStartupEntries(ctx, binName, warn = (line) => void process.stderr.write(line)) {
|
|
2510
2643
|
const failures = await inactiveEntries(ctx);
|
|
2511
|
-
const required =
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2644
|
+
const required = new Set(failures.filter(({ entry }) => entry === bootstrapIncludes.get(ctx) || requiredStartupEntryIds.has(entry.options.id)).map(({ entry }) => entry));
|
|
2645
|
+
if (required.size > 0) throw new StartupError(startupDiagnostic(binName, failures, required), failures.map(({ entry, outcome }) => ({
|
|
2646
|
+
id: entry.options.id,
|
|
2647
|
+
module: entry.options.name,
|
|
2648
|
+
required: required.has(entry),
|
|
2649
|
+
fiberState: entry.fiber?.state,
|
|
2650
|
+
outcome
|
|
2651
|
+
})));
|
|
2652
|
+
if (failures.length > 0) warn(activationDiagnostic(binName, failures));
|
|
2516
2653
|
}
|
|
2517
2654
|
/**
|
|
2518
2655
|
* Boot the Loader against `absoluteConfigPath` and return only after the whole
|
|
@@ -2539,13 +2676,28 @@ async function auditStartupEntries(ctx, binName, warn = (line) => void process.s
|
|
|
2539
2676
|
* complete plugin set.
|
|
2540
2677
|
* @returns the root context after the initial startup audit, or as soon as a
|
|
2541
2678
|
* surface disposed the tree while startup was still in flight.
|
|
2542
|
-
* @throws
|
|
2679
|
+
* @throws {@link StartupError} for an inactive required entry, including all inactive plugins in its message;
|
|
2680
|
+
* otherwise a labelled error after disposing the partial context — `host
|
|
2543
2681
|
* preparation failed` when `prepare` threw before any config-tree entry
|
|
2544
2682
|
* mounted, `plugin tree failed to load` afterwards. Cyclic causes terminate
|
|
2545
2683
|
* diagnostic traversal without replacing the original cause.
|
|
2546
2684
|
*/
|
|
2547
2685
|
async function boot(binName, absoluteConfigPath, patches, prepare, bareModuleBaseUrl) {
|
|
2548
2686
|
const ctx = new Context();
|
|
2687
|
+
const startupLogs = [];
|
|
2688
|
+
const diagnostics = new Context();
|
|
2689
|
+
diagnostics.logger = ctx.logger;
|
|
2690
|
+
diagnostics.logger.exporter({
|
|
2691
|
+
levels: { default: 2 },
|
|
2692
|
+
export: ({ ts, name, type, args }) => {
|
|
2693
|
+
if (type === "warn" || type === "error") startupLogs.push({
|
|
2694
|
+
ts,
|
|
2695
|
+
name,
|
|
2696
|
+
type,
|
|
2697
|
+
args
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2549
2701
|
let stage = "host preparation failed";
|
|
2550
2702
|
try {
|
|
2551
2703
|
ctx.baseUrl = pathToFileURL(dirname(absoluteConfigPath)).href + "/";
|
|
@@ -2568,6 +2720,13 @@ async function boot(binName, absoluteConfigPath, patches, prepare, bareModuleBas
|
|
|
2568
2720
|
return ctx;
|
|
2569
2721
|
} catch (cause) {
|
|
2570
2722
|
await ctx.fiber.dispose();
|
|
2723
|
+
if (cause instanceof StartupError) {
|
|
2724
|
+
cause.startup = {
|
|
2725
|
+
configurationPath: absoluteConfigPath,
|
|
2726
|
+
messages: startupLogs
|
|
2727
|
+
};
|
|
2728
|
+
throw cause;
|
|
2729
|
+
}
|
|
2571
2730
|
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
2572
2731
|
let deepest = cause;
|
|
2573
2732
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2577,6 +2736,8 @@ async function boot(binName, absoluteConfigPath, patches, prepare, bareModuleBas
|
|
|
2577
2736
|
}
|
|
2578
2737
|
const stack = deepest instanceof AggregateError ? `\n${deepest.stack ?? deepest.message}\n${deepest.errors.map(formatActivationError).join("\n")}` : deepest instanceof Error && deepest !== cause ? `\n${deepest.stack ?? deepest.message}` : "";
|
|
2579
2738
|
throw new Error(`${binName}: ${stage}: ${detail}${stack}`, { cause });
|
|
2739
|
+
} finally {
|
|
2740
|
+
await diagnostics.fiber.dispose();
|
|
2580
2741
|
}
|
|
2581
2742
|
}
|
|
2582
2743
|
/** Prompt-section name for the harness-source location line an app bin adds after boot. */
|
|
@@ -2606,4 +2767,4 @@ function addHarnessSourceSection(ctx, sourceRoot) {
|
|
|
2606
2767
|
});
|
|
2607
2768
|
}
|
|
2608
2769
|
//#endregion
|
|
2609
|
-
export { DEFAULT_PROFILE_BUNDLES,
|
|
2770
|
+
export { DEFAULT_PROFILE_BUNDLES, FAIL_LOUD_RELEASE_TIMEOUT_MS, HARNESS_SOURCE_SECTION, OPTIONAL_BUNDLES, PROFILES_DIR, PROFILE_PATCH_FILENAME, PROFILE_TEMPLATES, PluginPackages, StartupError, addHarnessSourceSection, auditStartupEntries, boot, composeEntries, createProfileResolutionGeneration, healIsolatedProfileModuleFallback, healProfilesModuleFallback, initProfile, installFailLoud, loadEnv, loadLayeredEnv, loadOptionalPatches, loadOverlayPatches, loadProfile, loadProfileDirectory, mountRootInclude, readProfileManifest, readProfilePatches, readProfilePlugins, reconcileProfilePatches, reconcileProfilePlugins, renderConfigDump, resolveBundleDir, resolveConfigPath, resolveProfileDir, resolveTelemetryPatch, sanitizeProfile, unlinkProfileModuleFallback, writeProfileBundles, writeProfileManifest };
|