@deepseek-ai/dsh-experimental-webworker-runtime 0.2.1-alpha.3 → 0.2.1-alpha.4
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 +4 -4
- package/README.md +9 -4
- package/README.zh.md +9 -4
- package/lib/index.js +447 -102
- package/lib/types/image-layout.d.ts +13 -4
- package/lib/types/image-manifest.d.ts +42 -0
- package/lib/types/index.d.ts +3 -2
- package/lib/types/module-system/module-loader.d.ts +20 -6
- package/lib/types/node/builtin_modules/implemented/path.d.ts +8 -2
- package/lib/types/node/builtin_modules/implemented/util.d.ts +7 -0
- package/lib/types/node/external_packages/node-addon-require-builtin.d.ts +8 -0
- package/lib/types/node/globals/process.d.ts +3 -3
- package/lib/types/node/internal_modules/esm-loader.d.ts +50 -0
- package/lib/types/node/internal_modules/module-map.d.ts +59 -0
- package/lib/types/node/internal_modules/package-surfaces.d.ts +29 -0
- package/lib/types/node/notImplementedFail.d.ts +7 -3
- package/lib/types/storage/memory.d.ts +23 -2
- package/lib/types/storage/paths.d.ts +1 -1
- package/lib/types/storage/types.d.ts +33 -0
- package/lib/types/worker-host.d.ts +8 -9
- package/lib/worker.js +683 -157
- package/package.json +20 -20
package/lib/index.js
CHANGED
|
@@ -142,6 +142,122 @@ function parseInboundFrame(data) {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
|
145
|
+
//#region src/node/notImplementedFail.ts
|
|
146
|
+
/** Node's standard code for a feature the running platform cannot provide. */
|
|
147
|
+
const FEATURE_UNAVAILABLE_ON_PLATFORM = "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM";
|
|
148
|
+
/**
|
|
149
|
+
* Build the refusal error and write it to the console first, for stubs that
|
|
150
|
+
* cannot be a plain throwing function (constructors, methods on structural
|
|
151
|
+
* fakes).
|
|
152
|
+
* @param module - module specifier being stubbed.
|
|
153
|
+
* @param symbol - unavailable member, named as the importer sees it.
|
|
154
|
+
* @returns the error to throw, with `code` set to Node's `ERR_FEATURE_UNAVAILABLE_ON_PLATFORM`.
|
|
155
|
+
*/
|
|
156
|
+
function notAvailableError(module, symbol) {
|
|
157
|
+
const message = `web-preview: ${module}.${symbol} is not available in the worker host`;
|
|
158
|
+
console.error(message);
|
|
159
|
+
return Object.assign(new Error(message), { code: FEATURE_UNAVAILABLE_ON_PLATFORM });
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/node/internal_modules/module-map.ts
|
|
163
|
+
/**
|
|
164
|
+
* `internal/modules/esm/module_map` for the Worker, following Node v24.18.0. The Worker evaluates no ES module
|
|
165
|
+
* jobs, so a {@link LoadCache} stays empty and refuses to store one.
|
|
166
|
+
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/internal_modules/module-map
|
|
167
|
+
*/
|
|
168
|
+
/** Job slot Node uses when an import names no `type` attribute. */
|
|
169
|
+
const IMPLICIT_TYPE_ATTRIBUTE = "javascript";
|
|
170
|
+
/**
|
|
171
|
+
* Node's `validateString` refusal.
|
|
172
|
+
* @param value - Argument to check.
|
|
173
|
+
* @param name - Argument name in the message.
|
|
174
|
+
*/
|
|
175
|
+
function validateString(value, name) {
|
|
176
|
+
if (typeof value === "string") return;
|
|
177
|
+
throw Object.assign(/* @__PURE__ */ new TypeError(`The "${name}" argument must be of type string. Received ${typeof value}`), { code: "ERR_INVALID_ARG_TYPE" });
|
|
178
|
+
}
|
|
179
|
+
/** Module jobs keyed by URL, then by import `type` attribute; always empty in the Worker. */
|
|
180
|
+
var LoadCache = class extends Map {
|
|
181
|
+
/**
|
|
182
|
+
* Read the job for one URL and type.
|
|
183
|
+
* @param url - Module URL.
|
|
184
|
+
* @param type - Import `type` attribute.
|
|
185
|
+
* @returns The cached job, if any.
|
|
186
|
+
*/
|
|
187
|
+
get(url, type = IMPLICIT_TYPE_ATTRIBUTE) {
|
|
188
|
+
validateString(url, "url");
|
|
189
|
+
validateString(type, "type");
|
|
190
|
+
return super.get(url)?.[type];
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Refuse to store a job: the Worker creates no ES module jobs.
|
|
194
|
+
* @param url - Module URL.
|
|
195
|
+
* @param type - Import `type` attribute.
|
|
196
|
+
* @param _job - Job Node would store.
|
|
197
|
+
* @returns Never; always throws.
|
|
198
|
+
*/
|
|
199
|
+
set(url, type = IMPLICIT_TYPE_ATTRIBUTE, _job) {
|
|
200
|
+
validateString(url, "url");
|
|
201
|
+
validateString(type, "type");
|
|
202
|
+
throw notAvailableError("internal/modules/esm/module_map", "LoadCache.set");
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Check for the job of one URL and type.
|
|
206
|
+
* @param url - Module URL.
|
|
207
|
+
* @param type - Import `type` attribute.
|
|
208
|
+
* @returns Whether a job is cached.
|
|
209
|
+
*/
|
|
210
|
+
has(url, type = IMPLICIT_TYPE_ATTRIBUTE) {
|
|
211
|
+
validateString(url, "url");
|
|
212
|
+
validateString(type, "type");
|
|
213
|
+
return super.get(url)?.[type] !== void 0;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/node/internal_modules/esm-loader.ts
|
|
218
|
+
/**
|
|
219
|
+
* `internal/modules/esm/loader` cascaded loader for the Worker. {@link createCascadedLoader} builds the object with
|
|
220
|
+
* the Node v24.18.0 (v2) loader members this repository calls: `resolveSync` and `import` over one Worker module
|
|
221
|
+
* loader, and an empty `loadCache`. ES module jobs are unavailable on this platform.
|
|
222
|
+
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/internal_modules/esm-loader
|
|
223
|
+
*/
|
|
224
|
+
/**
|
|
225
|
+
* Translate a Worker resolution failure into the code Node's ESM resolver reports.
|
|
226
|
+
* @param error - Failure thrown by the Worker module loader.
|
|
227
|
+
* @returns The same failure with an ESM resolution code.
|
|
228
|
+
*/
|
|
229
|
+
function esmResolutionError(error) {
|
|
230
|
+
if (error instanceof Error && error.code === "MODULE_NOT_FOUND") return Object.assign(error, { code: "ERR_MODULE_NOT_FOUND" });
|
|
231
|
+
return error;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Build the cascaded loader for one Worker module loader.
|
|
235
|
+
* @param source - The Worker module loader's resolution and loading operations.
|
|
236
|
+
* @returns A cascaded loader delegating to `source`.
|
|
237
|
+
*/
|
|
238
|
+
function createCascadedLoader(source) {
|
|
239
|
+
return {
|
|
240
|
+
loadCache: new LoadCache(),
|
|
241
|
+
resolveSync(parentURL, request) {
|
|
242
|
+
try {
|
|
243
|
+
const { url, format } = source.resolveSync(request.specifier, parentURL);
|
|
244
|
+
return {
|
|
245
|
+
url,
|
|
246
|
+
format
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw esmResolutionError(error);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
import(specifier, parentURL) {
|
|
253
|
+
return source.import(specifier, parentURL);
|
|
254
|
+
},
|
|
255
|
+
getOrCreateModuleJob() {
|
|
256
|
+
throw notAvailableError("internal/modules/esm/loader", "getOrCreateModuleJob");
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
145
261
|
//#region src/module-system/posix-path.ts
|
|
146
262
|
var posix_path_exports = /* @__PURE__ */ __exportAll({
|
|
147
263
|
SEP: () => "/",
|
|
@@ -306,8 +422,8 @@ function fileUrlToPath(url) {
|
|
|
306
422
|
//#region src/image-layout.ts
|
|
307
423
|
/**
|
|
308
424
|
* Image layout contract shared by the packer and the worker host: the virtual
|
|
309
|
-
* root, where the
|
|
310
|
-
* the working directories every image carries empty. One definition, two
|
|
425
|
+
* root, where the manifest sits inside the image, where lazy archives sit beside
|
|
426
|
+
* it, and the working directories every image carries empty. One definition, two
|
|
311
427
|
* consumers — the packer writes this layout, the worker host mounts it.
|
|
312
428
|
*/
|
|
313
429
|
/** Default virtual root; the runtime mounts the image here unless told otherwise. */
|
|
@@ -318,12 +434,21 @@ const DEFAULT_ROOT = "/dsh";
|
|
|
318
434
|
* there, so the extension is part of what a deployment serves.
|
|
319
435
|
*/
|
|
320
436
|
const IMAGE_FILE_NAME = "vfs-image.tar.gz";
|
|
321
|
-
/** Image path the composed profile is written to; the runtime's Loader reads it. */
|
|
322
|
-
const IMAGE_CONFIG_PATH = "config/cordis.yml";
|
|
323
437
|
/** Image path of the manifest the runtime reads before it wraps a single module. */
|
|
324
438
|
const IMAGE_MANIFEST_PATH = "config/vfs-manifest.json";
|
|
439
|
+
/**
|
|
440
|
+
* Directory, relative to the base image URL, holding the lazy chunk archives:
|
|
441
|
+
* `lazy/<sha256 of the archive>.tar.gz`, one page chunk each.
|
|
442
|
+
*/
|
|
443
|
+
const IMAGE_LAZY_DIRECTORY = "lazy";
|
|
325
444
|
/** Home directory under the root; the process shim's `DSH_HOME`/`HOME` default. */
|
|
326
445
|
const IMAGE_HOME_DIRECTORY = "home";
|
|
446
|
+
/**
|
|
447
|
+
* Config fields the Worker requires on Loader rows, by row id. The packer writes them into the image's home patch
|
|
448
|
+
* (`home/cordis.patch.yml`) over each row's composed config: the Worker carries no Zstandard codec, so JSONL Session
|
|
449
|
+
* logs stay plaintext.
|
|
450
|
+
*/
|
|
451
|
+
const PLATFORM_ENTRY_CONFIG = { "session-persistence-jsonl": { compression: "none" } };
|
|
327
452
|
/** Working directories the host tree expects to exist, empty. */
|
|
328
453
|
const IMAGE_EMPTY_DIRECTORIES = [
|
|
329
454
|
"home/",
|
|
@@ -361,10 +486,11 @@ const WRAPPER_PARAMS = [
|
|
|
361
486
|
//#endregion
|
|
362
487
|
//#region src/module-system/module-loader.ts
|
|
363
488
|
/**
|
|
364
|
-
* CommonJS module loader over the worker VFS. It
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
489
|
+
* CommonJS module loader over the worker VFS. It backs the Node internal ESM
|
|
490
|
+
* loader that `node-addon-require-builtin` answers, which Cordis's Loader uses
|
|
491
|
+
* for every entry import, and the `node:module` `createRequire` proxy that
|
|
492
|
+
* `typert-loader`, `client-modules`, and the plugin package inventory resolve
|
|
493
|
+
* package metadata through.
|
|
368
494
|
*
|
|
369
495
|
* Resolution is a narrowed Node `require` algorithm: `exports` walk with a
|
|
370
496
|
* fixed condition order, extension probing, and one cache keyed by resolved
|
|
@@ -400,9 +526,11 @@ var WorkerModuleLoader = class {
|
|
|
400
526
|
modules = /* @__PURE__ */ new Map();
|
|
401
527
|
manifests = /* @__PURE__ */ new Map();
|
|
402
528
|
stack = [];
|
|
529
|
+
cascaded;
|
|
403
530
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
531
|
+
* Resolution and loading over this mount, in Node's v1 argument order. The cascaded ESM loader
|
|
532
|
+
* ({@link cascadedLoader}) and the packer call it; `parentURL` positions relative specifiers and import attributes
|
|
533
|
+
* are ignored.
|
|
406
534
|
*/
|
|
407
535
|
internal;
|
|
408
536
|
constructor(options) {
|
|
@@ -434,9 +562,22 @@ var WorkerModuleLoader = class {
|
|
|
434
562
|
resolveSync: resolveInternal
|
|
435
563
|
};
|
|
436
564
|
}
|
|
437
|
-
|
|
565
|
+
/**
|
|
566
|
+
* The `internal/modules/esm/loader` cascaded loader over this mount, created on first use.
|
|
567
|
+
* @returns This loader's one cascaded loader.
|
|
568
|
+
*/
|
|
569
|
+
cascadedLoader() {
|
|
570
|
+
return this.cascaded ??= createCascadedLoader(this.internal);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Throw this loader's diagnostic.
|
|
574
|
+
* @param detail - What failed.
|
|
575
|
+
* @param code - Node error code for a resolution failure Node also reports by code.
|
|
576
|
+
*/
|
|
577
|
+
fail(detail, code) {
|
|
438
578
|
const chain = this.stack.length === 0 ? "" : ` (importer chain: ${this.stack.join(" -> ")})`;
|
|
439
|
-
|
|
579
|
+
const error = /* @__PURE__ */ new Error(`webworker modules: ${detail}${chain}`);
|
|
580
|
+
throw code === void 0 ? error : Object.assign(error, { code });
|
|
440
581
|
}
|
|
441
582
|
/** @returns Directory a base path or URL resolves specifiers from. */
|
|
442
583
|
baseDirectoryOf(base) {
|
|
@@ -517,7 +658,7 @@ var WorkerModuleLoader = class {
|
|
|
517
658
|
}
|
|
518
659
|
return this.probe(join(path, "index"), specifier);
|
|
519
660
|
}
|
|
520
|
-
return this.fail(`cannot resolve "${specifier}": no file at ${candidates.join(", ")}
|
|
661
|
+
return this.fail(`cannot resolve "${specifier}": no file at ${candidates.join(", ")}`, "MODULE_NOT_FOUND");
|
|
521
662
|
}
|
|
522
663
|
/** @returns The Worker-provided implementation of a static specifier. */
|
|
523
664
|
staticModule(specifier) {
|
|
@@ -556,12 +697,12 @@ var WorkerModuleLoader = class {
|
|
|
556
697
|
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? specifier;
|
|
557
698
|
const rest = specifier.slice(packageName.length).replace(/^\//, "");
|
|
558
699
|
const packageDirectory = join(this.root, "node_modules", packageName);
|
|
559
|
-
if (!this.vfs.existsSync(join(packageDirectory, "package.json"))) return this.fail(`cannot resolve "${specifier}": ${packageDirectory}/package.json is not in the image
|
|
700
|
+
if (!this.vfs.existsSync(join(packageDirectory, "package.json"))) return this.fail(`cannot resolve "${specifier}": ${packageDirectory}/package.json is not in the image`, "MODULE_NOT_FOUND");
|
|
560
701
|
const manifest = this.manifestOf(packageDirectory);
|
|
561
702
|
const subpath = rest === "" ? "." : `./${rest}`;
|
|
562
703
|
if (manifest.exports !== void 0) {
|
|
563
704
|
const target = this.selectExport(manifest.exports, subpath, packageName);
|
|
564
|
-
if (target === void 0) return this.fail(`"${packageName}" does not export "${subpath}" under conditions [${[...this.conditions].join(", ")}]
|
|
705
|
+
if (target === void 0) return this.fail(`"${packageName}" does not export "${subpath}" under conditions [${[...this.conditions].join(", ")}]`, "ERR_PACKAGE_PATH_NOT_EXPORTED");
|
|
565
706
|
return {
|
|
566
707
|
kind: "file",
|
|
567
708
|
path: this.probe(join(packageDirectory, target), specifier)
|
|
@@ -751,6 +892,7 @@ const MODULE_PROXIES = {
|
|
|
751
892
|
"got": "./node/external_packages/got.ts",
|
|
752
893
|
"@vscode/ripgrep": "./node/external_packages/ripgrep.ts",
|
|
753
894
|
"@earendil-works/pi-ai": "./node/external_packages/pi-ai.ts",
|
|
895
|
+
"node-addon-require-builtin": "./node/external_packages/node-addon-require-builtin.ts",
|
|
754
896
|
"ws": "./node/external_packages/ws.ts"
|
|
755
897
|
};
|
|
756
898
|
/** pi-ai subpaths (`/providers/all`, `/api/*.lazy`) share the one structural stub. */
|
|
@@ -773,6 +915,7 @@ const REPLACED_EXTERNAL_PACKAGES = [
|
|
|
773
915
|
"node-pty",
|
|
774
916
|
"execa",
|
|
775
917
|
"got",
|
|
918
|
+
"node-addon-require-builtin",
|
|
776
919
|
"sharp",
|
|
777
920
|
"ws"
|
|
778
921
|
];
|
|
@@ -1991,19 +2134,21 @@ function signalProcess(pid, signal) {
|
|
|
1991
2134
|
/**
|
|
1992
2135
|
* The `process` global the worker needs before any VFS module runs. Cordis
|
|
1993
2136
|
* reads `process.env` and `process.versions.node` while the Loader is
|
|
1994
|
-
* constructed, and
|
|
1995
|
-
* configuration bytes stay identical to the Node deployment. Third-party Node
|
|
2137
|
+
* constructed, and bundle patch rows keep their `!!js process.*` expressions, so
|
|
2138
|
+
* the configuration bytes stay identical to the Node deployment. Third-party Node
|
|
1996
2139
|
* packages use the presence of `process.title` to avoid browser-only globals.
|
|
1997
2140
|
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/globals/process
|
|
1998
2141
|
*/
|
|
1999
2142
|
/** Virtual executable identity; the worker has no Node binary behind it. */
|
|
2000
2143
|
const EXEC_PATH = "/dsh/bin/node";
|
|
2144
|
+
/** Node release whose internal-module members `node-addon-require-builtin` follows in the Worker. */
|
|
2145
|
+
const NODE_VERSION = "24.18.0";
|
|
2001
2146
|
/**
|
|
2002
2147
|
* Publish `globalThis.process`.
|
|
2003
2148
|
*
|
|
2004
|
-
* `versions.node`
|
|
2005
|
-
* `ModuleLoader.fromInternal()`
|
|
2006
|
-
* internals
|
|
2149
|
+
* `versions.node` names the Node release the Worker's `node-addon-require-builtin` internals follow, so Cordis's
|
|
2150
|
+
* `ModuleLoader.fromInternal()` reaches them the way it reaches Node's; `execArgv` stays empty, without
|
|
2151
|
+
* `--expose-internals`.
|
|
2007
2152
|
* @param options - Root, environment, and argument vector.
|
|
2008
2153
|
* @returns The published object, for the module proxy table.
|
|
2009
2154
|
*/
|
|
@@ -2022,8 +2167,8 @@ function installProcessGlobal(options) {
|
|
|
2022
2167
|
platform: "linux",
|
|
2023
2168
|
arch: "x64",
|
|
2024
2169
|
pid: 1,
|
|
2025
|
-
version:
|
|
2026
|
-
versions: { node:
|
|
2170
|
+
version: `v${NODE_VERSION}`,
|
|
2171
|
+
versions: { node: NODE_VERSION },
|
|
2027
2172
|
cwd: () => options.cwd,
|
|
2028
2173
|
getBuiltinModule: (id) => {
|
|
2029
2174
|
let resolution;
|
|
@@ -2417,6 +2562,8 @@ function resize(bytes, length) {
|
|
|
2417
2562
|
*/
|
|
2418
2563
|
var MemoryVfs = class {
|
|
2419
2564
|
files = /* @__PURE__ */ new Map();
|
|
2565
|
+
/** Files a lazy archive supplies on their first asynchronous read. */
|
|
2566
|
+
deferred = /* @__PURE__ */ new Map();
|
|
2420
2567
|
directories = new Set(["/"]);
|
|
2421
2568
|
/** Directory permission bits; absence means {@link DEFAULT_DIRECTORY_MODE}. */
|
|
2422
2569
|
directoryModes = /* @__PURE__ */ new Map();
|
|
@@ -2465,7 +2612,10 @@ var MemoryVfs = class {
|
|
|
2465
2612
|
}
|
|
2466
2613
|
/** Promise face mirroring `node:fs/promises` for the methods the roster uses. */
|
|
2467
2614
|
promises = {
|
|
2468
|
-
readFile: async (path, options) =>
|
|
2615
|
+
readFile: async (path, options) => {
|
|
2616
|
+
await this.whenReadable(path);
|
|
2617
|
+
return this.readFileSync(path, options);
|
|
2618
|
+
},
|
|
2469
2619
|
writeFile: async (path, data, options) => {
|
|
2470
2620
|
this.writeFileSync(path, data, options);
|
|
2471
2621
|
},
|
|
@@ -2497,11 +2647,13 @@ var MemoryVfs = class {
|
|
|
2497
2647
|
this.chmodSync(path, mode);
|
|
2498
2648
|
},
|
|
2499
2649
|
opendir: async (path) => this.opendir(path),
|
|
2500
|
-
open: async (path, flags, mode) =>
|
|
2650
|
+
open: async (path, flags, mode) => {
|
|
2651
|
+
await this.whenReadable(path);
|
|
2652
|
+
return this.open(path, flags, mode);
|
|
2653
|
+
},
|
|
2501
2654
|
/** Resolves for any existing path: the VFS grants read and write to everything it holds. */
|
|
2502
2655
|
access: async (path) => {
|
|
2503
|
-
|
|
2504
|
-
if (!this.files.has(target) && !this.directories.has(target)) fail("ENOENT", "access", target);
|
|
2656
|
+
if (!this.existsSync(path)) fail("ENOENT", "access", normalize(resolve(path)));
|
|
2505
2657
|
}
|
|
2506
2658
|
};
|
|
2507
2659
|
/** @returns Absolute path with no trailing separator. */
|
|
@@ -2510,6 +2662,101 @@ var MemoryVfs = class {
|
|
|
2510
2662
|
return absolute.length > 1 && absolute.endsWith("/") ? absolute.slice(0, -1) : absolute;
|
|
2511
2663
|
}
|
|
2512
2664
|
/**
|
|
2665
|
+
* Register files whose bytes arrive with one archive on the first
|
|
2666
|
+
* asynchronous read of any of them. Their parent directories are created now.
|
|
2667
|
+
* @param archive - Archive name, mount root, registered files, and fetch.
|
|
2668
|
+
* @throws When a registered path already holds a file, directory, or another registration.
|
|
2669
|
+
*/
|
|
2670
|
+
deferArchive(archive) {
|
|
2671
|
+
const root = this.key(archive.root);
|
|
2672
|
+
const files = /* @__PURE__ */ new Map();
|
|
2673
|
+
for (const file of archive.files) {
|
|
2674
|
+
const target = this.key(join(root, file.path));
|
|
2675
|
+
if (this.files.has(target) || this.directories.has(target) || this.deferred.has(target) || files.has(target)) throw new Error(`webworker vfs: ${target} is already registered, so lazy archive ${archive.name} cannot supply it`);
|
|
2676
|
+
files.set(target, file.size);
|
|
2677
|
+
}
|
|
2678
|
+
const state = {
|
|
2679
|
+
name: archive.name,
|
|
2680
|
+
root,
|
|
2681
|
+
load: () => archive.load(),
|
|
2682
|
+
files,
|
|
2683
|
+
fetching: void 0
|
|
2684
|
+
};
|
|
2685
|
+
const mtimeMs = Date.now();
|
|
2686
|
+
for (const [target, size] of files) {
|
|
2687
|
+
this.seedDirectory(dirname(target));
|
|
2688
|
+
this.deferred.set(target, {
|
|
2689
|
+
size,
|
|
2690
|
+
mode: DEFAULT_FILE_MODE,
|
|
2691
|
+
mtimeMs,
|
|
2692
|
+
archive: state
|
|
2693
|
+
});
|
|
2694
|
+
this.touchDirectory(dirname(target));
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Fetch the lazy archive that supplies a registered path. Concurrent readers
|
|
2699
|
+
* of one archive share one fetch; a failed fetch leaves its files registered,
|
|
2700
|
+
* so the next read fetches again.
|
|
2701
|
+
* @param path - Path about to be read asynchronously.
|
|
2702
|
+
* @returns A promise that settles once the archive arrived, or undefined when the path needs no fetch.
|
|
2703
|
+
*/
|
|
2704
|
+
whenReadable(path) {
|
|
2705
|
+
const file = this.deferred.get(this.key(path));
|
|
2706
|
+
if (file === void 0) return void 0;
|
|
2707
|
+
file.archive.fetching ??= this.fetchArchive(file.archive);
|
|
2708
|
+
return file.archive.fetching;
|
|
2709
|
+
}
|
|
2710
|
+
/** Fetch, validate against the registration, and mount one archive's files. */
|
|
2711
|
+
async fetchArchive(state) {
|
|
2712
|
+
try {
|
|
2713
|
+
let archive;
|
|
2714
|
+
try {
|
|
2715
|
+
archive = await state.load();
|
|
2716
|
+
} catch (error) {
|
|
2717
|
+
throw new Error(`webworker vfs: lazy archive ${state.name} could not be fetched`, { cause: error });
|
|
2718
|
+
}
|
|
2719
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2720
|
+
for (const entry of parseTar(archive)) {
|
|
2721
|
+
if (entry.directory) continue;
|
|
2722
|
+
const relativeName = entry.name.startsWith("./") ? entry.name.slice(2) : entry.name;
|
|
2723
|
+
const target = this.key(join(state.root, relativeName));
|
|
2724
|
+
const size = state.files.get(target);
|
|
2725
|
+
if (size === void 0) throw new Error(`webworker vfs: lazy archive ${state.name} carries unregistered entry "${relativeName}"`);
|
|
2726
|
+
if (entry.bytes.length !== size) throw new Error(`webworker vfs: lazy archive ${state.name} holds ${String(entry.bytes.length)} bytes for ${target}, registered as ${String(size)}`);
|
|
2727
|
+
entries.set(target, entry.bytes);
|
|
2728
|
+
}
|
|
2729
|
+
for (const target of state.files.keys()) if (!entries.has(target)) throw new Error(`webworker vfs: lazy archive ${state.name} lacks registered file ${target}`);
|
|
2730
|
+
for (const [target, bytes] of entries) {
|
|
2731
|
+
const file = this.deferred.get(target);
|
|
2732
|
+
if (file?.archive !== state) continue;
|
|
2733
|
+
this.deferred.delete(target);
|
|
2734
|
+
const identity = this.identities.get(target);
|
|
2735
|
+
this.identities.delete(target);
|
|
2736
|
+
this.setFile(target, {
|
|
2737
|
+
bytes,
|
|
2738
|
+
mtimeMs: file.mtimeMs,
|
|
2739
|
+
mode: file.mode,
|
|
2740
|
+
paths: void 0,
|
|
2741
|
+
...identity === void 0 ? {} : { identity }
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
} catch (error) {
|
|
2745
|
+
state.fetching = void 0;
|
|
2746
|
+
throw error;
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
/** Refuse a synchronous operation on a registered file whose bytes have not arrived. */
|
|
2750
|
+
requireArrived(syscall, target) {
|
|
2751
|
+
const file = this.deferred.get(target);
|
|
2752
|
+
if (file === void 0) return;
|
|
2753
|
+
const error = /* @__PURE__ */ new Error(`ERR_DSH_VFS_DEFERRED: ${syscall} cannot wait for '${target}', which lazy archive ${file.archive.name} supplies on its first asynchronous read`);
|
|
2754
|
+
error.code = "ERR_DSH_VFS_DEFERRED";
|
|
2755
|
+
error.path = target;
|
|
2756
|
+
error.syscall = syscall;
|
|
2757
|
+
throw error;
|
|
2758
|
+
}
|
|
2759
|
+
/**
|
|
2513
2760
|
* Read a file.
|
|
2514
2761
|
* @param path - File path.
|
|
2515
2762
|
* @param options - `'utf8'` or `{encoding}` for text; omitted for bytes.
|
|
@@ -2517,6 +2764,7 @@ var MemoryVfs = class {
|
|
|
2517
2764
|
*/
|
|
2518
2765
|
readFileSync(path, options) {
|
|
2519
2766
|
const target = this.key(path);
|
|
2767
|
+
this.requireArrived("open", target);
|
|
2520
2768
|
const node = this.files.get(target);
|
|
2521
2769
|
if (node === void 0) {
|
|
2522
2770
|
if (this.directories.has(target)) fail("EISDIR", "read", target);
|
|
@@ -2531,7 +2779,7 @@ var MemoryVfs = class {
|
|
|
2531
2779
|
*/
|
|
2532
2780
|
existsSync(path) {
|
|
2533
2781
|
const target = this.key(path);
|
|
2534
|
-
return this.files.has(target) || this.directories.has(target);
|
|
2782
|
+
return this.files.has(target) || this.directories.has(target) || this.deferred.has(target);
|
|
2535
2783
|
}
|
|
2536
2784
|
/**
|
|
2537
2785
|
* Stat a path.
|
|
@@ -2542,11 +2790,17 @@ var MemoryVfs = class {
|
|
|
2542
2790
|
statSync(path, options) {
|
|
2543
2791
|
const target = this.key(path);
|
|
2544
2792
|
const node = this.files.get(target);
|
|
2793
|
+
const deferred = this.deferred.get(target);
|
|
2545
2794
|
const [size, mtimeMs, directory, mode] = node !== void 0 ? [
|
|
2546
2795
|
node.bytes.length,
|
|
2547
2796
|
node.mtimeMs,
|
|
2548
2797
|
false,
|
|
2549
2798
|
node.mode
|
|
2799
|
+
] : deferred !== void 0 ? [
|
|
2800
|
+
deferred.size,
|
|
2801
|
+
deferred.mtimeMs,
|
|
2802
|
+
false,
|
|
2803
|
+
deferred.mode
|
|
2550
2804
|
] : this.directories.has(target) ? [
|
|
2551
2805
|
0,
|
|
2552
2806
|
this.directoryMtimes.get(target) ?? 0,
|
|
@@ -2716,12 +2970,16 @@ var MemoryVfs = class {
|
|
|
2716
2970
|
readdirSync(path, options) {
|
|
2717
2971
|
const target = this.key(path);
|
|
2718
2972
|
if (!this.directories.has(target)) {
|
|
2719
|
-
if (this.files.has(target)) fail("ENOTDIR", "scandir", target);
|
|
2973
|
+
if (this.files.has(target) || this.deferred.has(target)) fail("ENOTDIR", "scandir", target);
|
|
2720
2974
|
fail("ENOENT", "scandir", target);
|
|
2721
2975
|
}
|
|
2722
2976
|
const prefix = target === "/" ? "/" : `${target}/`;
|
|
2723
2977
|
const names = /* @__PURE__ */ new Set();
|
|
2724
|
-
for (const candidate of [
|
|
2978
|
+
for (const candidate of [
|
|
2979
|
+
...this.files.keys(),
|
|
2980
|
+
...this.deferred.keys(),
|
|
2981
|
+
...this.directories
|
|
2982
|
+
]) {
|
|
2725
2983
|
if (!candidate.startsWith(prefix) || candidate === target) continue;
|
|
2726
2984
|
const rest = candidate.slice(prefix.length);
|
|
2727
2985
|
if (rest === "") continue;
|
|
@@ -2761,7 +3019,7 @@ var MemoryVfs = class {
|
|
|
2761
3019
|
*/
|
|
2762
3020
|
mkdirSync(path, options) {
|
|
2763
3021
|
const target = this.key(path);
|
|
2764
|
-
if (this.files.has(target)) fail("EEXIST", "mkdir", target);
|
|
3022
|
+
if (this.files.has(target) || this.deferred.has(target)) fail("EEXIST", "mkdir", target);
|
|
2765
3023
|
if (this.directories.has(target)) {
|
|
2766
3024
|
if (options?.recursive === true) return void 0;
|
|
2767
3025
|
fail("EEXIST", "mkdir", target);
|
|
@@ -2791,6 +3049,7 @@ var MemoryVfs = class {
|
|
|
2791
3049
|
*/
|
|
2792
3050
|
writeFileSync(path, data, options) {
|
|
2793
3051
|
const target = this.key(path);
|
|
3052
|
+
this.requireArrived("open", target);
|
|
2794
3053
|
if (this.directories.has(target)) fail("EISDIR", "open", target);
|
|
2795
3054
|
if (!this.directories.has(dirname(target))) fail("ENOENT", "open", target);
|
|
2796
3055
|
const flag = options?.flag ?? "w";
|
|
@@ -2921,6 +3180,7 @@ var MemoryVfs = class {
|
|
|
2921
3180
|
*/
|
|
2922
3181
|
openFileSync(path, flags = "r", mode) {
|
|
2923
3182
|
const target = this.key(path);
|
|
3183
|
+
this.requireArrived("open", target);
|
|
2924
3184
|
const access = openMode(flags);
|
|
2925
3185
|
const existing = this.files.get(target);
|
|
2926
3186
|
if (this.directories.has(target)) fail("EISDIR", "open", target);
|
|
@@ -2982,6 +3242,7 @@ var MemoryVfs = class {
|
|
|
2982
3242
|
*/
|
|
2983
3243
|
appendFileSync(path, data) {
|
|
2984
3244
|
const target = this.key(path);
|
|
3245
|
+
this.requireArrived("open", target);
|
|
2985
3246
|
const existing = this.files.get(target);
|
|
2986
3247
|
const addition = typeof data === "string" ? encoder.encode(data) : data;
|
|
2987
3248
|
if (existing === void 0) {
|
|
@@ -2998,6 +3259,8 @@ var MemoryVfs = class {
|
|
|
2998
3259
|
renameSync(from, to) {
|
|
2999
3260
|
const source = this.key(from);
|
|
3000
3261
|
const destination = this.key(to);
|
|
3262
|
+
this.requireArrived("rename", source);
|
|
3263
|
+
this.requireArrived("rename", destination);
|
|
3001
3264
|
if (source === destination) return;
|
|
3002
3265
|
const node = this.files.get(source);
|
|
3003
3266
|
if (node !== void 0) {
|
|
@@ -3026,13 +3289,14 @@ var MemoryVfs = class {
|
|
|
3026
3289
|
if (!this.directories.has(source)) fail("ENOENT", "rename", source);
|
|
3027
3290
|
if (this.files.has(destination)) fail("ENOTDIR", "rename", destination);
|
|
3028
3291
|
if (!this.directories.has(dirname(destination))) fail("ENOENT", "rename", destination);
|
|
3292
|
+
const prefix = `${source}/`;
|
|
3293
|
+
for (const registered of this.deferred.keys()) if (registered.startsWith(prefix)) this.requireArrived("rename", registered);
|
|
3029
3294
|
if (this.directories.has(destination)) {
|
|
3030
3295
|
if (this.readdirSync(destination).length > 0) fail("ENOTEMPTY", "rename", destination);
|
|
3031
3296
|
this.directories.delete(destination);
|
|
3032
3297
|
this.directoryModes.delete(destination);
|
|
3033
3298
|
this.directoryMtimes.delete(destination);
|
|
3034
3299
|
}
|
|
3035
|
-
const prefix = `${source}/`;
|
|
3036
3300
|
const movedFiles = [];
|
|
3037
3301
|
for (const [candidate, value] of [...this.files]) {
|
|
3038
3302
|
if (!candidate.startsWith(prefix)) continue;
|
|
@@ -3094,9 +3358,10 @@ var MemoryVfs = class {
|
|
|
3094
3358
|
linkSync(existing, next) {
|
|
3095
3359
|
const source = this.key(existing);
|
|
3096
3360
|
const target = this.key(next);
|
|
3361
|
+
this.requireArrived("link", source);
|
|
3097
3362
|
const node = this.files.get(source);
|
|
3098
3363
|
if (node === void 0) fail("ENOENT", "link", source);
|
|
3099
|
-
if (this.
|
|
3364
|
+
if (this.existsSync(target)) fail("EEXIST", "link", target);
|
|
3100
3365
|
if (!this.directories.has(dirname(target))) fail("ENOENT", "link", target);
|
|
3101
3366
|
this.setFile(target, node);
|
|
3102
3367
|
this.touchDirectory(dirname(target));
|
|
@@ -3115,6 +3380,7 @@ var MemoryVfs = class {
|
|
|
3115
3380
|
*/
|
|
3116
3381
|
truncateSync(path, length = 0) {
|
|
3117
3382
|
const target = this.key(path);
|
|
3383
|
+
this.requireArrived("truncate", target);
|
|
3118
3384
|
const node = this.files.get(target);
|
|
3119
3385
|
if (node === void 0) fail("ENOENT", "truncate", target);
|
|
3120
3386
|
this.truncateFile(node, length);
|
|
@@ -3126,6 +3392,7 @@ var MemoryVfs = class {
|
|
|
3126
3392
|
*/
|
|
3127
3393
|
chmodSync(path, mode) {
|
|
3128
3394
|
const target = this.key(path);
|
|
3395
|
+
this.requireArrived("chmod", target);
|
|
3129
3396
|
const node = this.files.get(target);
|
|
3130
3397
|
if (node !== void 0) {
|
|
3131
3398
|
this.chmodFile(node, mode);
|
|
@@ -3149,7 +3416,7 @@ var MemoryVfs = class {
|
|
|
3149
3416
|
*/
|
|
3150
3417
|
unlinkSync(path) {
|
|
3151
3418
|
const target = this.key(path);
|
|
3152
|
-
if (this.deleteFile(target) === void 0) fail("ENOENT", "unlink", target);
|
|
3419
|
+
if (this.deleteFile(target) === void 0 && !this.deferred.delete(target)) fail("ENOENT", "unlink", target);
|
|
3153
3420
|
this.forgetIdentity(target);
|
|
3154
3421
|
this.touchDirectory(dirname(target));
|
|
3155
3422
|
this.publish({
|
|
@@ -3164,7 +3431,7 @@ var MemoryVfs = class {
|
|
|
3164
3431
|
*/
|
|
3165
3432
|
rmSync(path, options) {
|
|
3166
3433
|
const target = this.key(path);
|
|
3167
|
-
if (this.deleteFile(target) !== void 0) {
|
|
3434
|
+
if (this.deleteFile(target) !== void 0 || this.deferred.delete(target)) {
|
|
3168
3435
|
this.forgetIdentity(target);
|
|
3169
3436
|
this.touchDirectory(dirname(target));
|
|
3170
3437
|
this.publish({
|
|
@@ -3177,6 +3444,7 @@ var MemoryVfs = class {
|
|
|
3177
3444
|
if (options?.recursive !== true) fail("ERR_FS_EISDIR", "rm", target);
|
|
3178
3445
|
const prefix = `${target}/`;
|
|
3179
3446
|
for (const candidate of [...this.files.keys()]) if (candidate.startsWith(prefix)) this.deleteFile(candidate);
|
|
3447
|
+
for (const candidate of [...this.deferred.keys()]) if (candidate.startsWith(prefix)) this.deferred.delete(candidate);
|
|
3180
3448
|
for (const candidate of [...this.directories]) {
|
|
3181
3449
|
if (!candidate.startsWith(prefix)) continue;
|
|
3182
3450
|
this.directories.delete(candidate);
|
|
@@ -3216,6 +3484,7 @@ var MemoryVfs = class {
|
|
|
3216
3484
|
seed(path, data, options = {}) {
|
|
3217
3485
|
const target = this.key(path);
|
|
3218
3486
|
this.seedDirectory(dirname(target));
|
|
3487
|
+
this.deferred.delete(target);
|
|
3219
3488
|
this.setFile(target, {
|
|
3220
3489
|
bytes: typeof data === "string" ? encoder.encode(data) : data,
|
|
3221
3490
|
mtimeMs: options.mtimeMs ?? this.touch(target),
|
|
@@ -3234,7 +3503,7 @@ var MemoryVfs = class {
|
|
|
3234
3503
|
if (!this.directories.has(target)) {
|
|
3235
3504
|
const parent = dirname(target);
|
|
3236
3505
|
if (parent !== target) this.seedDirectory(parent);
|
|
3237
|
-
if (this.files.has(target)) fail("EEXIST", "mkdir", target);
|
|
3506
|
+
if (this.files.has(target) || this.deferred.has(target)) fail("EEXIST", "mkdir", target);
|
|
3238
3507
|
this.directories.add(target);
|
|
3239
3508
|
this.directoryMtimes.set(target, options.mtimeMs ?? Date.now());
|
|
3240
3509
|
this.touchDirectory(parent);
|
|
@@ -3259,7 +3528,7 @@ var MemoryVfs = class {
|
|
|
3259
3528
|
/**
|
|
3260
3529
|
* Mount a tar image produced by the build-time collector.
|
|
3261
3530
|
*
|
|
3262
|
-
* Entry names are relative to `root` (`node_modules/...`, `config/
|
|
3531
|
+
* Entry names are relative to `root` (`node_modules/...`, `config/vfs-manifest.json`);
|
|
3263
3532
|
* an absolute entry name is a collector defect and fails loud. File contents
|
|
3264
3533
|
* stay views into `image` — nothing is copied at mount time.
|
|
3265
3534
|
* @param image - The ustar archive, as `inflateImage` produces it from the fetched image.
|
|
@@ -3327,6 +3596,72 @@ function requireActiveVfs() {
|
|
|
3327
3596
|
if (active === void 0) throw new Error("webworker vfs: no filesystem is mounted; the worker entry must call setActiveVfs before any node:fs access");
|
|
3328
3597
|
return active;
|
|
3329
3598
|
}
|
|
3599
|
+
//#endregion
|
|
3600
|
+
//#region src/image-manifest.ts
|
|
3601
|
+
/**
|
|
3602
|
+
* The base image's manifest: what the packer records about one image and what
|
|
3603
|
+
* the worker host requires before it wraps a module or boots the tree. Both
|
|
3604
|
+
* sides use these types; the worker validates the fetched file with
|
|
3605
|
+
* {@link parseImageManifest}.
|
|
3606
|
+
*/
|
|
3607
|
+
function recordOf$1(value) {
|
|
3608
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
3609
|
+
}
|
|
3610
|
+
/** A relative path made of non-empty segments without traversal. */
|
|
3611
|
+
function isRelativePath(value) {
|
|
3612
|
+
return typeof value === "string" && value !== "" && !value.startsWith("/") && value.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
3613
|
+
}
|
|
3614
|
+
function isCount(value) {
|
|
3615
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3616
|
+
}
|
|
3617
|
+
/**
|
|
3618
|
+
* Validate a fetched base-image manifest.
|
|
3619
|
+
* @param value - Parsed manifest JSON.
|
|
3620
|
+
* @param path - Manifest path inside the image, named in refusals.
|
|
3621
|
+
* @returns The detached manifest.
|
|
3622
|
+
* @throws When the manifest is malformed, or its bodies were lowered against another wrapper contract.
|
|
3623
|
+
*/
|
|
3624
|
+
function parseImageManifest(value, path) {
|
|
3625
|
+
const record = recordOf$1(value);
|
|
3626
|
+
if (record === void 0) throw new Error(`webworker host: ${path} does not hold an object`);
|
|
3627
|
+
if (record.lowered !== "dsh-worker-transform/1") throw new Error(`webworker host: image was lowered by ${String(record.lowered)}, this build runs ${LOWERING_VERSION}; rebuild the image`);
|
|
3628
|
+
const { root, profile, installation, javascriptEntries, visitedEntries, rewrittenEntries, lazy } = record;
|
|
3629
|
+
if (typeof root !== "string" || typeof profile !== "string" || profile === "" || installation !== void 0 && (typeof installation !== "string" || installation === "") || !isCount(javascriptEntries) || !isCount(visitedEntries) || !isCount(rewrittenEntries) || !Array.isArray(lazy)) throw new Error(`webworker host: ${path} lacks a valid root, profile, installation, entry count, or lazy archive list`);
|
|
3630
|
+
const archives = [];
|
|
3631
|
+
const archiveNames = /* @__PURE__ */ new Set();
|
|
3632
|
+
const filePaths = /* @__PURE__ */ new Set();
|
|
3633
|
+
for (const entry of lazy) {
|
|
3634
|
+
const archive = recordOf$1(entry);
|
|
3635
|
+
const name = archive?.archive;
|
|
3636
|
+
const files = archive?.files;
|
|
3637
|
+
if (!isRelativePath(name) || archiveNames.has(name) || !Array.isArray(files) || files.length === 0) throw new Error(`webworker host: ${path} lists an invalid or repeated lazy archive`);
|
|
3638
|
+
archiveNames.add(name);
|
|
3639
|
+
const parsed = [];
|
|
3640
|
+
for (const candidate of files) {
|
|
3641
|
+
const file = recordOf$1(candidate);
|
|
3642
|
+
if (file === void 0 || !isRelativePath(file.path) || !isCount(file.size) || filePaths.has(file.path)) throw new Error(`webworker host: ${path} lists an invalid or repeated file in lazy archive ${name}`);
|
|
3643
|
+
filePaths.add(file.path);
|
|
3644
|
+
parsed.push({
|
|
3645
|
+
path: file.path,
|
|
3646
|
+
size: file.size
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3649
|
+
archives.push({
|
|
3650
|
+
archive: name,
|
|
3651
|
+
files: parsed
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
return {
|
|
3655
|
+
root,
|
|
3656
|
+
profile,
|
|
3657
|
+
...installation === void 0 ? {} : { installation },
|
|
3658
|
+
lowered: LOWERING_VERSION,
|
|
3659
|
+
javascriptEntries,
|
|
3660
|
+
visitedEntries,
|
|
3661
|
+
rewrittenEntries,
|
|
3662
|
+
lazy: archives
|
|
3663
|
+
};
|
|
3664
|
+
}
|
|
3330
3665
|
function requireGlobalPort(channel) {
|
|
3331
3666
|
if (channel !== void 0) return channel;
|
|
3332
3667
|
const post = globalThis.postMessage;
|
|
@@ -3349,7 +3684,6 @@ async function readImage(image) {
|
|
|
3349
3684
|
*/
|
|
3350
3685
|
function createWorkerHost(options) {
|
|
3351
3686
|
const root = options.root ?? "/dsh";
|
|
3352
|
-
const configPath = options.configPath ?? join(root, "config/cordis.yml");
|
|
3353
3687
|
const port = options.port ?? 3080;
|
|
3354
3688
|
const tunnel = new TunnelServer({
|
|
3355
3689
|
port: requireGlobalPort(options.channel),
|
|
@@ -3363,7 +3697,7 @@ function createWorkerHost(options) {
|
|
|
3363
3697
|
const start = async () => {
|
|
3364
3698
|
try {
|
|
3365
3699
|
const home = join(root, IMAGE_HOME_DIRECTORY);
|
|
3366
|
-
installProcessGlobal({
|
|
3700
|
+
const processShim = installProcessGlobal({
|
|
3367
3701
|
cwd: root,
|
|
3368
3702
|
env: {
|
|
3369
3703
|
DSH_HOME: home,
|
|
@@ -3377,7 +3711,8 @@ function createWorkerHost(options) {
|
|
|
3377
3711
|
for (const directory of IMAGE_EMPTY_DIRECTORIES) mounted.seedDirectory(join(root, directory.replace(/\/$/, "")));
|
|
3378
3712
|
setActiveVfs(mounted);
|
|
3379
3713
|
vfs = mounted;
|
|
3380
|
-
|
|
3714
|
+
const manifest = readImageManifest(mounted, options.manifestPath ?? join(root, "config/vfs-manifest.json"));
|
|
3715
|
+
deferLazyArchives(mounted, manifest, root, options.image);
|
|
3381
3716
|
const staticModules = { ...options.staticModules };
|
|
3382
3717
|
for (const key of ["node:process", "process"]) staticModules[key] ??= () => globalThis.process;
|
|
3383
3718
|
const loader = new WorkerModuleLoader({
|
|
@@ -3389,16 +3724,18 @@ function createWorkerHost(options) {
|
|
|
3389
3724
|
});
|
|
3390
3725
|
setActiveModuleLoader(loader);
|
|
3391
3726
|
modules = loader;
|
|
3392
|
-
const require = loader.requireFrom(
|
|
3727
|
+
const require = loader.requireFrom(root);
|
|
3393
3728
|
const appBoot = require("@deepseek-ai/dsh-app-boot");
|
|
3729
|
+
const { DSH_LAUNCH_ENVIRONMENT_KEY } = require("@deepseek-ai/dsh-launch-environment");
|
|
3394
3730
|
const cmdline = require("@deepseek-ai/dsh-cmdline");
|
|
3395
3731
|
const started = Promise.withResolvers();
|
|
3396
|
-
const { patches, profile } =
|
|
3732
|
+
const { patches, profile, environment } = prepareProfile(appBoot, mounted, manifest, root, processShim.env);
|
|
3397
3733
|
const profileConfig = join(profile.dir, "cordis.yml");
|
|
3398
|
-
const ctx = await appBoot.boot(
|
|
3734
|
+
const ctx = await appBoot.boot(BIN_NAME, profileConfig, patches, async (hostCtx) => {
|
|
3399
3735
|
hostCtx.provide("profileContext", profile);
|
|
3400
|
-
hostCtx.
|
|
3736
|
+
hostCtx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, environment);
|
|
3401
3737
|
installLogSink(hostCtx, require);
|
|
3738
|
+
await hostCtx.plugin(appBoot.PluginPackages, {});
|
|
3402
3739
|
cmdline.provideCmdline(hostCtx, {
|
|
3403
3740
|
args: [...options.cmdlineArgs ?? [
|
|
3404
3741
|
"--host",
|
|
@@ -3428,7 +3765,7 @@ function createWorkerHost(options) {
|
|
|
3428
3765
|
if (typertGateway === void 0) throw new Error("webworker host: the tree activated without a typertGateway service");
|
|
3429
3766
|
const handler = connection.createSharedFetchHandler("/api");
|
|
3430
3767
|
const usage = loader.usage();
|
|
3431
|
-
console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)}, direct lane=connection.createSharedFetchHandler, als causality=${options.alsCausality === void 0 ? "inert" : "snapshot/restore"}, image lowering=${LOWERING_VERSION})`);
|
|
3768
|
+
console.info(`webworker host: tree active (modules=${String(usage.modules)}, data overlays=${String(overlays.length)}, lazy archives=${String(manifest.lazy.length)}, direct lane=connection.createSharedFetchHandler, als causality=${options.alsCausality === void 0 ? "inert" : "snapshot/restore"}, image lowering=${LOWERING_VERSION})`);
|
|
3432
3769
|
const peer = connection.browserPeer;
|
|
3433
3770
|
tunnel.serve({
|
|
3434
3771
|
directFetch: (request) => handler.fetch(request, peer),
|
|
@@ -3488,76 +3825,84 @@ function installLogSink(ctx, require) {
|
|
|
3488
3825
|
};
|
|
3489
3826
|
ctx.logger.exporter(exporter);
|
|
3490
3827
|
}
|
|
3828
|
+
/** Diagnostic prefix the launcher passes to app-boot; the Worker boots the same profile code. */
|
|
3829
|
+
const BIN_NAME = "dsh";
|
|
3491
3830
|
/**
|
|
3492
|
-
*
|
|
3831
|
+
* Read and validate the mounted image's manifest.
|
|
3493
3832
|
*
|
|
3494
3833
|
* The manifest the packer writes is the single source of truth: the worker holds
|
|
3495
3834
|
* no transform, so an image that was never lowered — or was lowered against
|
|
3496
3835
|
* different wrapper semantics — cannot be recovered at load and must be rebuilt.
|
|
3497
3836
|
* @param vfs - Mounted filesystem.
|
|
3498
3837
|
* @param path - Manifest path inside the image.
|
|
3499
|
-
* @
|
|
3838
|
+
* @returns The validated manifest.
|
|
3839
|
+
* @throws When the manifest is missing, malformed, or names another contract.
|
|
3500
3840
|
*/
|
|
3501
|
-
function
|
|
3841
|
+
function readImageManifest(vfs, path) {
|
|
3502
3842
|
if (!vfs.existsSync(path)) throw new Error(`webworker host: ${path} is missing, so the image records no lowering; rebuild the image`);
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
*
|
|
3510
|
-
*
|
|
3511
|
-
*
|
|
3512
|
-
* @
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3843
|
+
return parseImageManifest(JSON.parse(vfs.readFileSync(path, "utf8")), path);
|
|
3844
|
+
}
|
|
3845
|
+
/**
|
|
3846
|
+
* Register the manifest's lazy archives; each is fetched beside the image on
|
|
3847
|
+
* the first asynchronous read of a file it carries.
|
|
3848
|
+
* @param vfs - Mounted filesystem.
|
|
3849
|
+
* @param manifest - Validated image manifest.
|
|
3850
|
+
* @param root - Virtual root the archives' entries mount under.
|
|
3851
|
+
* @param image - The image bytes or URL; archive names resolve against a URL.
|
|
3852
|
+
* @throws When the image registers lazy archives but arrived as bytes, leaving them unlocatable.
|
|
3853
|
+
*/
|
|
3854
|
+
function deferLazyArchives(vfs, manifest, root, image) {
|
|
3855
|
+
if (manifest.lazy.length === 0) return;
|
|
3856
|
+
if (typeof image !== "string") throw new Error("webworker host: the image registers lazy archives, which only an image fetched from a URL can locate");
|
|
3857
|
+
const base = new URL(image, globalThis.location?.href);
|
|
3858
|
+
for (const archive of manifest.lazy) {
|
|
3859
|
+
const url = new URL(archive.archive, base).href;
|
|
3860
|
+
vfs.deferArchive({
|
|
3861
|
+
name: archive.archive,
|
|
3862
|
+
root,
|
|
3863
|
+
files: archive.files,
|
|
3864
|
+
load: () => readImage(url)
|
|
3865
|
+
});
|
|
3866
|
+
}
|
|
3867
|
+
}
|
|
3868
|
+
/**
|
|
3869
|
+
* Prepare the profile the served launcher would boot: the image's app-boot
|
|
3870
|
+
* initializes the manifest's shipped profile template under the Worker home
|
|
3871
|
+
* and composes its bundle layers, user layer, and home layer. The profile
|
|
3872
|
+
* context carries the launcher's facts with no command-line overlay; the
|
|
3873
|
+
* image's home patch, written by the packer, carries the Worker's required
|
|
3874
|
+
* row config (`PLATFORM_ENTRY_CONFIG` in `image-layout.ts`).
|
|
3875
|
+
* @param appBoot - The image's app-boot.
|
|
3876
|
+
* @param vfs - Filesystem holding the profile.
|
|
3877
|
+
* @param manifest - Validated image manifest naming the profile and installation.
|
|
3878
|
+
* @param root - Virtual root, the process working directory.
|
|
3879
|
+
* @param env - The process shim's environment, after the launch layers applied.
|
|
3880
|
+
* @returns Effective boot patches, the profile context, and the launch environment snapshot.
|
|
3881
|
+
* @throws When the image names no installation manifest to anchor bundle resolution.
|
|
3882
|
+
*/
|
|
3883
|
+
function prepareProfile(appBoot, vfs, manifest, root, env) {
|
|
3884
|
+
if (manifest.installation === void 0) throw new Error("webworker host: the image names no installation manifest, so no profile bundle can resolve; rebuild the image");
|
|
3885
|
+
const home = join(root, IMAGE_HOME_DIRECTORY);
|
|
3886
|
+
const installAnchor = join(root, "node_modules", manifest.installation, "package.json");
|
|
3887
|
+
const environment = appBoot.loadLayeredEnv(BIN_NAME, root);
|
|
3888
|
+
const loaded = appBoot.loadProfile(BIN_NAME, manifest.profile, installAnchor, home);
|
|
3889
|
+
appBoot.reportSkippedBundles(BIN_NAME, loaded);
|
|
3890
|
+
vfs.writeFileSync(join(loaded.dir, "cordis.yml"), "[]\n");
|
|
3544
3891
|
const profile = {
|
|
3545
|
-
name:
|
|
3546
|
-
dir,
|
|
3547
|
-
patchPath:
|
|
3548
|
-
installAnchor
|
|
3892
|
+
name: loaded.name,
|
|
3893
|
+
dir: loaded.dir,
|
|
3894
|
+
patchPath: loaded.patchPath,
|
|
3895
|
+
installAnchor,
|
|
3549
3896
|
cwd: root,
|
|
3550
|
-
home
|
|
3551
|
-
startedBundles:
|
|
3552
|
-
overlays:
|
|
3553
|
-
telemetryDisabledEnv:
|
|
3897
|
+
home,
|
|
3898
|
+
startedBundles: loaded.layers.map((layer) => layer.packageName),
|
|
3899
|
+
overlays: [],
|
|
3900
|
+
telemetryDisabledEnv: env.DSH_TELEMETRY_DISABLED
|
|
3554
3901
|
};
|
|
3555
|
-
vfs.seed(join(dir, "package.json"), "{\"private\":true,\"dsh\":{\"profile\":{\"bundles\":[]}}}\n");
|
|
3556
|
-
vfs.seed(join(dir, "cordis.yml"), "[]\n");
|
|
3557
|
-
if (!vfs.existsSync(profile.patchPath)) vfs.seed(profile.patchPath, yaml.dump([{ insert: rows }], { schema: include.entryListSchema }));
|
|
3558
3902
|
return {
|
|
3559
|
-
patches:
|
|
3560
|
-
profile
|
|
3903
|
+
patches: appBoot.readProfilePatches(BIN_NAME, profile, loaded),
|
|
3904
|
+
profile,
|
|
3905
|
+
environment
|
|
3561
3906
|
};
|
|
3562
3907
|
}
|
|
3563
3908
|
/**
|
|
@@ -3619,4 +3964,4 @@ function parsePreviewFixtureManifest(value) {
|
|
|
3619
3964
|
};
|
|
3620
3965
|
}
|
|
3621
3966
|
//#endregion
|
|
3622
|
-
export { API_PREFIX, DEFAULT_CONDITIONS, DEFAULT_ROOT,
|
|
3967
|
+
export { API_PREFIX, DEFAULT_CONDITIONS, DEFAULT_ROOT, IMAGE_EMPTY_DIRECTORIES, IMAGE_FILE_NAME, IMAGE_HOME_DIRECTORY, IMAGE_LAZY_DIRECTORY, IMAGE_MANIFEST_PATH, IMAGE_OVERLAY_DIRECTORIES, LOWERING_VERSION, MODULE_PROXIES, MODULE_PROXY_PREFIXES, MemoryVfs, PLATFORM_ENTRY_CONFIG, PREVIEW_FIXTURE_MANIFEST_FILE, PREVIEW_FIXTURE_MANIFEST_VERSION, REPLACED_EXTERNAL_PACKAGES, SYNTHETIC_HOST, TunnelServer, WRAPPER_PARAMS, WorkerModuleLoader, createAlsRuntime, createSyntheticExchange, createWorkerHost, inflateImage, inflateImageStream, installProcessGlobal, loadVfsImage, loadVfsOverlay, lowerModuleSource, packTar, parseImageManifest, parseInboundFrame, parsePreviewFixtureManifest, parseTar, posix_path_exports as posixPath, requireActiveModuleLoader, requireActiveVfs, setActiveModuleLoader, setActiveVfs };
|