@powerhousedao/builder-tools 6.2.3-dev.5 → 6.2.3-dev.50
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/dist/index.d.mts +401 -79
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1059 -82
- package/dist/index.mjs.map +1 -1
- package/dist/service-worker/service-worker.ts +8 -2
- package/package.json +10 -10
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import fs, { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import fs, { chmodSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import path, { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
5
5
|
import { exec, execSync, spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { DYNAMIC_BASE_PLACEHOLDER, PWA_FILE_HANDLER_ACTION, buildRuntimeConfig, deepMerge, loadConnectEnv, mergeManifest, mergePwaConfig, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, resolveSourceConnect, setConnectEnv, unionStrings, withInferredCategory, workerPackageFileName, workflowsSettingConflict } from "@powerhousedao/shared/connect";
|
|
7
8
|
import MagicString from "magic-string";
|
|
8
9
|
import { readFile, writeFile } from "node:fs/promises";
|
|
9
10
|
import { cwd } from "node:process";
|
|
10
|
-
import { DEFAULT_CONNECT_CONFIG, PWA_FILE_HANDLER_ACTION, buildRuntimeConfig, deepMerge, loadConnectEnv, mergeManifest, mergePwaConfig, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, setConnectEnv, unionStrings, withInferredCategory } from "@powerhousedao/shared/connect";
|
|
11
11
|
import { getConfig } from "@powerhousedao/config/node";
|
|
12
12
|
import tailwind from "@tailwindcss/vite";
|
|
13
13
|
import react from "@vitejs/plugin-react";
|
|
@@ -44,7 +44,8 @@ function computeBuildHash(input) {
|
|
|
44
44
|
*/
|
|
45
45
|
function readConnectVersion(projectRoot) {
|
|
46
46
|
try {
|
|
47
|
-
|
|
47
|
+
const entry = createRequire(join(projectRoot, "package.json")).resolve("@powerhousedao/connect", { paths: [projectRoot] });
|
|
48
|
+
let dir = dirname(entry);
|
|
48
49
|
for (;;) {
|
|
49
50
|
try {
|
|
50
51
|
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
@@ -91,16 +92,6 @@ function connectBuildHashPlugin(buildHash) {
|
|
|
91
92
|
//#endregion
|
|
92
93
|
//#region connect-utils/vite-plugins/dynamic-base.ts
|
|
93
94
|
/**
|
|
94
|
-
* Placeholder base used when Connect is built in dynamic-base mode. The Vite
|
|
95
|
-
* `base` option is set to this token; this plugin rewrites it in the emitted
|
|
96
|
-
* output so the effective base is resolved at serve time from a global instead
|
|
97
|
-
* of being baked at build time.
|
|
98
|
-
*
|
|
99
|
-
* Trailing slash matches `normalizeBasePath` output, so the token sits in the
|
|
100
|
-
* same syntactic position as a concrete base would.
|
|
101
|
-
*/
|
|
102
|
-
const DYNAMIC_BASE_PLACEHOLDER = "/__PH_DYNAMIC_BASE__/";
|
|
103
|
-
/**
|
|
104
95
|
* Global the runtime (ph-clint proxy) must set before the entry bundle loads.
|
|
105
96
|
* Value is the normalized deploy base, e.g. "/myagent/" or "/". The JS rewrite
|
|
106
97
|
* below resolves all asset / lazy-chunk / BASE_URL references against it.
|
|
@@ -108,7 +99,8 @@ const DYNAMIC_BASE_PLACEHOLDER = "/__PH_DYNAMIC_BASE__/";
|
|
|
108
99
|
const RUNTIME_GLOBAL = "globalThis.__PH_DYNAMIC_BASE__";
|
|
109
100
|
const BASE_EXPR = `(${RUNTIME_GLOBAL}||"/")`;
|
|
110
101
|
function workerPrelude(stripPrefix) {
|
|
111
|
-
|
|
102
|
+
const prefix = escapeForRegExp(stripPrefix).replace(/\//g, "\\/");
|
|
103
|
+
return `${RUNTIME_GLOBAL}=self.location.pathname.replace(/${prefix}assets\\/[^/]*$/,"");\n`;
|
|
112
104
|
}
|
|
113
105
|
const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}((?:(?!\\1)[^$])*)\\1`, "g");
|
|
114
106
|
/**
|
|
@@ -144,7 +136,7 @@ function connectDynamicBasePlugin(options = {}) {
|
|
|
144
136
|
name: "ph-connect-dynamic-base",
|
|
145
137
|
enforce: "post",
|
|
146
138
|
renderChunk(code, chunk) {
|
|
147
|
-
if (!code.includes(
|
|
139
|
+
if (!code.includes(DYNAMIC_BASE_PLACEHOLDER)) return null;
|
|
148
140
|
const s = new MagicString(code);
|
|
149
141
|
for (const match of code.matchAll(PLACEHOLDER_LITERAL)) {
|
|
150
142
|
const rest = match[2];
|
|
@@ -161,7 +153,7 @@ function connectDynamicBasePlugin(options = {}) {
|
|
|
161
153
|
};
|
|
162
154
|
},
|
|
163
155
|
generateBundle(_options, bundle) {
|
|
164
|
-
for (const file of Object.values(bundle)) if ((file.type === "chunk" ? file.code : file.fileName.endsWith(".css") && typeof file.source === "string" ? file.source : void 0)?.includes(
|
|
156
|
+
for (const file of Object.values(bundle)) if ((file.type === "chunk" ? file.code : file.fileName.endsWith(".css") && typeof file.source === "string" ? file.source : void 0)?.includes(DYNAMIC_BASE_PLACEHOLDER)) this.error(`dynamic-base: unrewritten placeholder ${DYNAMIC_BASE_PLACEHOLDER} remains in ${file.fileName}`);
|
|
165
157
|
}
|
|
166
158
|
};
|
|
167
159
|
}
|
|
@@ -241,31 +233,34 @@ async function prebuildConnectVendor(options) {
|
|
|
241
233
|
const vendorDir = options.vendorDir ?? join(options.dirname, "node_modules/.ph-vendor");
|
|
242
234
|
const importMapPath = join(vendorDir, "import-map.json");
|
|
243
235
|
const versionDigest = resolveVersionDigest(options.dirname, [...include, ...external]);
|
|
236
|
+
const versions = resolveDepVersions(options.dirname, [...include, ...external]);
|
|
237
|
+
const base = options.base ?? VENDOR_DYNAMIC_BASE;
|
|
238
|
+
const nodeEnv = options.nodeEnv ?? "development";
|
|
244
239
|
try {
|
|
245
240
|
const hit = readCacheHit(importMapPath, include, external, versionDigest);
|
|
246
241
|
if (hit) return {
|
|
247
242
|
vendorDir,
|
|
248
|
-
|
|
243
|
+
...hit
|
|
249
244
|
};
|
|
250
245
|
const lockDir = `${vendorDir}.lock`;
|
|
251
246
|
const lock = acquireLock(lockDir);
|
|
252
247
|
if (!lock) {
|
|
253
|
-
const
|
|
254
|
-
return
|
|
248
|
+
const hit = await waitForCacheHit(importMapPath, include, external, versionDigest, lockDir);
|
|
249
|
+
return hit ? {
|
|
255
250
|
vendorDir,
|
|
256
|
-
|
|
251
|
+
...hit
|
|
257
252
|
} : null;
|
|
258
253
|
}
|
|
259
254
|
try {
|
|
260
255
|
const raced = readCacheHit(importMapPath, include, external, versionDigest);
|
|
261
256
|
if (raced) return {
|
|
262
257
|
vendorDir,
|
|
263
|
-
|
|
258
|
+
...raced
|
|
264
259
|
};
|
|
265
|
-
const
|
|
266
|
-
return
|
|
260
|
+
const result = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest, versions, base, nodeEnv);
|
|
261
|
+
return result ? {
|
|
267
262
|
vendorDir,
|
|
268
|
-
|
|
263
|
+
...result
|
|
269
264
|
} : null;
|
|
270
265
|
} finally {
|
|
271
266
|
releaseLock(lock);
|
|
@@ -279,27 +274,73 @@ function readCacheHit(importMapPath, include, external, versionDigest) {
|
|
|
279
274
|
if (!existsSync(importMapPath)) return null;
|
|
280
275
|
try {
|
|
281
276
|
const cached = JSON.parse(readFileSync(importMapPath, "utf8"));
|
|
282
|
-
if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest) return
|
|
277
|
+
if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest) return {
|
|
278
|
+
imports: cached.imports,
|
|
279
|
+
versions: cached.versions ?? {}
|
|
280
|
+
};
|
|
283
281
|
} catch {}
|
|
284
282
|
return null;
|
|
285
283
|
}
|
|
286
|
-
|
|
287
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Resolve the installed version of each spec's owning package, deduped by
|
|
286
|
+
* package name. Resolution order per spec:
|
|
287
|
+
* 1. the project itself, when it IS the owning package (a self-vendoring
|
|
288
|
+
* app has no node_modules self-link);
|
|
289
|
+
* 2. the project's own node_modules link (works for pnpm-strict and
|
|
290
|
+
* hoisted layouts alike, and — unlike `require.resolve` — for
|
|
291
|
+
* ESM-only packages whose exports map has no CJS condition);
|
|
292
|
+
* 3. Node's own module resolution (covers transitive installs), walking
|
|
293
|
+
* the entry up to the owning package's package.json.
|
|
294
|
+
* Unresolvable specs contribute a "missing" sentinel, unknown versions
|
|
295
|
+
* "unknown".
|
|
296
|
+
*/
|
|
297
|
+
function resolveDepVersions(dirname$3, specs) {
|
|
298
|
+
const req = createRequire(join(dirname$3, "noop.js"));
|
|
299
|
+
const seen = {};
|
|
300
|
+
let selfMeta = null;
|
|
288
301
|
for (const spec of specs) {
|
|
289
302
|
const { pkg } = parsePkg(spec);
|
|
290
|
-
if (seen
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
303
|
+
if (pkg in seen) continue;
|
|
304
|
+
if (selfMeta === null) try {
|
|
305
|
+
selfMeta = JSON.parse(readFileSync(join(dirname$3, "package.json"), "utf8"));
|
|
306
|
+
} catch {
|
|
307
|
+
selfMeta = {};
|
|
308
|
+
}
|
|
309
|
+
let version;
|
|
310
|
+
if (typeof selfMeta.name === "string" && selfMeta.name === pkg && typeof selfMeta.version === "string") version = selfMeta.version;
|
|
311
|
+
else {
|
|
312
|
+
version = "missing";
|
|
313
|
+
try {
|
|
314
|
+
const pkgRoot = realpathSync(join(dirname$3, "node_modules", pkg));
|
|
315
|
+
const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
|
|
316
|
+
version = String(meta.version ?? "unknown");
|
|
317
|
+
} catch {
|
|
318
|
+
try {
|
|
319
|
+
const entry = req.resolve(spec);
|
|
320
|
+
let dir = dirname(entry);
|
|
321
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
322
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
323
|
+
const meta = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
324
|
+
version = String(meta.version ?? "unknown");
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
const parent = dirname(dir);
|
|
328
|
+
if (parent === dir) break;
|
|
329
|
+
dir = parent;
|
|
330
|
+
}
|
|
331
|
+
} catch {}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
seen[pkg] = version;
|
|
298
335
|
}
|
|
336
|
+
return seen;
|
|
337
|
+
}
|
|
338
|
+
function resolveVersionDigest(dirname, specs) {
|
|
339
|
+
const versions = resolveDepVersions(dirname, specs);
|
|
299
340
|
const h = createHash("sha256");
|
|
300
341
|
const workerHash = createHash("sha256").update(VENDOR_BUILD_WORKER).digest("hex");
|
|
301
342
|
h.update(`worker:${workerHash}\n`);
|
|
302
|
-
for (const pkg of
|
|
343
|
+
for (const pkg of Object.keys(versions).sort()) h.update(`${pkg}@${versions[pkg]}\n`);
|
|
303
344
|
return h.digest("hex").slice(0, 16);
|
|
304
345
|
}
|
|
305
346
|
function sameSet(a, b) {
|
|
@@ -307,7 +348,7 @@ function sameSet(a, b) {
|
|
|
307
348
|
const s = new Set(a);
|
|
308
349
|
return b.every((x) => s.has(x));
|
|
309
350
|
}
|
|
310
|
-
const LOCK_STALE_MS =
|
|
351
|
+
const LOCK_STALE_MS = 3e5;
|
|
311
352
|
const LOCK_HEARTBEAT_MS = 6e4;
|
|
312
353
|
const ownerFile = (lockDir) => join(lockDir, "owner");
|
|
313
354
|
function lockIsStale(lockDir) {
|
|
@@ -406,11 +447,16 @@ function parsePkg(spec) {
|
|
|
406
447
|
* `@powerhousedao/design-system/connect/toast`, `zod/v4/core`). Those bare
|
|
407
448
|
* imports need their own import-map entry, so expand each listed spec to its
|
|
408
449
|
* package's concrete (non-wildcard, JS) subpath exports that share the spec's
|
|
409
|
-
* prefix. Unresolvable / CSS / JSON targets are skipped.
|
|
410
|
-
*
|
|
450
|
+
* prefix. Unresolvable / CSS / JSON targets are skipped.
|
|
451
|
+
*
|
|
452
|
+
* A spec whose package isn't installed in `dirname` is dropped entirely
|
|
453
|
+
* rather than kept for the worker: the worker cannot build an entry for a
|
|
454
|
+
* missing package (its `await import` and the vite resolution both fail),
|
|
455
|
+
* and a bare re-export entry would fail the whole vendor build. The
|
|
456
|
+
* importer of a missing shared dep simply bundles its own copy.
|
|
411
457
|
*/
|
|
412
458
|
function expandIncludeSubpaths(dirname, include) {
|
|
413
|
-
const out = new Set(
|
|
459
|
+
const out = /* @__PURE__ */ new Set();
|
|
414
460
|
for (const spec of include) {
|
|
415
461
|
const { pkg, sub } = parsePkg(spec);
|
|
416
462
|
let exp;
|
|
@@ -421,6 +467,7 @@ function expandIncludeSubpaths(dirname, include) {
|
|
|
421
467
|
} catch {
|
|
422
468
|
continue;
|
|
423
469
|
}
|
|
470
|
+
out.add(sub ? spec : pkg);
|
|
424
471
|
if (!exp || typeof exp !== "object") continue;
|
|
425
472
|
const expMap = exp;
|
|
426
473
|
const prefixKey = sub ? `./${sub}` : ".";
|
|
@@ -437,34 +484,72 @@ function expandIncludeSubpaths(dirname, include) {
|
|
|
437
484
|
return [...out];
|
|
438
485
|
}
|
|
439
486
|
/**
|
|
487
|
+
* The published specifiers whose entry file the vendor build never emitted.
|
|
488
|
+
* URLs are base-less (`/__vendor__/x.js`) and resolve inside `vendorDir`.
|
|
489
|
+
*/
|
|
490
|
+
function missingVendorEntries(vendorDir, imports) {
|
|
491
|
+
const prefix = VENDOR_URL_PREFIX;
|
|
492
|
+
return Object.entries(imports).filter(([, url]) => {
|
|
493
|
+
const rel = url.startsWith(prefix) ? url.slice(12) : url;
|
|
494
|
+
return !existsSync(join(vendorDir, rel));
|
|
495
|
+
}).map(([spec, url]) => ({
|
|
496
|
+
spec,
|
|
497
|
+
url
|
|
498
|
+
}));
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Rebase the vendor's base-less entry URLs (`/__vendor__/x.js`) onto the
|
|
502
|
+
* deploy base, so a consumer that resolves them against the page origin lands
|
|
503
|
+
* under that base rather than the server root. A dynamic-base build keeps its
|
|
504
|
+
* placeholder, which the runtime substitutes.
|
|
505
|
+
*
|
|
506
|
+
* `base` is the app base from `prebuildConnectVendor` callers, but the
|
|
507
|
+
* VENDOR_DYNAMIC_BASE default already carries the vendor segment — that
|
|
508
|
+
* trailing segment is dropped first so it is never doubled.
|
|
509
|
+
*/
|
|
510
|
+
function withVendorBase(imports, base) {
|
|
511
|
+
const segment = VENDOR_URL_PREFIX.replace(/^\/+/, "");
|
|
512
|
+
const appBase = base.endsWith(segment) ? base.slice(0, base.length - segment.length) : base;
|
|
513
|
+
return Object.fromEntries(Object.entries(imports).map(([spec, url]) => [spec, `${appBase}/${url}`.replace(/([^:])\/{2,}/g, "$1/").replace(/^\/{2,}/, "/")]));
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
440
516
|
* Build the vendor into a unique temp dir, then atomically swap it into place,
|
|
441
517
|
* so a concurrent reader never sees a partial bundle or import-map.json. Runs
|
|
442
518
|
* the build in a throwaway subprocess (its peak memory is reclaimed on exit);
|
|
443
|
-
* the parent augments the import map with the version digest
|
|
444
|
-
*
|
|
519
|
+
* the parent augments the import map with the version digest + the version
|
|
520
|
+
* table, writes the runtime shared-deps module, and does the swap.
|
|
521
|
+
* Returns the published import map + versions, or null on failure.
|
|
445
522
|
*/
|
|
446
|
-
async function buildVendorAtomic(dirname$
|
|
523
|
+
async function buildVendorAtomic(dirname$4, vendorDir, include, external, versionDigest, versions, base, nodeEnv) {
|
|
447
524
|
const parent = dirname(vendorDir);
|
|
448
525
|
mkdirSync(parent, { recursive: true });
|
|
449
526
|
const tmpDir = mkdtempSync(join(parent, ".ph-vendor.tmp-"));
|
|
450
527
|
try {
|
|
451
|
-
const { ok, stderr } = await runBuildWorker(dirname$
|
|
528
|
+
const { ok, stderr } = await runBuildWorker(dirname$4, tmpDir, include, external, base, nodeEnv);
|
|
452
529
|
const tmpMap = join(tmpDir, "import-map.json");
|
|
453
530
|
if (!ok || !existsSync(tmpMap)) {
|
|
454
531
|
const detail = stderr.trim();
|
|
455
532
|
throw new Error(`vendor build failed${detail ? `:\n${detail}` : " (no output captured)"}`);
|
|
456
533
|
}
|
|
457
534
|
const meta = JSON.parse(readFileSync(tmpMap, "utf8"));
|
|
535
|
+
const unbacked = missingVendorEntries(tmpDir, meta.imports);
|
|
536
|
+
if (unbacked.length > 0) throw new Error(`vendor build published ${unbacked.length} import-map ${unbacked.length === 1 ? "entry" : "entries"} with no file behind ${unbacked.length === 1 ? "it" : "them"}:\n` + unbacked.map((e) => ` ${e.spec} -> ${e.url}`).join("\n"));
|
|
458
537
|
meta.versionDigest = versionDigest;
|
|
538
|
+
meta.versions = versions;
|
|
459
539
|
writeFileSync(tmpMap, JSON.stringify(meta, null, 2));
|
|
540
|
+
writeFileSync(join(tmpDir, "shared-deps.js"), `export const imports = ${JSON.stringify(withVendorBase(meta.imports, base))};\nexport const versions = ${JSON.stringify(versions)};\n`);
|
|
460
541
|
const oldDir = `${vendorDir}.old-${process.pid}-${Date.now()}`;
|
|
461
542
|
if (existsSync(vendorDir)) renameSync(vendorDir, oldDir);
|
|
462
543
|
renameSync(tmpDir, vendorDir);
|
|
544
|
+
chmodSync(vendorDir, 493);
|
|
463
545
|
rmSync(oldDir, {
|
|
464
546
|
recursive: true,
|
|
465
547
|
force: true
|
|
466
548
|
});
|
|
467
|
-
return
|
|
549
|
+
return {
|
|
550
|
+
imports: meta.imports,
|
|
551
|
+
versions
|
|
552
|
+
};
|
|
468
553
|
} catch (err) {
|
|
469
554
|
rmSync(tmpDir, {
|
|
470
555
|
recursive: true,
|
|
@@ -473,16 +558,29 @@ async function buildVendorAtomic(dirname$2, vendorDir, include, external, versio
|
|
|
473
558
|
throw err;
|
|
474
559
|
}
|
|
475
560
|
}
|
|
561
|
+
function resolveSelfModulePath() {
|
|
562
|
+
const selfPath = fileURLToPath(import.meta.url);
|
|
563
|
+
if (!selfPath.endsWith(".ts")) return selfPath;
|
|
564
|
+
let dir = dirname(selfPath);
|
|
565
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
566
|
+
const candidate = join(dir, "dist", "index.mjs");
|
|
567
|
+
if (existsSync(candidate)) return candidate;
|
|
568
|
+
const parent = dirname(dir);
|
|
569
|
+
if (parent === dir) break;
|
|
570
|
+
dir = parent;
|
|
571
|
+
}
|
|
572
|
+
throw new Error("vendor build needs the built builder-tools bundle (dist/index.mjs); build the package first");
|
|
573
|
+
}
|
|
476
574
|
/**
|
|
477
575
|
* Spawn a throwaway worker that generates one entry per specifier (CJS deps get
|
|
478
576
|
* explicit named re-exports, ESM deps `export *`), `vite build`s them into the
|
|
479
577
|
* given out dir, and writes the import map. Captures stdout/stderr so a failure
|
|
480
578
|
* is debuggable; the normal path stays quiet.
|
|
481
579
|
*/
|
|
482
|
-
function runBuildWorker(dirname, outDir, include, external) {
|
|
580
|
+
function runBuildWorker(dirname, outDir, include, external, base, nodeEnv) {
|
|
483
581
|
const workerPath = join(outDir, "build-worker.mjs");
|
|
484
582
|
writeFileSync(workerPath, VENDOR_BUILD_WORKER);
|
|
485
|
-
const selfModulePath =
|
|
583
|
+
const selfModulePath = resolveSelfModulePath();
|
|
486
584
|
return new Promise((resolve) => {
|
|
487
585
|
const child = spawn(process.execPath, [
|
|
488
586
|
workerPath,
|
|
@@ -492,7 +590,9 @@ function runBuildWorker(dirname, outDir, include, external) {
|
|
|
492
590
|
VENDOR_URL_PREFIX,
|
|
493
591
|
JSON.stringify(external),
|
|
494
592
|
VENDOR_DYNAMIC_BASE,
|
|
495
|
-
selfModulePath
|
|
593
|
+
selfModulePath,
|
|
594
|
+
base,
|
|
595
|
+
nodeEnv
|
|
496
596
|
], {
|
|
497
597
|
cwd: dirname,
|
|
498
598
|
stdio: [
|
|
@@ -522,14 +622,22 @@ function runBuildWorker(dirname, outDir, include, external) {
|
|
|
522
622
|
* The vendor build worker, written to disk and run as a subprocess. Loads
|
|
523
623
|
* `vite` from the project and `connectDynamicBasePlugin` from builder-tools' own
|
|
524
624
|
* built bundle (selfModulePath). argv: dirname, vendorDir, includeJSON,
|
|
525
|
-
* urlPrefix, externalJSON, dynamicBase, selfModulePath
|
|
625
|
+
* urlPrefix, externalJSON, dynamicBase, selfModulePath, base (app base),
|
|
626
|
+
* nodeEnv.
|
|
526
627
|
*/
|
|
527
628
|
const VENDOR_BUILD_WORKER = `
|
|
528
629
|
import { createRequire } from 'node:module';
|
|
529
630
|
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
|
530
631
|
import { join, isAbsolute } from 'node:path';
|
|
531
632
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
532
|
-
const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON, dynamicBase, selfModulePath] = process.argv.slice(2);
|
|
633
|
+
const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON, dynamicBase, selfModulePath, baseArg, nodeEnvArg] = process.argv.slice(2);
|
|
634
|
+
// Trailing argv are optional (older callers): base defaults to the dynamic
|
|
635
|
+
// placeholder behavior, nodeEnv to development. baseArg is the APP base —
|
|
636
|
+
// the same string the app build uses; the build base appends the vendor
|
|
637
|
+
// segment so emitted asset URLs land under <app-base>__vendor__/.
|
|
638
|
+
const segment = urlPrefix.replace(/^\\/+/, '');
|
|
639
|
+
const base = baseArg ? baseArg + segment : dynamicBase;
|
|
640
|
+
const nodeEnv = nodeEnvArg ?? 'development';
|
|
533
641
|
const include = JSON.parse(includeJSON);
|
|
534
642
|
const external = JSON.parse(externalJSON ?? '[]');
|
|
535
643
|
const externalSet = new Set(external);
|
|
@@ -602,14 +710,17 @@ const phVendorResolve = {
|
|
|
602
710
|
};
|
|
603
711
|
await build({
|
|
604
712
|
root: dirname, configFile: false, logLevel: 'error',
|
|
605
|
-
//
|
|
606
|
-
// emitted chunk/asset URLs to resolve
|
|
607
|
-
base
|
|
713
|
+
// Caller-provided build base (or the dynamic placeholder default):
|
|
714
|
+
// connectDynamicBasePlugin rewrites emitted chunk/asset URLs to resolve
|
|
715
|
+
// against the deploy base at serve time.
|
|
716
|
+
base,
|
|
608
717
|
define: {
|
|
609
|
-
'process.env.NODE_ENV':
|
|
718
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
610
719
|
// BASE_URL resolves to the deploy base (not the vendor prefix) so vendored
|
|
611
720
|
// Connect's router basename + BASE_URL-relative fetches use the right path.
|
|
612
|
-
|
|
721
|
+
// The app base when provided (concrete or placeholder), the placeholder
|
|
722
|
+
// otherwise (dev flow — rewritten at serve time by the dynamic-base plugin).
|
|
723
|
+
'import.meta.env.BASE_URL': JSON.stringify(baseArg ?? DYNAMIC_BASE_PLACEHOLDER),
|
|
613
724
|
},
|
|
614
725
|
// phVendorResolve (pre) resolves bares from the worker; esmExternalRequirePlugin owns
|
|
615
726
|
// react/virtual externalization; connectDynamicBasePlugin (post) rewrites the placeholder base.
|
|
@@ -618,7 +729,9 @@ await build({
|
|
|
618
729
|
// segment so the worker recovers the deploy base, not the vendor prefix.
|
|
619
730
|
worker: { format: 'es', plugins: () => [connectDynamicBasePlugin({ forWorker: true, workerStripPrefix: urlPrefix.replace(/^\\/+/, '') })] },
|
|
620
731
|
build: {
|
|
621
|
-
|
|
732
|
+
// Production ships this to every visitor, so minify it like the app's own
|
|
733
|
+
// chunks; dev keeps readable output and the faster build.
|
|
734
|
+
outDir: vendorDir, emptyOutDir: false, minify: nodeEnv === 'production', target: 'esnext', cssCodeSplit: true,
|
|
622
735
|
rollupOptions: {
|
|
623
736
|
input, preserveEntrySignatures: 'strict',
|
|
624
737
|
output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
|
|
@@ -631,6 +744,724 @@ for (const spec of include) imports[spec] = urlPrefix + entryName(spec) + '.js';
|
|
|
631
744
|
writeFileSync(join(vendorDir, 'import-map.json'), JSON.stringify({ include, external, imports }, null, 2));
|
|
632
745
|
`;
|
|
633
746
|
//#endregion
|
|
747
|
+
//#region connect-utils/reactor-worker-build.ts
|
|
748
|
+
/**
|
|
749
|
+
* Prebuild Connect's reactor SharedWorker into a browser-loadable bundle.
|
|
750
|
+
*
|
|
751
|
+
* The published `@powerhousedao/connect` dist ships `reactor.worker.js` as a
|
|
752
|
+
* library artifact: its dependencies are externalized onto bare specifiers
|
|
753
|
+
* (`@powerhousedao/reactor`, `kysely`, ...). The page resolves those through
|
|
754
|
+
* its `<script type="importmap">`, but import maps are a document feature —
|
|
755
|
+
* a worker never inherits one and no Worker/SharedWorker constructor option
|
|
756
|
+
* can supply one. A bare specifier inside a worker is unresolvable by the
|
|
757
|
+
* platform, so the shipped file can never run as a worker script.
|
|
758
|
+
*
|
|
759
|
+
* This module builds the worker's module graph into a self-contained bundle
|
|
760
|
+
* (one `vite build` in a throwaway subprocess, same pattern as the vendor
|
|
761
|
+
* prebuild). Two modes:
|
|
762
|
+
*
|
|
763
|
+
* - With `vendorImports` (production builds that also ship the `__vendor__/`
|
|
764
|
+
* bundle): specifiers present in the vendor import map are externalized and
|
|
765
|
+
* rewritten to `../__vendor__/<entry>.js`. The worker bundle is served as a
|
|
766
|
+
* sibling of `__vendor__/`, so those relative imports hit the exact URLs
|
|
767
|
+
* the page's import map uses — one fetch and one HTTP-cache entry per
|
|
768
|
+
* shared dep for the page and the worker together.
|
|
769
|
+
* - Without (dev servers, `PH_CONNECT_VENDOR=0` builds): everything is
|
|
770
|
+
* bundled; the worker is self-contained.
|
|
771
|
+
*
|
|
772
|
+
* The build uses Vite's relative base (`"./"`), so emitted chunk and asset
|
|
773
|
+
* URLs resolve against the worker script's own URL. That sidesteps the
|
|
774
|
+
* dynamic-base machinery entirely: the bundle works under any deploy base,
|
|
775
|
+
* concrete or proxy-substituted, as long as it is served under
|
|
776
|
+
* `<app-base>/__reactor_worker__/`.
|
|
777
|
+
*/
|
|
778
|
+
/** URL prefix the worker bundle is served under, next to `__vendor__/`. */
|
|
779
|
+
const REACTOR_WORKER_URL_PREFIX = "/__reactor_worker__/";
|
|
780
|
+
/** Entry filename of the bundle; the tab constructs its SharedWorker from it. */
|
|
781
|
+
const REACTOR_WORKER_ENTRY = "reactor.worker.js";
|
|
782
|
+
/** Metadata file the cache check reads; lives inside the bundle dir. */
|
|
783
|
+
const META_FILE = "worker-meta.json";
|
|
784
|
+
/** Content-Type for the extensions the worker build emits; JS is the default. */
|
|
785
|
+
const REACTOR_WORKER_MIME = {
|
|
786
|
+
".map": "application/json",
|
|
787
|
+
".json": "application/json",
|
|
788
|
+
".wasm": "application/wasm",
|
|
789
|
+
".data": "application/octet-stream"
|
|
790
|
+
};
|
|
791
|
+
/**
|
|
792
|
+
* The installed Connect dist's worker entry, or null when the installed
|
|
793
|
+
* version predates the worker (the feature is then unavailable, not broken).
|
|
794
|
+
*/
|
|
795
|
+
function resolveReactorWorkerSource(dirname) {
|
|
796
|
+
try {
|
|
797
|
+
const candidate = join(realpathSync(join(dirname, "node_modules", "@powerhousedao/connect")), "dist", REACTOR_WORKER_ENTRY);
|
|
798
|
+
return existsSync(candidate) ? candidate : null;
|
|
799
|
+
} catch {
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/** `/__vendor__/<entry>.js` -> `../__vendor__/<entry>.js` (sibling dirs). */
|
|
804
|
+
function vendorRelativePath(vendorUrl) {
|
|
805
|
+
return `../__vendor__/${vendorUrl.slice(vendorUrl.lastIndexOf("/") + 1)}`;
|
|
806
|
+
}
|
|
807
|
+
const SPECIFIER_PATTERNS = [
|
|
808
|
+
/(?<![.\w$])import\s*\(\s*["']([^"']+)["']/g,
|
|
809
|
+
/(?<![.\w$])import\s*[^"'()]*?from\s*["']([^"']+)["']/g,
|
|
810
|
+
/(?<![.\w$])export\s*[^"'()]*?from\s*["']([^"']+)["']/g,
|
|
811
|
+
/(?<![.\w$])import\s*["']([^"']+)["']/g
|
|
812
|
+
];
|
|
813
|
+
/**
|
|
814
|
+
* Module specifiers a worker cannot resolve: anything that is not relative,
|
|
815
|
+
* root-relative, or an http(s)/data/blob URL. Used as a post-build guard so a
|
|
816
|
+
* leftover bare (or `node:`) import fails the build with a name instead of
|
|
817
|
+
* shipping a worker that dies on its first import statement.
|
|
818
|
+
*
|
|
819
|
+
* Expects minified (comment-free) output: a raw-text scan cannot tell a real
|
|
820
|
+
* import from a JSDoc code sample, and unminified kysely/viem are full of the
|
|
821
|
+
* latter. The build below always minifies for exactly this reason.
|
|
822
|
+
*/
|
|
823
|
+
function findDisallowedSpecifiers(code) {
|
|
824
|
+
const found = /* @__PURE__ */ new Set();
|
|
825
|
+
for (const pattern of SPECIFIER_PATTERNS) for (const match of code.matchAll(pattern)) {
|
|
826
|
+
const spec = match[1];
|
|
827
|
+
if (!spec.startsWith("./") && !spec.startsWith("../") && !spec.startsWith("/") && !/^(https?:|data:|blob:)/.test(spec)) found.add(spec);
|
|
828
|
+
}
|
|
829
|
+
return [...found].sort();
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Fallback list of packages whose BUILT dist shapes the worker bundle's output,
|
|
833
|
+
* used only when the installed Connect's package.json cannot be read (see
|
|
834
|
+
* {@link upstreamWorkerPackages}).
|
|
835
|
+
*/
|
|
836
|
+
const FALLBACK_UPSTREAM_WORKER_PACKAGES = ["@powerhousedao/reactor", "@powerhousedao/reactor-browser"];
|
|
837
|
+
/** Dependency scopes whose installed dist can change the bundle's output. */
|
|
838
|
+
const UPSTREAM_SCOPES = ["@powerhousedao/", "@renown/"];
|
|
839
|
+
/**
|
|
840
|
+
* Packages whose BUILT dist shapes the worker bundle's output even when
|
|
841
|
+
* connect's own dist/reactor.worker.js entry file did not change.
|
|
842
|
+
*
|
|
843
|
+
* That entry is a library artifact whose bare imports this build resolves
|
|
844
|
+
* through node_modules at BUILD time, so a rebuilt dist anywhere in its
|
|
845
|
+
* closure changes the bundle output without touching the entry file's own
|
|
846
|
+
* size/mtime. The list is DERIVED from the installed Connect's own
|
|
847
|
+
* dependencies rather than hardcoded: the bundle pulls `@renown/sdk`,
|
|
848
|
+
* `@powerhousedao/powerhouse-vetra-packages` and `@powerhousedao/shared` out
|
|
849
|
+
* of node_modules too, and a hardcoded pair left every one of those able to be
|
|
850
|
+
* rebuilt behind a cache hit. Connect's dependency list is the cheap stand-in
|
|
851
|
+
* for the resolved bundle graph: every workspace package the worker can reach
|
|
852
|
+
* is reachable through it, and a package not in the bundle only costs a readdir.
|
|
853
|
+
*/
|
|
854
|
+
function upstreamWorkerPackages(dirname) {
|
|
855
|
+
const connectDir = resolveWorkspacePackageDir(dirname, "@powerhousedao/connect");
|
|
856
|
+
if (!connectDir) return [...FALLBACK_UPSTREAM_WORKER_PACKAGES];
|
|
857
|
+
let meta;
|
|
858
|
+
try {
|
|
859
|
+
meta = JSON.parse(readFileSync(join(connectDir, "package.json"), "utf8"));
|
|
860
|
+
} catch {
|
|
861
|
+
return [...FALLBACK_UPSTREAM_WORKER_PACKAGES];
|
|
862
|
+
}
|
|
863
|
+
const names = Object.keys(meta.dependencies ?? {}).filter((name) => UPSTREAM_SCOPES.some((scope) => name.startsWith(scope)));
|
|
864
|
+
for (const name of FALLBACK_UPSTREAM_WORKER_PACKAGES) if (!names.includes(name)) names.push(name);
|
|
865
|
+
return names.sort();
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* This module's own installed version, read by walking up from its file to
|
|
869
|
+
* the nearest `package.json` named "@powerhousedao/builder-tools" (same
|
|
870
|
+
* technique as `build-hash.ts`'s `readConnectVersion`: builder-tools'
|
|
871
|
+
* `exports` map has no `./package.json` subpath, so node resolution can't
|
|
872
|
+
* get there). Works whether this runs from source (tests, this monorepo) or
|
|
873
|
+
* the built dist.
|
|
874
|
+
*/
|
|
875
|
+
function resolveOwnPackageVersion() {
|
|
876
|
+
try {
|
|
877
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
878
|
+
for (;;) {
|
|
879
|
+
try {
|
|
880
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
881
|
+
if (pkg.name === "@powerhousedao/builder-tools") return pkg.version ?? "unknown";
|
|
882
|
+
} catch {}
|
|
883
|
+
const parent = dirname(dir);
|
|
884
|
+
if (parent === dir) break;
|
|
885
|
+
dir = parent;
|
|
886
|
+
}
|
|
887
|
+
} catch {}
|
|
888
|
+
return "unknown";
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* This package's own version, computed once. Folded into the worker bundle
|
|
892
|
+
* cache key so a builder-tools release — which can change how the bundle is
|
|
893
|
+
* built (anything in this module beyond {@link WORKER_BUILD_WORKER} itself,
|
|
894
|
+
* e.g. `workerSafeVendorImports`, the resolve plugin) — invalidates bundles
|
|
895
|
+
* a prior builder-tools version cached.
|
|
896
|
+
*/
|
|
897
|
+
const BUILDER_TOOLS_VERSION = resolveOwnPackageVersion();
|
|
898
|
+
/**
|
|
899
|
+
* A workspace package's real directory, resolved the way Node's resolver
|
|
900
|
+
* would (through `node_modules`), or null when it is not installed (e.g. a
|
|
901
|
+
* non-monorepo consumer project — graceful, not an error). pnpm's
|
|
902
|
+
* node_modules symlinks resolve to the real package directory, so this
|
|
903
|
+
* reflects the monorepo's actual dist state, not a stale symlink target.
|
|
904
|
+
*/
|
|
905
|
+
function resolveWorkspacePackageDir(dirname, pkgName) {
|
|
906
|
+
try {
|
|
907
|
+
return realpathSync(join(dirname, "node_modules", ...pkgName.split("/")));
|
|
908
|
+
} catch {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Cheap content fingerprint of a directory's immediate files: no file bytes
|
|
914
|
+
* are read, just each entry's name/size/mtime, so it stays fast even for a
|
|
915
|
+
* large dist. Sensitive to a rebuild in the common cases: Vite's
|
|
916
|
+
* content-hashed chunk filenames change the name itself; a stable-named file
|
|
917
|
+
* (an entry point) changes size or mtime. Bounded (`maxFiles`) so a
|
|
918
|
+
* pathological directory cannot make this slow — deliberately not a
|
|
919
|
+
* full-tree hash.
|
|
920
|
+
*
|
|
921
|
+
* The names are sorted BEFORE the bound is applied, so the subset the bound
|
|
922
|
+
* keeps is the same subset on every run: readdir order is unspecified, and
|
|
923
|
+
* truncating it first made the fingerprint of a directory past the bound a
|
|
924
|
+
* function of whatever order the filesystem happened to report.
|
|
925
|
+
*/
|
|
926
|
+
function distDirFingerprint(dir, maxFiles = 1e3) {
|
|
927
|
+
try {
|
|
928
|
+
return readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort().slice(0, maxFiles).map((name) => {
|
|
929
|
+
const stat = statSync(join(dir, name));
|
|
930
|
+
return `${name}:${stat.size}:${Math.round(stat.mtimeMs)}`;
|
|
931
|
+
}).join("|");
|
|
932
|
+
} catch {
|
|
933
|
+
return "unreadable";
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
function computeSourceDigest(dirname, entryPath, vendorDir, builderToolsVersion = BUILDER_TOOLS_VERSION) {
|
|
937
|
+
const h = createHash("sha256");
|
|
938
|
+
h.update(`builder:${WORKER_BUILD_WORKER_HASH}\n`);
|
|
939
|
+
h.update(`builder-tools:${builderToolsVersion}\n`);
|
|
940
|
+
try {
|
|
941
|
+
const pkgRoot = resolveWorkspacePackageDir(dirname, "@powerhousedao/connect");
|
|
942
|
+
if (!pkgRoot) throw new Error("unresolved");
|
|
943
|
+
const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
|
|
944
|
+
h.update(`connect:${meta.version ?? "unknown"}\n`);
|
|
945
|
+
} catch {
|
|
946
|
+
h.update("connect:unresolved\n");
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
const stat = statSync(entryPath);
|
|
950
|
+
h.update(`entry:${stat.size}:${Math.round(stat.mtimeMs)}\n`);
|
|
951
|
+
} catch {
|
|
952
|
+
h.update("entry:unstatable\n");
|
|
953
|
+
}
|
|
954
|
+
for (const pkg of upstreamWorkerPackages(dirname)) {
|
|
955
|
+
const pkgDir = resolveWorkspacePackageDir(dirname, pkg);
|
|
956
|
+
h.update(`${pkg}:${pkgDir ? distDirFingerprint(join(pkgDir, "dist")) : "unresolved"}\n`);
|
|
957
|
+
}
|
|
958
|
+
if (vendorDir) try {
|
|
959
|
+
h.update(`vendor:${createHash("sha256").update(readFileSync(join(vendorDir, "import-map.json"))).digest("hex")}\n`);
|
|
960
|
+
} catch {
|
|
961
|
+
h.update("vendor:unreadable\n");
|
|
962
|
+
}
|
|
963
|
+
return h.digest("hex").slice(0, 16);
|
|
964
|
+
}
|
|
965
|
+
function vendorKeyOf(vendorImports) {
|
|
966
|
+
const entries = Object.entries(vendorImports).sort(([a], [b]) => a.localeCompare(b));
|
|
967
|
+
return createHash("sha256").update(JSON.stringify(entries)).digest("hex").slice(0, 16);
|
|
968
|
+
}
|
|
969
|
+
function readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv) {
|
|
970
|
+
if (!existsSync(metaPath)) return false;
|
|
971
|
+
try {
|
|
972
|
+
const cached = JSON.parse(readFileSync(metaPath, "utf8"));
|
|
973
|
+
return cached.sourceDigest === sourceDigest && cached.vendorKey === vendorKey && cached.nodeEnv === nodeEnv && existsSync(join(dirname(metaPath), "reactor.worker.js"));
|
|
974
|
+
} catch {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
async function waitForWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv, lockDir) {
|
|
979
|
+
const deadline = Date.now() + LOCK_STALE_MS;
|
|
980
|
+
while (Date.now() < deadline) {
|
|
981
|
+
if (readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv)) return true;
|
|
982
|
+
if (!existsSync(lockDir)) return readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv);
|
|
983
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
984
|
+
}
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Build the worker bundle (once, in a throwaway subprocess) into `outDir`.
|
|
989
|
+
* Idempotent: reuses a cached bundle built from the same connect dist, vendor
|
|
990
|
+
* mapping, and builder logic. Returns null on failure (`errorRef` carries the
|
|
991
|
+
* cause) or when the installed Connect has no worker entry.
|
|
992
|
+
*/
|
|
993
|
+
async function prebuildReactorWorker(options) {
|
|
994
|
+
const entryPath = options.entryPath ?? resolveReactorWorkerSource(options.dirname);
|
|
995
|
+
if (!entryPath) {
|
|
996
|
+
if (options.errorRef) options.errorRef.message = "the installed @powerhousedao/connect ships no dist/reactor.worker.js";
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
const vendorImports = options.vendor ? workerSafeVendorImports(options.vendor.dir, options.vendor.imports) : {};
|
|
1000
|
+
const nodeEnv = options.nodeEnv ?? "development";
|
|
1001
|
+
const outDir = options.outDir;
|
|
1002
|
+
const metaPath = join(outDir, META_FILE);
|
|
1003
|
+
const sourceDigest = computeSourceDigest(options.dirname, entryPath, options.vendor?.dir);
|
|
1004
|
+
const vendorKey = vendorKeyOf(vendorImports);
|
|
1005
|
+
const result = {
|
|
1006
|
+
outDir,
|
|
1007
|
+
entry: REACTOR_WORKER_ENTRY,
|
|
1008
|
+
sourceDigest,
|
|
1009
|
+
vendorImports
|
|
1010
|
+
};
|
|
1011
|
+
try {
|
|
1012
|
+
if (readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv)) return result;
|
|
1013
|
+
const lockDir = `${outDir}.lock`;
|
|
1014
|
+
const lock = acquireLock(lockDir);
|
|
1015
|
+
if (!lock) return await waitForWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv, lockDir) ? result : null;
|
|
1016
|
+
try {
|
|
1017
|
+
if (readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv)) return result;
|
|
1018
|
+
await buildWorkerAtomic(options.dirname, outDir, entryPath, vendorImports, nodeEnv, {
|
|
1019
|
+
sourceDigest,
|
|
1020
|
+
vendorKey,
|
|
1021
|
+
nodeEnv
|
|
1022
|
+
});
|
|
1023
|
+
return result;
|
|
1024
|
+
} finally {
|
|
1025
|
+
releaseLock(lock);
|
|
1026
|
+
}
|
|
1027
|
+
} catch (err) {
|
|
1028
|
+
if (options.errorRef) options.errorRef.message = err instanceof Error ? err.message : String(err);
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async function buildWorkerAtomic(dirname$2, outDir, entryPath, vendorImports, nodeEnv, meta) {
|
|
1033
|
+
const parent = dirname(outDir);
|
|
1034
|
+
mkdirSync(parent, { recursive: true });
|
|
1035
|
+
const tmpDir = mkdtempSync(join(parent, ".ph-reactor-worker.tmp-"));
|
|
1036
|
+
try {
|
|
1037
|
+
const { ok, stderr } = await runWorkerBuild(dirname$2, tmpDir, { "reactor.worker": entryPath }, vendorImports, nodeEnv);
|
|
1038
|
+
if (!ok || !existsSync(join(tmpDir, "reactor.worker.js"))) {
|
|
1039
|
+
const detail = stderr.trim();
|
|
1040
|
+
throw new Error(`reactor worker build failed${detail ? `:\n${detail}` : " (no output captured)"}`);
|
|
1041
|
+
}
|
|
1042
|
+
assertWorkerResolvable(tmpDir);
|
|
1043
|
+
writeFileSync(join(tmpDir, META_FILE), JSON.stringify(meta, null, 2));
|
|
1044
|
+
publishDirAtomic(tmpDir, outDir);
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
rmSync(tmpDir, {
|
|
1047
|
+
recursive: true,
|
|
1048
|
+
force: true
|
|
1049
|
+
});
|
|
1050
|
+
throw err;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
/** Replace `outDir` with the finished `tmpDir`, old contents removed last. */
|
|
1054
|
+
function publishDirAtomic(tmpDir, outDir) {
|
|
1055
|
+
const oldDir = `${outDir}.old-${process.pid}-${Date.now()}`;
|
|
1056
|
+
if (existsSync(outDir)) renameSync(outDir, oldDir);
|
|
1057
|
+
renameSync(tmpDir, outDir);
|
|
1058
|
+
chmodSync(outDir, 493);
|
|
1059
|
+
rmSync(oldDir, {
|
|
1060
|
+
recursive: true,
|
|
1061
|
+
force: true
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
const RELATIVE_SPECIFIER_PATTERNS = SPECIFIER_PATTERNS;
|
|
1065
|
+
function findRelativeSpecifiers(code) {
|
|
1066
|
+
const found = /* @__PURE__ */ new Set();
|
|
1067
|
+
for (const pattern of RELATIVE_SPECIFIER_PATTERNS) for (const match of code.matchAll(pattern)) {
|
|
1068
|
+
const spec = match[1];
|
|
1069
|
+
if (spec.startsWith("./") || spec.startsWith("../")) found.add(spec);
|
|
1070
|
+
}
|
|
1071
|
+
return [...found];
|
|
1072
|
+
}
|
|
1073
|
+
function normalizeBundlePath(fromFile, spec) {
|
|
1074
|
+
const segments = fromFile.split("/").slice(0, -1);
|
|
1075
|
+
for (const part of spec.split("/")) {
|
|
1076
|
+
if (part === "" || part === ".") continue;
|
|
1077
|
+
if (part === "..") {
|
|
1078
|
+
if (segments.length === 0) return "";
|
|
1079
|
+
segments.pop();
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
segments.push(part);
|
|
1083
|
+
}
|
|
1084
|
+
return segments.join("/");
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Scan the emitted JS files REACHABLE from the entry via import statements
|
|
1088
|
+
* for specifiers a worker cannot resolve. Vendor externals were rewritten to
|
|
1089
|
+
* ../__vendor__/ relative paths, so anything bare that survives in the graph
|
|
1090
|
+
* is a real defect.
|
|
1091
|
+
*
|
|
1092
|
+
* Deliberately a graph walk, not a directory walk: the bundled reactor code
|
|
1093
|
+
* carries `new URL(..., import.meta.url)` references to its node
|
|
1094
|
+
* worker-thread entries (executor/projection workers), which Vite emits as
|
|
1095
|
+
* stray assets full of node-only imports. They are unreachable dead weight in
|
|
1096
|
+
* a browser bundle — the production vendor output tolerates the same files —
|
|
1097
|
+
* and must not fail the build.
|
|
1098
|
+
*/
|
|
1099
|
+
function findBundleSpecifierOffenders(bundleDir, entry = REACTOR_WORKER_ENTRY) {
|
|
1100
|
+
const offenders = [];
|
|
1101
|
+
walkBundleGraph(bundleDir, entry, (file, code) => {
|
|
1102
|
+
const specs = findDisallowedSpecifiers(code);
|
|
1103
|
+
if (specs.length > 0) offenders.push({
|
|
1104
|
+
file,
|
|
1105
|
+
specs
|
|
1106
|
+
});
|
|
1107
|
+
return true;
|
|
1108
|
+
});
|
|
1109
|
+
return offenders;
|
|
1110
|
+
}
|
|
1111
|
+
function walkBundleGraph(bundleDir, entry, visit) {
|
|
1112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1113
|
+
const queue = [entry];
|
|
1114
|
+
while (queue.length > 0) {
|
|
1115
|
+
const rel = queue.shift();
|
|
1116
|
+
if (seen.has(rel)) continue;
|
|
1117
|
+
seen.add(rel);
|
|
1118
|
+
const full = join(bundleDir, rel);
|
|
1119
|
+
if (!existsSync(full) || statSync(full).isDirectory()) continue;
|
|
1120
|
+
if (!rel.endsWith(".js") && !rel.endsWith(".mjs")) continue;
|
|
1121
|
+
const code = readFileSync(full, "utf8");
|
|
1122
|
+
if (!visit(rel, code)) return false;
|
|
1123
|
+
for (const spec of findRelativeSpecifiers(code)) {
|
|
1124
|
+
const next = normalizeBundlePath(rel, spec);
|
|
1125
|
+
if (next) queue.push(next);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
function assertWorkerResolvable(bundleDir) {
|
|
1131
|
+
const offenders = findBundleSpecifierOffenders(bundleDir);
|
|
1132
|
+
if (offenders.length > 0) throw new Error("reactor worker bundle contains module specifiers a worker cannot resolve:\n" + offenders.map((o) => ` ${o.file}: ${o.specs.join(", ")}`).join("\n"));
|
|
1133
|
+
}
|
|
1134
|
+
const WORKER_UNSAFE_MARKERS = ["__PH_DYNAMIC_BASE__", "vite:preloadError"];
|
|
1135
|
+
/** The page-only markers present in `code`, in a fixed order. */
|
|
1136
|
+
function findWorkerUnsafeMarkers(code) {
|
|
1137
|
+
return WORKER_UNSAFE_MARKERS.filter((marker) => code.includes(marker));
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* The subset of the vendor's entries a worker may import.
|
|
1141
|
+
*
|
|
1142
|
+
* The vendor build keeps the React family external: its chunks carry bare
|
|
1143
|
+
* `import "react"` statements that the PAGE resolves through its import map.
|
|
1144
|
+
* A worker resolves no import map, so externalizing a vendor spec whose chunk
|
|
1145
|
+
* closure reaches such an import would kill the worker on its first load —
|
|
1146
|
+
* and the chunk graph is shared across vendor entries, so even a React-free
|
|
1147
|
+
* module (e.g. an rpc subpath) can be entangled with React through a shared
|
|
1148
|
+
* chunk. The vendor is also built for the page: a dynamic-base build rewrites
|
|
1149
|
+
* its asset URLs onto a global only the page sets, and its dynamic imports go
|
|
1150
|
+
* through Vite's preload helper, which needs `document`. Each entry's own
|
|
1151
|
+
* closure inside the vendor dir decides: clean closure, worker-safe; anything
|
|
1152
|
+
* bare or page-only in it (see {@link findWorkerUnsafeMarkers}), or a missing
|
|
1153
|
+
* entry file, bundled into the worker instead.
|
|
1154
|
+
*/
|
|
1155
|
+
function workerSafeVendorImports(vendorDir, vendorImports) {
|
|
1156
|
+
const safe = {};
|
|
1157
|
+
for (const [spec, url] of Object.entries(vendorImports)) {
|
|
1158
|
+
const entryFile = url.slice(url.lastIndexOf("/") + 1);
|
|
1159
|
+
if (!existsSync(join(vendorDir, entryFile))) continue;
|
|
1160
|
+
if (walkBundleGraph(vendorDir, entryFile, (_file, code) => findDisallowedSpecifiers(code).length === 0 && findWorkerUnsafeMarkers(code).length === 0)) safe[spec] = url;
|
|
1161
|
+
}
|
|
1162
|
+
return safe;
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Builds arbitrary worker-loadable entries with the worker build config.
|
|
1166
|
+
* Exported for the local-package bundles, which need the same self-contained
|
|
1167
|
+
* output and vendor externalization at a different depth.
|
|
1168
|
+
*/
|
|
1169
|
+
function runWorkerBuildEntries(dirname, outDir, entries, vendorImports, nodeEnv, vendorPrefix = "../__vendor__/") {
|
|
1170
|
+
return runWorkerBuild(dirname, outDir, entries, vendorImports, nodeEnv, vendorPrefix);
|
|
1171
|
+
}
|
|
1172
|
+
function runWorkerBuild(dirname, outDir, entries, vendorImports, nodeEnv, vendorPrefix = "../__vendor__/") {
|
|
1173
|
+
const workerPath = join(outDir, "build-worker.mjs");
|
|
1174
|
+
writeFileSync(workerPath, WORKER_BUILD_WORKER);
|
|
1175
|
+
return new Promise((resolve) => {
|
|
1176
|
+
const child = spawn(process.execPath, [
|
|
1177
|
+
workerPath,
|
|
1178
|
+
dirname,
|
|
1179
|
+
outDir,
|
|
1180
|
+
JSON.stringify(entries),
|
|
1181
|
+
JSON.stringify(vendorImports),
|
|
1182
|
+
nodeEnv,
|
|
1183
|
+
resolveBuilderVite() ?? "",
|
|
1184
|
+
vendorPrefix
|
|
1185
|
+
], {
|
|
1186
|
+
cwd: dirname,
|
|
1187
|
+
stdio: [
|
|
1188
|
+
"ignore",
|
|
1189
|
+
"pipe",
|
|
1190
|
+
"pipe"
|
|
1191
|
+
]
|
|
1192
|
+
});
|
|
1193
|
+
let stderr = "";
|
|
1194
|
+
child.stdout.on("data", (d) => {
|
|
1195
|
+
stderr += String(d);
|
|
1196
|
+
});
|
|
1197
|
+
child.stderr.on("data", (d) => {
|
|
1198
|
+
stderr += String(d);
|
|
1199
|
+
});
|
|
1200
|
+
child.on("exit", (code) => {
|
|
1201
|
+
rmSync(workerPath, { force: true });
|
|
1202
|
+
resolve({
|
|
1203
|
+
ok: code === 0,
|
|
1204
|
+
stderr
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
child.on("error", (e) => resolve({
|
|
1208
|
+
ok: false,
|
|
1209
|
+
stderr: String(e)
|
|
1210
|
+
}));
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* The vite installed next to this module. The subprocess must run the vite
|
|
1215
|
+
* this build config was written against: a consumer project can pin an older
|
|
1216
|
+
* vite/rolldown (overrides) whose bundler leaves unresolvable specifiers bare
|
|
1217
|
+
* instead of erroring and shims node builtins differently — the output guard
|
|
1218
|
+
* then correctly rejects the bundle. builder-tools declares vite as a direct
|
|
1219
|
+
* dependency, so this resolution works wherever builder-tools is installed.
|
|
1220
|
+
*/
|
|
1221
|
+
function resolveBuilderVite() {
|
|
1222
|
+
try {
|
|
1223
|
+
return createRequire(import.meta.url).resolve("vite");
|
|
1224
|
+
} catch {
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* The build subprocess (same pattern as the vendor's). Loads builder-tools'
|
|
1230
|
+
* own vite (argv; project fallback) and builds the given entries with
|
|
1231
|
+
* relative base, so chunk/asset URLs resolve against each script's own URL
|
|
1232
|
+
* under any deploy base. argv: dirname, outDir, entriesJSON (name ->
|
|
1233
|
+
* absolute entry path), vendorImportsJSON, nodeEnv, vitePath, vendorPrefix
|
|
1234
|
+
* (relative path from the out dir to __vendor__/, e.g. "../__vendor__/").
|
|
1235
|
+
*/
|
|
1236
|
+
const WORKER_BUILD_WORKER = `
|
|
1237
|
+
import { createRequire } from 'node:module';
|
|
1238
|
+
import { isAbsolute } from 'node:path';
|
|
1239
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
1240
|
+
const [dirname, outDir, entriesJSON, vendorImportsJSON, nodeEnvArg, vitePathArg, vendorPrefixArg] = process.argv.slice(2);
|
|
1241
|
+
const nodeEnv = nodeEnvArg ?? 'development';
|
|
1242
|
+
const entries = JSON.parse(entriesJSON);
|
|
1243
|
+
const vendorImports = JSON.parse(vendorImportsJSON ?? '{}');
|
|
1244
|
+
const vendorPrefix = vendorPrefixArg || '../__vendor__/';
|
|
1245
|
+
const externalSet = new Set(Object.keys(vendorImports));
|
|
1246
|
+
const vendorPath = (spec) => {
|
|
1247
|
+
const url = vendorImports[spec];
|
|
1248
|
+
return vendorPrefix + url.slice(url.lastIndexOf('/') + 1);
|
|
1249
|
+
};
|
|
1250
|
+
const reqProj = createRequire(dirname + '/noop.js');
|
|
1251
|
+
// pathToFileURL: on Windows import('D:\\\\...') parses "D:" as a URL scheme.
|
|
1252
|
+
const vitePath = vitePathArg || reqProj.resolve('vite');
|
|
1253
|
+
const { build } = await import(pathToFileURL(vitePath).href);
|
|
1254
|
+
// Resolve bare specifiers from the project (same plugin as the vendor build):
|
|
1255
|
+
// prefer the bundler's browser-condition-aware resolution, fall back to the
|
|
1256
|
+
// worker's own resolution for Rolldown's realpath-anchoring bug.
|
|
1257
|
+
const phResolveCache = new Map();
|
|
1258
|
+
const phWorkerResolve = {
|
|
1259
|
+
name: 'ph-reactor-worker-resolve', enforce: 'pre',
|
|
1260
|
+
async resolveId(source, importer, options) {
|
|
1261
|
+
if (externalSet.has(source)) return null;
|
|
1262
|
+
if (source[0] === '\\0' || source[0] === '.' || isAbsolute(source)) return null;
|
|
1263
|
+
if (source.startsWith('node:') || source.startsWith('data:')) return null;
|
|
1264
|
+
let viaBundler = null;
|
|
1265
|
+
try { viaBundler = await this.resolve(source, importer, { ...options, skipSelf: true }); } catch {}
|
|
1266
|
+
if (viaBundler) return viaBundler;
|
|
1267
|
+
if (phResolveCache.has(source)) return phResolveCache.get(source);
|
|
1268
|
+
let resolved = null;
|
|
1269
|
+
try { resolved = fileURLToPath(import.meta.resolve(source)); } catch {}
|
|
1270
|
+
phResolveCache.set(source, resolved);
|
|
1271
|
+
return resolved;
|
|
1272
|
+
},
|
|
1273
|
+
};
|
|
1274
|
+
// A worker has no window: on a failed dynamic import, the preload helper's
|
|
1275
|
+
// window.dispatchEvent would throw a ReferenceError in place of the real error.
|
|
1276
|
+
const PRELOAD_HELPER_ID = '\\0vite/preload-helper.js';
|
|
1277
|
+
const PRELOAD_DISPATCH = 'window.dispatchEvent(';
|
|
1278
|
+
const phWorkerPreloadHelper = {
|
|
1279
|
+
name: 'ph-reactor-worker-preload-helper',
|
|
1280
|
+
transform(code, id) {
|
|
1281
|
+
if (id !== PRELOAD_HELPER_ID) return null;
|
|
1282
|
+
if (code.split(PRELOAD_DISPATCH).length !== 2) {
|
|
1283
|
+
this.error('expected exactly one ' + PRELOAD_DISPATCH + ' in ' + PRELOAD_HELPER_ID);
|
|
1284
|
+
}
|
|
1285
|
+
return { code: code.replace(PRELOAD_DISPATCH, 'globalThis.dispatchEvent?.('), map: null };
|
|
1286
|
+
},
|
|
1287
|
+
};
|
|
1288
|
+
await build({
|
|
1289
|
+
root: dirname, configFile: false, logLevel: 'error',
|
|
1290
|
+
// The project's public/ dir belongs to the app build, not this bundle.
|
|
1291
|
+
publicDir: false,
|
|
1292
|
+
// Relative base: emitted chunk/asset URLs resolve against the worker
|
|
1293
|
+
// script's own URL, so the bundle works under any deploy base.
|
|
1294
|
+
base: './',
|
|
1295
|
+
define: {
|
|
1296
|
+
'process.env.NODE_ENV': JSON.stringify(nodeEnv),
|
|
1297
|
+
'import.meta.env.BASE_URL': JSON.stringify('./'),
|
|
1298
|
+
},
|
|
1299
|
+
plugins: [phWorkerResolve, phWorkerPreloadHelper],
|
|
1300
|
+
// pglite ships nested web workers as ES-module chunks.
|
|
1301
|
+
worker: { format: 'es' },
|
|
1302
|
+
build: {
|
|
1303
|
+
// Always minified, dev included: the bundle is browser-served output
|
|
1304
|
+
// (sourcemaps carry the debugging story), and minification strips the
|
|
1305
|
+
// JSDoc comments whose embedded code samples (kysely's, viem's) would
|
|
1306
|
+
// otherwise trip the bare-specifier guard below.
|
|
1307
|
+
outDir, emptyOutDir: false, minify: true, target: 'esnext', sourcemap: true,
|
|
1308
|
+
// No document in a worker: the module-preload polyfill and preload helper
|
|
1309
|
+
// touch the DOM, so dynamic imports must stay plain import().
|
|
1310
|
+
modulePreload: false,
|
|
1311
|
+
rollupOptions: {
|
|
1312
|
+
input: entries,
|
|
1313
|
+
// A models entry is pure re-exports; without this the bundler treats
|
|
1314
|
+
// its exports as unused and tree-shakes the whole file to nothing.
|
|
1315
|
+
// Harmless for the worker entry, which exports nothing to preserve.
|
|
1316
|
+
preserveEntrySignatures: 'strict',
|
|
1317
|
+
external: (id) => externalSet.has(id),
|
|
1318
|
+
output: {
|
|
1319
|
+
format: 'es', entryFileNames: '[name].js',
|
|
1320
|
+
// Chunks live at the bundle root, not a chunks/ subdir: the vendor
|
|
1321
|
+
// paths below are emitted VERBATIM into every chunk, so all modules
|
|
1322
|
+
// must sit at the same depth for ../__vendor__/ to resolve.
|
|
1323
|
+
chunkFileNames: '[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]',
|
|
1324
|
+
paths: (id) => (externalSet.has(id) ? vendorPath(id) : id),
|
|
1325
|
+
},
|
|
1326
|
+
},
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
1329
|
+
`;
|
|
1330
|
+
const WORKER_BUILD_WORKER_HASH = createHash("sha256").update(WORKER_BUILD_WORKER).digest("hex").slice(0, 16);
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region connect-utils/worker-packages-build.ts
|
|
1333
|
+
/**
|
|
1334
|
+
* Prebuild a project's local packages into worker-loadable model bundles.
|
|
1335
|
+
*
|
|
1336
|
+
* The reactor worker gets document models from the registry CDN or from what
|
|
1337
|
+
* Connect statically bundled. A project's own (`provider: "local"`) package is
|
|
1338
|
+
* in neither, so its document types have no reducer in the worker and their
|
|
1339
|
+
* load jobs fail. Dev solves this by pointing the worker at the dev server's
|
|
1340
|
+
* transformed models entry; a production deployment has no dev server, so the
|
|
1341
|
+
* entry is prebuilt here, next to the worker bundle it is loaded beside.
|
|
1342
|
+
*
|
|
1343
|
+
* Each package's `browser/document-models/index.js` (reducers only: no React,
|
|
1344
|
+
* no CSS, no editors) is built self-contained, with the worker-safe subset of
|
|
1345
|
+
* the vendor externalized onto `../../__vendor__/` - two levels up, since
|
|
1346
|
+
* these land in `__reactor_worker__/packages/`. A `manifest.json` names what
|
|
1347
|
+
* was built so the tab can turn it into package sources without guessing.
|
|
1348
|
+
*/
|
|
1349
|
+
/** Served under `<base>__reactor_worker__/packages/`. */
|
|
1350
|
+
const WORKER_PACKAGES_MANIFEST = "manifest.json";
|
|
1351
|
+
/** Vendor is two levels up from `__reactor_worker__/packages/`. */
|
|
1352
|
+
const VENDOR_PREFIX = "../../__vendor__/";
|
|
1353
|
+
function readVersion(root) {
|
|
1354
|
+
try {
|
|
1355
|
+
const meta = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
1356
|
+
return typeof meta.version === "string" ? meta.version : void 0;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
/**
|
|
1362
|
+
* A package root's models entry, or null when it ships none.
|
|
1363
|
+
*
|
|
1364
|
+
* Two roots are possible and both are normal: an installed dependency under
|
|
1365
|
+
* `node_modules/<name>`, and the project itself - the vetra case, where the
|
|
1366
|
+
* project *is* the local package and has no self-link in node_modules.
|
|
1367
|
+
*/
|
|
1368
|
+
function resolvePackageModels(dirname, name) {
|
|
1369
|
+
const roots = [];
|
|
1370
|
+
try {
|
|
1371
|
+
roots.push(realpathSync(join(dirname, "node_modules", name)));
|
|
1372
|
+
} catch {}
|
|
1373
|
+
if (readProjectName(dirname) === name) roots.push(dirname);
|
|
1374
|
+
for (const root of roots) {
|
|
1375
|
+
const entryPath = join(root, "dist", "browser", "document-models", "index.js");
|
|
1376
|
+
if (existsSync(entryPath)) return {
|
|
1377
|
+
entryPath,
|
|
1378
|
+
version: readVersion(root)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
function readProjectName(dirname) {
|
|
1384
|
+
try {
|
|
1385
|
+
const meta = JSON.parse(readFileSync(join(dirname, "package.json"), "utf8"));
|
|
1386
|
+
return typeof meta.name === "string" ? meta.name : void 0;
|
|
1387
|
+
} catch {
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* The project's own package name when it ships a models bundle, so a caller
|
|
1393
|
+
* can include it beside the configured `provider: "local"` packages. A vetra
|
|
1394
|
+
* project declares no package entry for itself, yet its models are exactly
|
|
1395
|
+
* the ones the worker is missing.
|
|
1396
|
+
*/
|
|
1397
|
+
function ownProjectPackage(dirname) {
|
|
1398
|
+
const name = readProjectName(dirname);
|
|
1399
|
+
if (!name) return void 0;
|
|
1400
|
+
return resolvePackageModels(dirname, name) ? name : void 0;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Build each local package's models entry into `outDir` and write the
|
|
1404
|
+
* manifest the tab reads. Returns the manifest entries (empty when no local
|
|
1405
|
+
* package ships a models bundle), or null when the build itself failed -
|
|
1406
|
+
* `errorRef` then carries the cause. Both of those remove `outDir`, so a
|
|
1407
|
+
* previous build's bundles never outlive the packages they came from.
|
|
1408
|
+
*/
|
|
1409
|
+
async function prebuildWorkerPackages(options) {
|
|
1410
|
+
const nodeEnv = options.nodeEnv ?? "production";
|
|
1411
|
+
const resolved = [];
|
|
1412
|
+
for (const name of options.packages) {
|
|
1413
|
+
const models = resolvePackageModels(options.dirname, name);
|
|
1414
|
+
if (!models) continue;
|
|
1415
|
+
resolved.push({
|
|
1416
|
+
entry: {
|
|
1417
|
+
name,
|
|
1418
|
+
version: models.version,
|
|
1419
|
+
file: workerPackageFileName(name)
|
|
1420
|
+
},
|
|
1421
|
+
entryPath: models.entryPath
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
if (resolved.length === 0) {
|
|
1425
|
+
rmSync(options.outDir, {
|
|
1426
|
+
recursive: true,
|
|
1427
|
+
force: true
|
|
1428
|
+
});
|
|
1429
|
+
return [];
|
|
1430
|
+
}
|
|
1431
|
+
const vendorImports = options.safeVendorImports ?? (options.vendor ? workerSafeVendorImports(options.vendor.dir, options.vendor.imports) : {});
|
|
1432
|
+
const entries = Object.fromEntries(resolved.map((item) => [item.entry.file.replace(/\.js$/, ""), item.entryPath]));
|
|
1433
|
+
const parent = dirname(options.outDir);
|
|
1434
|
+
mkdirSync(parent, { recursive: true });
|
|
1435
|
+
const tmpDir = mkdtempSync(join(parent, ".ph-worker-packages.tmp-"));
|
|
1436
|
+
try {
|
|
1437
|
+
const { ok, stderr } = await runWorkerBuildEntries(options.dirname, tmpDir, entries, vendorImports, nodeEnv, VENDOR_PREFIX);
|
|
1438
|
+
const missing = resolved.filter((item) => !existsSync(join(tmpDir, item.entry.file)));
|
|
1439
|
+
if (!ok || missing.length > 0) {
|
|
1440
|
+
const detail = stderr.trim();
|
|
1441
|
+
throw new Error(`worker package bundles failed to build${missing.length > 0 ? ` (missing: ${missing.map((m) => m.entry.name).join(", ")})` : ""}${detail ? `:\n${detail}` : ""}`);
|
|
1442
|
+
}
|
|
1443
|
+
for (const item of resolved) {
|
|
1444
|
+
const offenders = findBundleSpecifierOffenders(tmpDir, item.entry.file);
|
|
1445
|
+
if (offenders.length > 0) throw new Error(`worker package bundle for ${item.entry.name} contains module specifiers a worker cannot resolve:\n` + offenders.map((o) => ` ${o.file}: ${o.specs.join(", ")}`).join("\n"));
|
|
1446
|
+
}
|
|
1447
|
+
const manifest = resolved.map((item) => item.entry);
|
|
1448
|
+
writeFileSync(join(tmpDir, WORKER_PACKAGES_MANIFEST), JSON.stringify(manifest, null, 2));
|
|
1449
|
+
publishDirAtomic(tmpDir, options.outDir);
|
|
1450
|
+
return manifest;
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
rmSync(tmpDir, {
|
|
1453
|
+
recursive: true,
|
|
1454
|
+
force: true
|
|
1455
|
+
});
|
|
1456
|
+
rmSync(options.outDir, {
|
|
1457
|
+
recursive: true,
|
|
1458
|
+
force: true
|
|
1459
|
+
});
|
|
1460
|
+
if (options.errorRef) options.errorRef.message = err instanceof Error ? err.message : String(err);
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
//#endregion
|
|
634
1465
|
//#region connect-utils/helpers.ts
|
|
635
1466
|
const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
|
|
636
1467
|
function resolveViteConfigPath(options) {
|
|
@@ -655,7 +1486,8 @@ function resolveConnectPackageJson(root = process.cwd()) {
|
|
|
655
1486
|
*/
|
|
656
1487
|
function resolveConnectBundle(root = process.cwd()) {
|
|
657
1488
|
const connectIndexPath = resolvePackage("@powerhousedao/connect", root);
|
|
658
|
-
|
|
1489
|
+
const connectRootPath = connectIndexPath.substring(0, connectIndexPath.indexOf("connect") + 7);
|
|
1490
|
+
return join(connectRootPath, "dist/");
|
|
659
1491
|
}
|
|
660
1492
|
function resolveConnectPublicDir(root = process.cwd()) {
|
|
661
1493
|
const connectIconPath = resolvePackage("@powerhousedao/connect/public/icon.ico", root);
|
|
@@ -939,7 +1771,7 @@ const VENDOR_MIME = {
|
|
|
939
1771
|
const VENDOR_MODE = process.env.PH_CONNECT_EXTERNALIZE_VENDOR === "1";
|
|
940
1772
|
const VENDOR_EXTRA = (process.env.PH_CONNECT_VENDOR_EXTRA ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
941
1773
|
const HEAVY_LIBS = [...DEFAULT_VENDOR_INCLUDE, ...VENDOR_EXTRA];
|
|
942
|
-
function withBase(base, p) {
|
|
1774
|
+
function withBase$1(base, p) {
|
|
943
1775
|
return `${base}${p}`.replace(/\/{2,}/g, "/");
|
|
944
1776
|
}
|
|
945
1777
|
function externalizePlugin(externals) {
|
|
@@ -1002,7 +1834,7 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
|
1002
1834
|
REACT_DEPS.forEach((d) => include.add(d));
|
|
1003
1835
|
if (vendorActive) {
|
|
1004
1836
|
HEAVY_LIBS.forEach((d) => include.delete(d));
|
|
1005
|
-
cfg.optimizeDeps.exclude = [
|
|
1837
|
+
cfg.optimizeDeps.exclude = [.../* @__PURE__ */ new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
|
|
1006
1838
|
}
|
|
1007
1839
|
cfg.optimizeDeps.include = [...include];
|
|
1008
1840
|
},
|
|
@@ -1021,7 +1853,7 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
|
1021
1853
|
}));
|
|
1022
1854
|
if (vendorActive) {
|
|
1023
1855
|
const vendorDir = vendor.vendorDir;
|
|
1024
|
-
const vendorPrefixes = [withBase(base, VENDOR_URL_PREFIX), VENDOR_URL_PREFIX];
|
|
1856
|
+
const vendorPrefixes = [withBase$1(base, VENDOR_URL_PREFIX), VENDOR_URL_PREFIX];
|
|
1025
1857
|
server.middlewares.use((req, res, next) => {
|
|
1026
1858
|
const url = (req.url ?? "").split("?")[0];
|
|
1027
1859
|
const prefix = vendorPrefixes.find((p) => url.startsWith(p));
|
|
@@ -1043,7 +1875,7 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
|
1043
1875
|
});
|
|
1044
1876
|
});
|
|
1045
1877
|
}
|
|
1046
|
-
const shimPrefixes = [withBase(base, SHIM_PATH), `/${SHIM_PATH}`];
|
|
1878
|
+
const shimPrefixes = [withBase$1(base, SHIM_PATH), `/${SHIM_PATH}`];
|
|
1047
1879
|
server.middlewares.use((req, res, next) => {
|
|
1048
1880
|
const prefix = shimPrefixes.find((p) => req.url?.startsWith(p));
|
|
1049
1881
|
if (!prefix) return next();
|
|
@@ -1057,7 +1889,7 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
|
1057
1889
|
return;
|
|
1058
1890
|
}
|
|
1059
1891
|
const browserHash = info.browserHash ?? optimizer.metadata.browserHash;
|
|
1060
|
-
const depUrl = `${withBase(base, VITE_DEPS_PATH)}/${path.basename(info.file)}?v=${browserHash}`;
|
|
1892
|
+
const depUrl = `${withBase$1(base, VITE_DEPS_PATH)}/${path.basename(info.file)}?v=${browserHash}`;
|
|
1061
1893
|
const names = namedExports.get(id) ?? [];
|
|
1062
1894
|
res.setHeader("Content-Type", "application/javascript");
|
|
1063
1895
|
res.end(`import * as M from ${JSON.stringify(depUrl)};\nconst ns = M.default ?? M;\nexport default ns;\n` + (names.length ? `export const { ${names.join(", ")} } = ns;\n` : ""));
|
|
@@ -1068,12 +1900,12 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
|
|
|
1068
1900
|
handler(html, ctx) {
|
|
1069
1901
|
const browserHash = ctx.server?.environments.client.depsOptimizer?.metadata.browserHash;
|
|
1070
1902
|
if (!browserHash) return;
|
|
1071
|
-
const shimPrefix = withBase(base, SHIM_PATH);
|
|
1903
|
+
const shimPrefix = withBase$1(base, SHIM_PATH);
|
|
1072
1904
|
const imports = Object.fromEntries(REACT_DEPS.map((id) => [id, `${shimPrefix}${id}.js?v=${browserHash}`]));
|
|
1073
1905
|
let dynamicBaseScript = "";
|
|
1074
1906
|
if (vendorActive) {
|
|
1075
|
-
for (const [spec, url] of Object.entries(vendor.imports)) imports[spec] = withBase(base, url);
|
|
1076
|
-
if (vendor.imports["@powerhousedao/connect"]) imports["ph-bundled-packages-virtual"] = withBase(base, BUNDLED_PACKAGES_DEV_URL);
|
|
1907
|
+
for (const [spec, url] of Object.entries(vendor.imports)) imports[spec] = withBase$1(base, url);
|
|
1908
|
+
if (vendor.imports["@powerhousedao/connect"]) imports["ph-bundled-packages-virtual"] = withBase$1(base, BUNDLED_PACKAGES_DEV_URL);
|
|
1077
1909
|
dynamicBaseScript = `<script>globalThis.__PH_DYNAMIC_BASE__=${JSON.stringify(base)};<\/script>\n`;
|
|
1078
1910
|
}
|
|
1079
1911
|
return html.replace(/<script type="importmap">[\s\S]*?<\/script>/, `${dynamicBaseScript}<script type="importmap">${JSON.stringify({ imports }, null, 2)}<\/script>`);
|
|
@@ -1118,7 +1950,7 @@ function connectFaviconPlugin(opts = {}) {
|
|
|
1118
1950
|
res.end(readFileSync(resolved.id));
|
|
1119
1951
|
}).catch(() => next());
|
|
1120
1952
|
};
|
|
1121
|
-
const paths = new Set([faviconRoute, "/icon.ico"]);
|
|
1953
|
+
const paths = /* @__PURE__ */ new Set([faviconRoute, "/icon.ico"]);
|
|
1122
1954
|
for (const path of paths) server.middlewares.use(path, handler);
|
|
1123
1955
|
},
|
|
1124
1956
|
async generateBundle(_options, bundle) {
|
|
@@ -1158,13 +1990,20 @@ function readProjectPackageInfo(projectRoot) {
|
|
|
1158
1990
|
}
|
|
1159
1991
|
function phConfigPlugin(options) {
|
|
1160
1992
|
const localPackage = readProjectPackageInfo(options.projectRoot ?? process.cwd());
|
|
1161
|
-
const
|
|
1993
|
+
const sourceSettings = {
|
|
1994
|
+
connect: options.connect,
|
|
1995
|
+
workflows: options.workflows
|
|
1996
|
+
};
|
|
1997
|
+
const conflict = workflowsSettingConflict(sourceSettings);
|
|
1998
|
+
if (conflict) console.warn(`[powerhouse] ${conflict}`);
|
|
1999
|
+
const withDefaults = resolveSourceConnect(sourceSettings);
|
|
1162
2000
|
const mergedConnect = options.cliConnectOverride ? deepMerge(withDefaults, options.cliConnectOverride) : withDefaults;
|
|
1163
|
-
const
|
|
2001
|
+
const source = {
|
|
1164
2002
|
packages: options.packages,
|
|
1165
2003
|
packageRegistryUrl: options.packageRegistryUrl,
|
|
1166
2004
|
connect: mergedConnect
|
|
1167
|
-
}
|
|
2005
|
+
};
|
|
2006
|
+
const runtimeConfig = buildRuntimeConfig(source, localPackage);
|
|
1168
2007
|
const content = JSON.stringify({
|
|
1169
2008
|
$schema: RUNTIME_CONFIG_SCHEMA_URL,
|
|
1170
2009
|
...runtimeConfig
|
|
@@ -1217,11 +2056,13 @@ function toPwaContribution(manifest, fallbackLabel, onWarn) {
|
|
|
1217
2056
|
if (!isPlainObject(manifest)) return null;
|
|
1218
2057
|
const label = typeof manifest.name === "string" && manifest.name ? manifest.name : fallbackLabel;
|
|
1219
2058
|
let config = {};
|
|
1220
|
-
if (manifest.pwa !== void 0)
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
2059
|
+
if (manifest.pwa !== void 0) {
|
|
2060
|
+
if (!isPlainObject(manifest.pwa)) onWarn(`PWA config: ${label} declares a non-object 'pwa' field in its manifest; ignored.`);
|
|
2061
|
+
else {
|
|
2062
|
+
const parsed = PwaConfigSchema.safeParse(manifest.pwa);
|
|
2063
|
+
if (!parsed.success) onWarn(`PWA config: ${label}'s pwa fragment is invalid; ignored. ${formatZodIssues(parsed.error)}`);
|
|
2064
|
+
else config = parsed.data;
|
|
2065
|
+
}
|
|
1225
2066
|
}
|
|
1226
2067
|
config = withInferredCategory(config, manifest.category);
|
|
1227
2068
|
if (Object.keys(config).length === 0) return null;
|
|
@@ -1330,7 +2171,8 @@ function collectProjectPwaContribution(options) {
|
|
|
1330
2171
|
}
|
|
1331
2172
|
if (!isPlainObject(manifest)) return null;
|
|
1332
2173
|
const label = typeof manifest.name === "string" && manifest.name ? manifest.name : "project manifest";
|
|
1333
|
-
const
|
|
2174
|
+
const config = manifest.pwa === void 0 ? {} : parsePwaConfigStrict(manifest.pwa, `pwa in ${manifestPath}`);
|
|
2175
|
+
const withCategory = withInferredCategory(config, manifest.category);
|
|
1334
2176
|
if (Object.keys(withCategory).length === 0) return null;
|
|
1335
2177
|
return {
|
|
1336
2178
|
source: label,
|
|
@@ -1471,8 +2313,8 @@ const BASE_MANIFEST = {
|
|
|
1471
2313
|
* declarative runtimeCaching option.
|
|
1472
2314
|
*/
|
|
1473
2315
|
const BASE_PRECACHE = {
|
|
1474
|
-
maximumFileSizeToCacheInBytes:
|
|
1475
|
-
globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}"],
|
|
2316
|
+
maximumFileSizeToCacheInBytes: 16777216,
|
|
2317
|
+
globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}", "__reactor_worker__/**/*.json"],
|
|
1476
2318
|
globIgnores: [
|
|
1477
2319
|
"**/powerhouse.config.json",
|
|
1478
2320
|
"**/*.map",
|
|
@@ -1738,6 +2580,91 @@ try {
|
|
|
1738
2580
|
}
|
|
1739
2581
|
`;
|
|
1740
2582
|
//#endregion
|
|
2583
|
+
//#region connect-utils/vite-plugins/reactor-worker-dev.ts
|
|
2584
|
+
const DEV_BUNDLE_DIR = "node_modules/.ph-reactor-worker";
|
|
2585
|
+
function withBase(base, p) {
|
|
2586
|
+
return `${base}${p}`.replace(/\/{2,}/g, "/");
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Dev-only: serve Connect's reactor SharedWorker bundle under
|
|
2590
|
+
* `<base>__reactor_worker__/`, building it lazily on first request.
|
|
2591
|
+
*
|
|
2592
|
+
* The dev server serves Connect from its prebuilt dist in node_modules, where
|
|
2593
|
+
* `dist/reactor.worker.js` is a library artifact no worker can resolve. The
|
|
2594
|
+
* tab GETs `worker-meta.json` here (JSON, no-cache, written by
|
|
2595
|
+
* `prebuildReactorWorker`) to find the bundle and fold its `sourceDigest`
|
|
2596
|
+
* into its version fingerprint; without this plugin that GET fails and the
|
|
2597
|
+
* worker feature reports itself unavailable instead of dying opaquely.
|
|
2598
|
+
*
|
|
2599
|
+
* Lazy because it is a full `vite build` of the reactor + PGlite graph. The
|
|
2600
|
+
* result is cached in node_modules/.ph-reactor-worker until the installed
|
|
2601
|
+
* Connect, its upstream dists, or builder-tools change (`computeSourceDigest`).
|
|
2602
|
+
*/
|
|
2603
|
+
function reactorWorkerDevPlugin(projectRoot) {
|
|
2604
|
+
let base = "/";
|
|
2605
|
+
let building = null;
|
|
2606
|
+
const ensureBuilt = () => {
|
|
2607
|
+
building ??= (async () => {
|
|
2608
|
+
const errorRef = {};
|
|
2609
|
+
const built = await prebuildReactorWorker({
|
|
2610
|
+
dirname: projectRoot,
|
|
2611
|
+
outDir: path.join(projectRoot, DEV_BUNDLE_DIR),
|
|
2612
|
+
nodeEnv: "development",
|
|
2613
|
+
errorRef
|
|
2614
|
+
});
|
|
2615
|
+
if (!built) {
|
|
2616
|
+
console.warn(`[connect] reactor worker bundle build failed${errorRef.message ? `: ${errorRef.message}` : ""}`);
|
|
2617
|
+
building = null;
|
|
2618
|
+
}
|
|
2619
|
+
return built;
|
|
2620
|
+
})();
|
|
2621
|
+
return building;
|
|
2622
|
+
};
|
|
2623
|
+
return {
|
|
2624
|
+
name: "ph-reactor-worker-dev",
|
|
2625
|
+
apply: "serve",
|
|
2626
|
+
configResolved(config) {
|
|
2627
|
+
base = config.base;
|
|
2628
|
+
},
|
|
2629
|
+
configureServer(server) {
|
|
2630
|
+
const prefixes = [withBase(base, REACTOR_WORKER_URL_PREFIX), REACTOR_WORKER_URL_PREFIX];
|
|
2631
|
+
server.middlewares.use((req, res, next) => {
|
|
2632
|
+
const url = (req.url ?? "").split("?")[0];
|
|
2633
|
+
const prefix = prefixes.find((p) => url.startsWith(p));
|
|
2634
|
+
if (!prefix) return next();
|
|
2635
|
+
const name = url.slice(prefix.length).replace(/^\/+/, "");
|
|
2636
|
+
if (!name) return next();
|
|
2637
|
+
(async () => {
|
|
2638
|
+
const built = await ensureBuilt();
|
|
2639
|
+
if (!built) {
|
|
2640
|
+
res.statusCode = 503;
|
|
2641
|
+
res.setHeader("Content-Type", "text/plain");
|
|
2642
|
+
res.end("reactor worker bundle unavailable (build failed; see server log)");
|
|
2643
|
+
return;
|
|
2644
|
+
}
|
|
2645
|
+
const file = path.join(built.outDir, name);
|
|
2646
|
+
const rel = path.relative(built.outDir, file);
|
|
2647
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
2648
|
+
next();
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
const stream = createReadStream(file);
|
|
2652
|
+
stream.on("error", () => {
|
|
2653
|
+
if (!res.headersSent) next();
|
|
2654
|
+
});
|
|
2655
|
+
stream.once("open", () => {
|
|
2656
|
+
const ext = file.slice(file.lastIndexOf("."));
|
|
2657
|
+
res.setHeader("Content-Type", REACTOR_WORKER_MIME[ext] ?? "text/javascript");
|
|
2658
|
+
const hashed = !name.startsWith("reactor.worker.js") && !name.endsWith(".json");
|
|
2659
|
+
res.setHeader("Cache-Control", hashed ? "public, max-age=31536000, immutable" : "no-cache");
|
|
2660
|
+
stream.pipe(res);
|
|
2661
|
+
});
|
|
2662
|
+
})();
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
//#endregion
|
|
1741
2668
|
//#region connect-utils/vite-plugins/theme-boot.ts
|
|
1742
2669
|
/**
|
|
1743
2670
|
* Marker attribute on the injected script. Serve-time injectors (e.g. the
|
|
@@ -1776,6 +2703,48 @@ function connectThemeBootPlugin() {
|
|
|
1776
2703
|
};
|
|
1777
2704
|
}
|
|
1778
2705
|
//#endregion
|
|
2706
|
+
//#region connect-utils/vite-plugins/vendor-import-map.ts
|
|
2707
|
+
function extractImports(raw) {
|
|
2708
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
2709
|
+
const imports = "imports" in raw ? raw.imports : void 0;
|
|
2710
|
+
if (typeof imports !== "object" || imports === null) return {};
|
|
2711
|
+
const out = {};
|
|
2712
|
+
for (const [key, value] of Object.entries(imports)) if (typeof value === "string") out[key] = value;
|
|
2713
|
+
return out;
|
|
2714
|
+
}
|
|
2715
|
+
function parseImportMap(raw) {
|
|
2716
|
+
try {
|
|
2717
|
+
return extractImports(JSON.parse(raw));
|
|
2718
|
+
} catch {
|
|
2719
|
+
return {};
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
const INLINE_IMPORTMAP = /<script type="importmap">([\s\S]*?)<\/script>/;
|
|
2723
|
+
function vendorImportMapPlugin(options) {
|
|
2724
|
+
const vendorEntries = Object.keys(options.imports).length;
|
|
2725
|
+
return {
|
|
2726
|
+
name: "ph-vendor-import-map",
|
|
2727
|
+
apply: "build",
|
|
2728
|
+
transformIndexHtml(html) {
|
|
2729
|
+
if (!vendorEntries) return;
|
|
2730
|
+
const match = INLINE_IMPORTMAP.exec(html);
|
|
2731
|
+
if (match) {
|
|
2732
|
+
const merged = { imports: {
|
|
2733
|
+
...parseImportMap(match[1]),
|
|
2734
|
+
...options.imports
|
|
2735
|
+
} };
|
|
2736
|
+
return html.replace(INLINE_IMPORTMAP, `<script type="importmap">${JSON.stringify(merged)}<\/script>`);
|
|
2737
|
+
}
|
|
2738
|
+
return [{
|
|
2739
|
+
tag: "script",
|
|
2740
|
+
attrs: { type: "importmap" },
|
|
2741
|
+
children: JSON.stringify({ imports: options.imports }),
|
|
2742
|
+
injectTo: "head-prepend"
|
|
2743
|
+
}];
|
|
2744
|
+
}
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
//#endregion
|
|
1779
2748
|
//#region connect-utils/vite-config.ts
|
|
1780
2749
|
function getConnectHtmlTags(options = {}) {
|
|
1781
2750
|
const { registryUrl, injectTo = "head" } = options;
|
|
@@ -1896,7 +2865,8 @@ function getLocalPackageNamesFromPowerhouseConfig({ packages }) {
|
|
|
1896
2865
|
}
|
|
1897
2866
|
function getConnectBaseViteConfig(options) {
|
|
1898
2867
|
const mode = options.mode;
|
|
1899
|
-
const
|
|
2868
|
+
const envDir = options.envDir ?? options.dirname;
|
|
2869
|
+
const fileEnv = loadEnv(mode, envDir, "PH_");
|
|
1900
2870
|
const env = loadConnectEnv({
|
|
1901
2871
|
processEnv: process.env,
|
|
1902
2872
|
fileEnv
|
|
@@ -1968,9 +2938,10 @@ function getConnectBaseViteConfig(options) {
|
|
|
1968
2938
|
});
|
|
1969
2939
|
const projectContribution = collectProjectPwaContribution({ projectRoot: options.dirname });
|
|
1970
2940
|
if (projectContribution) contributions.push(projectContribution);
|
|
2941
|
+
const mergedPwa = mergePwaConfig(contributions, projectPwa, pwaWarn);
|
|
1971
2942
|
return connectPwaPlugins({
|
|
1972
2943
|
offlineEnabled,
|
|
1973
|
-
pwa:
|
|
2944
|
+
pwa: mergedPwa
|
|
1974
2945
|
});
|
|
1975
2946
|
})();
|
|
1976
2947
|
const reactExternal = [
|
|
@@ -2023,6 +2994,7 @@ function getConnectBaseViteConfig(options) {
|
|
|
2023
2994
|
packages: phPackages,
|
|
2024
2995
|
projectRoot: options.dirname,
|
|
2025
2996
|
connect: phConfig.connect,
|
|
2997
|
+
workflows: phConfig.workflows,
|
|
2026
2998
|
packageRegistryUrl: phPackageRegistryUrl ?? void 0,
|
|
2027
2999
|
cliConnectOverride: options.cliConnectOverride
|
|
2028
3000
|
}),
|
|
@@ -2031,12 +3003,14 @@ function getConnectBaseViteConfig(options) {
|
|
|
2031
3003
|
projectRoot: options.dirname
|
|
2032
3004
|
}),
|
|
2033
3005
|
devReactImportmapPlugin(options.dirname),
|
|
3006
|
+
reactorWorkerDevPlugin(options.dirname),
|
|
2034
3007
|
...plugins,
|
|
2035
|
-
esmExternalRequirePlugin({ external: reactExternal }),
|
|
3008
|
+
esmExternalRequirePlugin({ external: [...reactExternal, ...options.vendor && mode === "production" ? Object.keys(options.vendor.imports) : []] }),
|
|
2036
3009
|
reactSelfHostPlugin({
|
|
2037
3010
|
dirname: options.dirname,
|
|
2038
3011
|
dev: mode !== "production" || isDebug
|
|
2039
3012
|
}),
|
|
3013
|
+
...options.vendor && mode === "production" ? [vendorImportMapPlugin({ imports: options.vendor.imports })] : [],
|
|
2040
3014
|
connectFaviconPlugin({ faviconPath: options.favicon }),
|
|
2041
3015
|
connectThemeBootPlugin(),
|
|
2042
3016
|
connectBuildHashPlugin(buildHash),
|
|
@@ -2047,10 +3021,13 @@ function getConnectBaseViteConfig(options) {
|
|
|
2047
3021
|
format: "es",
|
|
2048
3022
|
...options.dynamicBase ? { plugins: () => [connectDynamicBasePlugin({ forWorker: true })] } : {}
|
|
2049
3023
|
},
|
|
2050
|
-
build: {
|
|
3024
|
+
build: {
|
|
3025
|
+
sourcemap: true,
|
|
3026
|
+
rolldownOptions: { shimMissingExports: true }
|
|
3027
|
+
}
|
|
2051
3028
|
};
|
|
2052
3029
|
}
|
|
2053
3030
|
//#endregion
|
|
2054
|
-
export { BUILD_HASH_FILE, DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, THEME_BOOT_MARKER, VENDOR_EXTERNAL, VENDOR_URL_PREFIX, appendToHtmlHead, backupIndexHtml, buildHashFromBuildOptions, computeBuildHash, connectBuildHashPlugin, connectDynamicBasePlugin, connectThemeBootPlugin, copyConnect, ensureNodeVersion, escapeForRegExp, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
|
|
3031
|
+
export { BUILDER_TOOLS_VERSION, BUILD_HASH_FILE, DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, LOCK_STALE_MS, PH_DIR_NAME, REACTOR_WORKER_ENTRY, REACTOR_WORKER_MIME, REACTOR_WORKER_URL_PREFIX, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, THEME_BOOT_MARKER, VENDOR_EXTERNAL, VENDOR_URL_PREFIX, WORKER_PACKAGES_MANIFEST, acquireLock, appendToHtmlHead, backupIndexHtml, buildHashFromBuildOptions, computeBuildHash, computeSourceDigest, connectBuildHashPlugin, connectDynamicBasePlugin, connectThemeBootPlugin, copyConnect, distDirFingerprint, ensureNodeVersion, escapeForRegExp, findBundleSpecifierOffenders, findDisallowedSpecifiers, findWorkerUnsafeMarkers, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, missingVendorEntries, ownProjectPackage, phConfigPlugin, prebuildConnectVendor, prebuildReactorWorker, prebuildWorkerPackages, prependToHtmlHead, publishDirAtomic, readJsonFile, releaseLock, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolveOwnPackageVersion, resolvePackage, resolveReactorWorkerSource, resolveViteConfigPath, resolveWorkspacePackageDir, runShellScriptPlugin, runTsc, runWorkerBuildEntries, runtimeConfigSchema, stripVersionFromPackage, upstreamWorkerPackages, vendorImportMapPlugin, vendorRelativePath, withVendorBase, workerSafeVendorImports };
|
|
2055
3032
|
|
|
2056
3033
|
//# sourceMappingURL=index.mjs.map
|