@powerhousedao/builder-tools 6.2.3-dev.8 → 6.2.3-staging.0

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.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, 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 } 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
- let dir = dirname(createRequire(join(projectRoot, "package.json")).resolve("@powerhousedao/connect", { paths: [projectRoot] }));
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
- return `${RUNTIME_GLOBAL}=self.location.pathname.replace(/${escapeForRegExp(stripPrefix).replace(/\//g, "\\/")}assets\\/[^/]*$/,"");\n`;
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("/__PH_DYNAMIC_BASE__/")) return null;
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("/__PH_DYNAMIC_BASE__/")) this.error(`dynamic-base: unrewritten placeholder ${DYNAMIC_BASE_PLACEHOLDER} remains in ${file.fileName}`);
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
- imports: hit
243
+ ...hit
249
244
  };
250
245
  const lockDir = `${vendorDir}.lock`;
251
246
  const lock = acquireLock(lockDir);
252
247
  if (!lock) {
253
- const imports = await waitForCacheHit(importMapPath, include, external, versionDigest, lockDir);
254
- return imports ? {
248
+ const hit = await waitForCacheHit(importMapPath, include, external, versionDigest, lockDir);
249
+ return hit ? {
255
250
  vendorDir,
256
- imports
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
- imports: raced
258
+ ...raced
264
259
  };
265
- const imports = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest);
266
- return imports ? {
260
+ const result = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest, versions, base, nodeEnv);
261
+ return result ? {
267
262
  vendorDir,
268
- imports
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 cached.imports;
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
- function resolveVersionDigest(dirname, specs) {
287
- const seen = /* @__PURE__ */ new Map();
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$2, specs) {
298
+ const req = createRequire(join(dirname$2, "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.has(pkg)) continue;
291
- let version = "missing";
292
- try {
293
- const pkgRoot = realpathSync(join(dirname, "node_modules", pkg));
294
- const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
295
- version = String(meta.version ?? "unknown");
296
- } catch {}
297
- seen.set(pkg, version);
303
+ if (pkg in seen) continue;
304
+ if (selfMeta === null) try {
305
+ selfMeta = JSON.parse(readFileSync(join(dirname$2, "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$2, "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 [...seen.keys()].sort()) h.update(`${pkg}@${seen.get(pkg)}\n`);
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 = 5 * 6e4;
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. Failures leave the
410
- * original spec untouched.
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(include);
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 and does the swap.
444
- * Returns the published import map, or null on failure.
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$2, vendorDir, include, external, versionDigest) {
523
+ async function buildVendorAtomic(dirname$3, 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$2, tmpDir, include, external);
528
+ const { ok, stderr } = await runBuildWorker(dirname$3, 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 meta.imports;
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 = fileURLToPath(import.meta.url);
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
- // Dynamic-base placeholder + vendor segment: connectDynamicBasePlugin rewrites
606
- // emitted chunk/asset URLs to resolve against the deploy base at serve time.
607
- base: dynamicBase,
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': '"development"',
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
- 'import.meta.env.BASE_URL': JSON.stringify(DYNAMIC_BASE_PLACEHOLDER),
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
- outDir: vendorDir, emptyOutDir: false, minify: false, target: 'esnext', cssCodeSplit: true,
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]' },
@@ -655,7 +768,8 @@ function resolveConnectPackageJson(root = process.cwd()) {
655
768
  */
656
769
  function resolveConnectBundle(root = process.cwd()) {
657
770
  const connectIndexPath = resolvePackage("@powerhousedao/connect", root);
658
- return join(connectIndexPath.substring(0, connectIndexPath.indexOf("connect") + 7), "dist/");
771
+ const connectRootPath = connectIndexPath.substring(0, connectIndexPath.indexOf("connect") + 7);
772
+ return join(connectRootPath, "dist/");
659
773
  }
660
774
  function resolveConnectPublicDir(root = process.cwd()) {
661
775
  const connectIconPath = resolvePackage("@powerhousedao/connect/public/icon.ico", root);
@@ -1002,7 +1116,7 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
1002
1116
  REACT_DEPS.forEach((d) => include.add(d));
1003
1117
  if (vendorActive) {
1004
1118
  HEAVY_LIBS.forEach((d) => include.delete(d));
1005
- cfg.optimizeDeps.exclude = [...new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
1119
+ cfg.optimizeDeps.exclude = [.../* @__PURE__ */ new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
1006
1120
  }
1007
1121
  cfg.optimizeDeps.include = [...include];
1008
1122
  },
@@ -1118,7 +1232,7 @@ function connectFaviconPlugin(opts = {}) {
1118
1232
  res.end(readFileSync(resolved.id));
1119
1233
  }).catch(() => next());
1120
1234
  };
1121
- const paths = new Set([faviconRoute, "/icon.ico"]);
1235
+ const paths = /* @__PURE__ */ new Set([faviconRoute, "/icon.ico"]);
1122
1236
  for (const path of paths) server.middlewares.use(path, handler);
1123
1237
  },
1124
1238
  async generateBundle(_options, bundle) {
@@ -1158,13 +1272,17 @@ function readProjectPackageInfo(projectRoot) {
1158
1272
  }
1159
1273
  function phConfigPlugin(options) {
1160
1274
  const localPackage = readProjectPackageInfo(options.projectRoot ?? process.cwd());
1161
- const withDefaults = deepMerge(DEFAULT_CONNECT_CONFIG, options.connect ?? {});
1275
+ const withDefaults = resolveSourceConnect({
1276
+ connect: options.connect,
1277
+ workflows: options.workflows
1278
+ });
1162
1279
  const mergedConnect = options.cliConnectOverride ? deepMerge(withDefaults, options.cliConnectOverride) : withDefaults;
1163
- const runtimeConfig = buildRuntimeConfig({
1280
+ const source = {
1164
1281
  packages: options.packages,
1165
1282
  packageRegistryUrl: options.packageRegistryUrl,
1166
1283
  connect: mergedConnect
1167
- }, localPackage);
1284
+ };
1285
+ const runtimeConfig = buildRuntimeConfig(source, localPackage);
1168
1286
  const content = JSON.stringify({
1169
1287
  $schema: RUNTIME_CONFIG_SCHEMA_URL,
1170
1288
  ...runtimeConfig
@@ -1217,11 +1335,13 @@ function toPwaContribution(manifest, fallbackLabel, onWarn) {
1217
1335
  if (!isPlainObject(manifest)) return null;
1218
1336
  const label = typeof manifest.name === "string" && manifest.name ? manifest.name : fallbackLabel;
1219
1337
  let config = {};
1220
- if (manifest.pwa !== void 0) if (!isPlainObject(manifest.pwa)) onWarn(`PWA config: ${label} declares a non-object 'pwa' field in its manifest; ignored.`);
1221
- else {
1222
- const parsed = PwaConfigSchema.safeParse(manifest.pwa);
1223
- if (!parsed.success) onWarn(`PWA config: ${label}'s pwa fragment is invalid; ignored. ${formatZodIssues(parsed.error)}`);
1224
- else config = parsed.data;
1338
+ if (manifest.pwa !== void 0) {
1339
+ if (!isPlainObject(manifest.pwa)) onWarn(`PWA config: ${label} declares a non-object 'pwa' field in its manifest; ignored.`);
1340
+ else {
1341
+ const parsed = PwaConfigSchema.safeParse(manifest.pwa);
1342
+ if (!parsed.success) onWarn(`PWA config: ${label}'s pwa fragment is invalid; ignored. ${formatZodIssues(parsed.error)}`);
1343
+ else config = parsed.data;
1344
+ }
1225
1345
  }
1226
1346
  config = withInferredCategory(config, manifest.category);
1227
1347
  if (Object.keys(config).length === 0) return null;
@@ -1330,7 +1450,8 @@ function collectProjectPwaContribution(options) {
1330
1450
  }
1331
1451
  if (!isPlainObject(manifest)) return null;
1332
1452
  const label = typeof manifest.name === "string" && manifest.name ? manifest.name : "project manifest";
1333
- const withCategory = withInferredCategory(manifest.pwa === void 0 ? {} : parsePwaConfigStrict(manifest.pwa, `pwa in ${manifestPath}`), manifest.category);
1453
+ const config = manifest.pwa === void 0 ? {} : parsePwaConfigStrict(manifest.pwa, `pwa in ${manifestPath}`);
1454
+ const withCategory = withInferredCategory(config, manifest.category);
1334
1455
  if (Object.keys(withCategory).length === 0) return null;
1335
1456
  return {
1336
1457
  source: label,
@@ -1471,7 +1592,7 @@ const BASE_MANIFEST = {
1471
1592
  * declarative runtimeCaching option.
1472
1593
  */
1473
1594
  const BASE_PRECACHE = {
1474
- maximumFileSizeToCacheInBytes: 16 * 1024 * 1024,
1595
+ maximumFileSizeToCacheInBytes: 16777216,
1475
1596
  globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}"],
1476
1597
  globIgnores: [
1477
1598
  "**/powerhouse.config.json",
@@ -1776,6 +1897,48 @@ function connectThemeBootPlugin() {
1776
1897
  };
1777
1898
  }
1778
1899
  //#endregion
1900
+ //#region connect-utils/vite-plugins/vendor-import-map.ts
1901
+ function extractImports(raw) {
1902
+ if (typeof raw !== "object" || raw === null) return {};
1903
+ const imports = "imports" in raw ? raw.imports : void 0;
1904
+ if (typeof imports !== "object" || imports === null) return {};
1905
+ const out = {};
1906
+ for (const [key, value] of Object.entries(imports)) if (typeof value === "string") out[key] = value;
1907
+ return out;
1908
+ }
1909
+ function parseImportMap(raw) {
1910
+ try {
1911
+ return extractImports(JSON.parse(raw));
1912
+ } catch {
1913
+ return {};
1914
+ }
1915
+ }
1916
+ const INLINE_IMPORTMAP = /<script type="importmap">([\s\S]*?)<\/script>/;
1917
+ function vendorImportMapPlugin(options) {
1918
+ const vendorEntries = Object.keys(options.imports).length;
1919
+ return {
1920
+ name: "ph-vendor-import-map",
1921
+ apply: "build",
1922
+ transformIndexHtml(html) {
1923
+ if (!vendorEntries) return;
1924
+ const match = INLINE_IMPORTMAP.exec(html);
1925
+ if (match) {
1926
+ const merged = { imports: {
1927
+ ...parseImportMap(match[1]),
1928
+ ...options.imports
1929
+ } };
1930
+ return html.replace(INLINE_IMPORTMAP, `<script type="importmap">${JSON.stringify(merged)}<\/script>`);
1931
+ }
1932
+ return [{
1933
+ tag: "script",
1934
+ attrs: { type: "importmap" },
1935
+ children: JSON.stringify({ imports: options.imports }),
1936
+ injectTo: "head-prepend"
1937
+ }];
1938
+ }
1939
+ };
1940
+ }
1941
+ //#endregion
1779
1942
  //#region connect-utils/vite-config.ts
1780
1943
  function getConnectHtmlTags(options = {}) {
1781
1944
  const { registryUrl, injectTo = "head" } = options;
@@ -1896,7 +2059,8 @@ function getLocalPackageNamesFromPowerhouseConfig({ packages }) {
1896
2059
  }
1897
2060
  function getConnectBaseViteConfig(options) {
1898
2061
  const mode = options.mode;
1899
- const fileEnv = loadEnv(mode, options.envDir ?? options.dirname, "PH_");
2062
+ const envDir = options.envDir ?? options.dirname;
2063
+ const fileEnv = loadEnv(mode, envDir, "PH_");
1900
2064
  const env = loadConnectEnv({
1901
2065
  processEnv: process.env,
1902
2066
  fileEnv
@@ -1968,9 +2132,10 @@ function getConnectBaseViteConfig(options) {
1968
2132
  });
1969
2133
  const projectContribution = collectProjectPwaContribution({ projectRoot: options.dirname });
1970
2134
  if (projectContribution) contributions.push(projectContribution);
2135
+ const mergedPwa = mergePwaConfig(contributions, projectPwa, pwaWarn);
1971
2136
  return connectPwaPlugins({
1972
2137
  offlineEnabled,
1973
- pwa: mergePwaConfig(contributions, projectPwa, pwaWarn)
2138
+ pwa: mergedPwa
1974
2139
  });
1975
2140
  })();
1976
2141
  const reactExternal = [
@@ -2023,6 +2188,7 @@ function getConnectBaseViteConfig(options) {
2023
2188
  packages: phPackages,
2024
2189
  projectRoot: options.dirname,
2025
2190
  connect: phConfig.connect,
2191
+ workflows: phConfig.workflows,
2026
2192
  packageRegistryUrl: phPackageRegistryUrl ?? void 0,
2027
2193
  cliConnectOverride: options.cliConnectOverride
2028
2194
  }),
@@ -2032,11 +2198,12 @@ function getConnectBaseViteConfig(options) {
2032
2198
  }),
2033
2199
  devReactImportmapPlugin(options.dirname),
2034
2200
  ...plugins,
2035
- esmExternalRequirePlugin({ external: reactExternal }),
2201
+ esmExternalRequirePlugin({ external: [...reactExternal, ...options.vendor && mode === "production" ? Object.keys(options.vendor.imports) : []] }),
2036
2202
  reactSelfHostPlugin({
2037
2203
  dirname: options.dirname,
2038
2204
  dev: mode !== "production" || isDebug
2039
2205
  }),
2206
+ ...options.vendor && mode === "production" ? [vendorImportMapPlugin({ imports: options.vendor.imports })] : [],
2040
2207
  connectFaviconPlugin({ faviconPath: options.favicon }),
2041
2208
  connectThemeBootPlugin(),
2042
2209
  connectBuildHashPlugin(buildHash),
@@ -2047,10 +2214,13 @@ function getConnectBaseViteConfig(options) {
2047
2214
  format: "es",
2048
2215
  ...options.dynamicBase ? { plugins: () => [connectDynamicBasePlugin({ forWorker: true })] } : {}
2049
2216
  },
2050
- build: { sourcemap: true }
2217
+ build: {
2218
+ sourcemap: true,
2219
+ rolldownOptions: { shimMissingExports: true }
2220
+ }
2051
2221
  };
2052
2222
  }
2053
2223
  //#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 };
2224
+ 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, missingVendorEntries, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage, vendorImportMapPlugin, withVendorBase };
2055
2225
 
2056
2226
  //# sourceMappingURL=index.mjs.map