@powerhousedao/builder-tools 6.2.3-dev.60 → 6.2.3-dev.62

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,14 +1,17 @@
1
1
  import { createRequire } from "node:module";
2
- import { createHash, randomUUID } from "node:crypto";
3
- import fs, { chmodSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import path, { dirname, extname, isAbsolute, join, resolve } from "node:path";
2
+ import fs, { chmodSync, cpSync, createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import path, { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
4
  import { exec, execSync, spawn } from "node:child_process";
5
+ import { readFile, writeFile } from "node:fs/promises";
6
+ import { cwd } from "node:process";
7
+ import { createHash, randomUUID } from "node:crypto";
8
+ import { Generator, getConfig } from "@tanstack/router-generator";
9
+ import { tanstackRouter } from "@tanstack/router-plugin/vite";
10
+ import { layout, physical, rootRoute } from "@tanstack/virtual-file-routes";
6
11
  import { fileURLToPath } from "node:url";
7
12
  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";
8
13
  import MagicString from "magic-string";
9
- import { readFile, writeFile } from "node:fs/promises";
10
- import { cwd } from "node:process";
11
- import { getConfig } from "@powerhousedao/config/node";
14
+ import { getConfig as getConfig$1 } from "@powerhousedao/config/node";
12
15
  import tailwind from "@tailwindcss/vite";
13
16
  import react from "@vitejs/plugin-react";
14
17
  import { createLogger, esmExternalRequirePlugin, loadEnv, searchForWorkspaceRoot } from "vite";
@@ -22,6 +25,254 @@ const IMPORT_SCRIPT_FILE = "external-packages.js";
22
25
  const LOCAL_PACKAGE_ID = "ph:local-package";
23
26
  const PH_DIR_NAME = ".ph";
24
27
  //#endregion
28
+ //#region connect-utils/helpers.ts
29
+ const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
30
+ function resolveViteConfigPath(options) {
31
+ const { projectRoot = cwd(), viteConfigFile } = options;
32
+ return viteConfigFile || join(projectRoot, "vite.config.ts");
33
+ }
34
+ function resolvePackage(packageName, root = process.cwd()) {
35
+ return createRequire(root).resolve(packageName, { paths: [root] });
36
+ }
37
+ function resolveConnectPackageJson(root = process.cwd()) {
38
+ try {
39
+ const connectPackageJsonPath = resolvePackage("@powerhousedao/connect/package.json", root);
40
+ const fileContents = fs.readFileSync(connectPackageJsonPath, "utf-8");
41
+ return JSON.parse(fileContents);
42
+ } catch (error) {
43
+ console.error(`Error reading Connect package.json:`, error);
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Finds the dist dir of Connect on the local machine
49
+ */
50
+ function resolveConnectBundle(root = process.cwd()) {
51
+ const connectIndexPath = resolvePackage("@powerhousedao/connect", root);
52
+ const connectRootPath = connectIndexPath.substring(0, connectIndexPath.indexOf("connect") + 7);
53
+ return join(connectRootPath, "dist/");
54
+ }
55
+ function resolveConnectPublicDir(root = process.cwd()) {
56
+ const connectIconPath = resolvePackage("@powerhousedao/connect/public/icon.ico", root);
57
+ return path.join(connectIconPath, "../");
58
+ }
59
+ /**
60
+ * Copies the Connect dist dir to the target path
61
+ */
62
+ function copyConnect(sourcePath, targetPath) {
63
+ try {
64
+ fs.rmSync(targetPath, {
65
+ recursive: true,
66
+ force: true
67
+ });
68
+ fs.cpSync(sourcePath, targetPath, { recursive: true });
69
+ } catch (error) {
70
+ console.error(`❌ Error copying ${sourcePath} to ${targetPath}:`, error);
71
+ }
72
+ }
73
+ /**
74
+ * Backs up the index.html file
75
+ *
76
+ * Needed when running the Connect Studio dev server on Windows
77
+ */
78
+ function backupIndexHtml(appPath, restore = false) {
79
+ const filePath = join(appPath, "index.html");
80
+ const backupPath = join(appPath, "index.html.bak");
81
+ const paths = restore ? [backupPath, filePath] : [filePath, backupPath];
82
+ if (fs.existsSync(paths[0])) fs.copyFileSync(paths[0], paths[1]);
83
+ }
84
+ function removeBase64EnvValues(appPath) {
85
+ backupIndexHtml(appPath);
86
+ const filePath = join(appPath, "index.html");
87
+ fs.readFile(filePath, "utf-8", (err, data) => {
88
+ if (err) {
89
+ console.error("Error reading file:", err);
90
+ return;
91
+ }
92
+ const modifiedData = data.replace(/"LOCAL_DOCUMENT_MODELS":\s*".*?",/, `"LOCAL_DOCUMENT_MODELS": "",`).replace(/"LOCAL_DOCUMENT_EDITORS":\s*".*?"/, `"LOCAL_DOCUMENT_EDITORS": ""`);
93
+ console.log("Modified data:", modifiedData);
94
+ fs.writeFile(filePath, modifiedData, "utf-8", (err) => {
95
+ if (err) {
96
+ console.error("Error writing file:", err);
97
+ return;
98
+ }
99
+ });
100
+ });
101
+ }
102
+ function readJsonFile(filePath) {
103
+ try {
104
+ const absolutePath = resolve(filePath);
105
+ const fileContents = fs.readFileSync(absolutePath, "utf-8");
106
+ return JSON.parse(fileContents);
107
+ } catch (_error) {
108
+ console.error(`Error reading file: ${filePath}`);
109
+ return null;
110
+ }
111
+ }
112
+ /**
113
+ * Takes a list of Powerhouse project packages and optionally local Powerhouse packages and outputs a js file which exports those packages for use in Connect Studio.
114
+ */
115
+ function makeImportScriptFromPackages(args) {
116
+ const { packages, localJsPath, localCssPath, importStyles = true } = args;
117
+ const imports = [];
118
+ const moduleNames = [];
119
+ let counter = 0;
120
+ for (const packageName of packages) {
121
+ const moduleName = `module${counter}`;
122
+ moduleNames.push(moduleName);
123
+ imports.push(`import * as ${moduleName} from '${packageName}';`);
124
+ if (importStyles) imports.push(`import '${packageName}/style.css';`);
125
+ counter++;
126
+ }
127
+ const exports = moduleNames.map((name, index) => `{
128
+ id: "${packages[index]}",
129
+ ...${name},
130
+ }`);
131
+ const hasModule = localJsPath !== void 0;
132
+ const hasStyles = importStyles && localCssPath !== void 0;
133
+ if (hasModule || hasStyles) {
134
+ if (hasStyles) imports.push(`import '${localCssPath}';`);
135
+ if (hasModule) {
136
+ const moduleName = `module${counter}`;
137
+ imports.push(`import * as ${moduleName} from '${localJsPath}';`);
138
+ exports.push(`{
139
+ id: "${LOCAL_PACKAGE_ID}",
140
+ ...${moduleName},
141
+ }`);
142
+ }
143
+ }
144
+ const exportStatement = `export default [${exports.length ? `
145
+ ${exports.join(",\n")}
146
+ ` : ""}];`;
147
+ return `${imports.join("\n")}\n\n${exportStatement}`;
148
+ }
149
+ function ensureNodeVersion(minVersion = "24") {
150
+ const version = process.versions.node;
151
+ if (!version) return;
152
+ if (version < minVersion) {
153
+ console.error(`Node version ${minVersion} or higher is required. Current version: ${version}`);
154
+ process.exit(1);
155
+ }
156
+ }
157
+ function runShellScriptPlugin(scriptName, connectPath) {
158
+ return {
159
+ name: "vite-plugin-run-shell-script",
160
+ buildStart() {
161
+ const scriptPath = join(connectPath, scriptName);
162
+ if (fs.existsSync(scriptPath)) exec(`sh ${scriptPath}`, (error, stdout, stderr) => {
163
+ if (error) {
164
+ console.error(`Error executing the script: ${error.message}`);
165
+ removeBase64EnvValues(connectPath);
166
+ return;
167
+ }
168
+ if (stderr) console.error(stderr);
169
+ });
170
+ }
171
+ };
172
+ }
173
+ /**
174
+ * Shared helper to modify the <head> tag of an HTML file by transforming its contents.
175
+ */
176
+ async function modifyHtmlHead(pathToHtml, contents, transform) {
177
+ if (!existsSync(pathToHtml)) throw new Error(`File ${pathToHtml} does not exist.`);
178
+ let html = await readFile(pathToHtml, "utf8");
179
+ html = transform(html, contents);
180
+ await writeFile(pathToHtml, html, "utf8");
181
+ }
182
+ /**
183
+ * Appends the contents to the <head> tag of the index.html file
184
+ */
185
+ async function appendToHtmlHead(pathToHtml, contents) {
186
+ return modifyHtmlHead(pathToHtml, contents, (html, contents) => {
187
+ if (!html.includes("</head>")) throw new Error("No </head> tag found in the HTML file.");
188
+ return html.replace("</head>", `\n${contents}\n</head>`);
189
+ });
190
+ }
191
+ /**
192
+ * Prepends the contents to the <head> tag of the index.html file
193
+ */
194
+ async function prependToHtmlHead(pathToHtml, contents) {
195
+ return modifyHtmlHead(pathToHtml, contents, (html, contents) => {
196
+ if (!html.includes("</head>")) throw new Error("No </head> tag found in the HTML file.");
197
+ return html.replace("<head>", `<head>\n${contents}\n`);
198
+ });
199
+ }
200
+ function runTsc(outDir) {
201
+ execSync(`npx tsc --outDir ${outDir}`, { stdio: "inherit" });
202
+ }
203
+ function stripVersionFromPackage(packageName) {
204
+ const trimmed = packageName.trim();
205
+ if (!trimmed) return "";
206
+ const lastAtIndex = trimmed.lastIndexOf("@");
207
+ if (lastAtIndex > 0) return trimmed.substring(0, lastAtIndex);
208
+ return trimmed;
209
+ }
210
+ //#endregion
211
+ //#region connect-utils/vite-plugins/app-shell.ts
212
+ /** On the prerendered shell's elements; marks an index.html that has it. */
213
+ const APP_SHELL_MARKER = "data-ph-shell";
214
+ /** The shell Connect's build prerendered, if the installed Connect ships one. */
215
+ function readAppShell(projectRoot) {
216
+ let distDir;
217
+ try {
218
+ distDir = join(dirname(resolvePackage("@powerhousedao/connect/package.json", projectRoot)), "dist");
219
+ } catch {
220
+ return;
221
+ }
222
+ const body = join(distDir, "shell.html");
223
+ const head = join(distDir, "shell-head.html");
224
+ if (!existsSync(body) || !existsSync(head)) return void 0;
225
+ return {
226
+ body: readFileSync(body, "utf8"),
227
+ head: readFileSync(head, "utf8")
228
+ };
229
+ }
230
+ /** On the boot-recovery script; its sessionStorage key guards against reload loops. */
231
+ const BOOT_RECOVER_MARKER = "data-ph-boot-recover";
232
+ const BOOT_RECOVER_KEY = "ph:sw-recover";
233
+ const BOOT_RECOVER_SCRIPT = `(function(){try{var K='${BOOT_RECOVER_KEY}',n=navigator.serviceWorker,done=0;if(!n||sessionStorage.getItem(K))return;var up=function(){return !document.querySelector('style[${APP_SHELL_MARKER}]')};var reload=function(){location.reload()};var skip=function(w){try{sessionStorage.setItem(K,'1')}catch(e){}n.addEventListener('controllerchange',reload);w.postMessage({type:'SKIP_WAITING'});setTimeout(reload,3000)};var watch=function(w){w.addEventListener('statechange',function(){if(w.state==='installed')skip(w)})};var go=function(){if(done||up())return;done=1;n.getRegistration().then(function(r){if(!r)return;if(r.waiting)return skip(r.waiting);if(r.installing)return watch(r.installing);r.addEventListener('updatefound',function(){if(r.installing)watch(r.installing)});return r.update()}).catch(function(){})};addEventListener('error',function(e){var t=e.target;if(e.error instanceof SyntaxError||e.error instanceof TypeError||(t&&t.tagName==='SCRIPT'))go()},true);addEventListener('unhandledrejection',function(e){if(e.reason instanceof SyntaxError||e.reason instanceof TypeError)go()});setTimeout(go,8000)}catch(e){}})();`;
234
+ /** Prefixes the shell's root-relative links with the deploy base. */
235
+ function rebaseShellLinks(body, base) {
236
+ if (base === "/" || !base) return body;
237
+ const prefix = base.endsWith("/") ? base : `${base}/`;
238
+ return body.replace(/(\shref=")\/(?!\/)/g, `$1${prefix}`);
239
+ }
240
+ const STYLESHEET_LINK = /<link\b(?=[^>]*\brel=["']?stylesheet\b)[^>]*>/gi;
241
+ function deferStylesheet(link) {
242
+ return `${link.replace(/\brel=["']?stylesheet["']?/i, "rel=\"preload\" as=\"style\"").replace(/\/?>$/, ` onload="this.onload=null;this.rel='stylesheet'">`)}<noscript>${link}</noscript>`;
243
+ }
244
+ /** Inlines `shell` into an empty `#root`, adds its critical CSS and defers the
245
+ * stylesheets; React replaces the markup when the app renders. */
246
+ function injectAppShell(html, shell, base = "/") {
247
+ if (html.includes("data-ph-shell")) return html;
248
+ const root = /<div id="root"><\/div>/;
249
+ if (!root.test(html)) return html;
250
+ const firstSheet = html.search(STYLESHEET_LINK);
251
+ const at = firstSheet >= 0 ? firstSheet : html.indexOf("</head>");
252
+ const recover = `<script ${BOOT_RECOVER_MARKER}>${BOOT_RECOVER_SCRIPT}<\/script>`;
253
+ const body = rebaseShellLinks(shell.body, base);
254
+ return (html.slice(0, at) + shell.head + recover + html.slice(at)).replace(STYLESHEET_LINK, deferStylesheet).replace(root, () => `<div id="root">${body}</div>`);
255
+ }
256
+ /** Build only: dev keeps the empty root and blocking CSS for HMR. */
257
+ function connectAppShellPlugin(projectRoot) {
258
+ let shell;
259
+ let base = "/";
260
+ return {
261
+ name: "ph-connect-app-shell",
262
+ apply: "build",
263
+ configResolved(config) {
264
+ base = config.base;
265
+ },
266
+ buildStart() {
267
+ shell = readAppShell(projectRoot);
268
+ },
269
+ transformIndexHtml: {
270
+ order: "post",
271
+ handler: (html) => shell ? injectAppShell(html, shell, base) : html
272
+ }
273
+ };
274
+ }
275
+ //#endregion
25
276
  //#region connect-utils/vite-plugins/build-hash.ts
26
277
  /** Name of the build-identity file emitted into the build output. */
27
278
  const BUILD_HASH_FILE = "build-hash.json";
@@ -90,6 +341,152 @@ function connectBuildHashPlugin(buildHash) {
90
341
  };
91
342
  }
92
343
  //#endregion
344
+ //#region connect-utils/vite-plugins/connect-routes.ts
345
+ /** A project's routes for its Connect deployment, beside Connect's own. */
346
+ const HOST_ROUTES_DIR = "hosts/connect/routes";
347
+ /** The generated tree of Connect's and the project's routes. */
348
+ const CONNECT_ROUTES_GEN = ".ph/connect-routes.gen.ts";
349
+ /** Connect's dist imports its route tree by this specifier. */
350
+ const ROUTE_TREE_SPECIFIER = "@powerhousedao/connect/route-tree";
351
+ /** The project's routes folder, `hosts/connect/routes/`; undefined if none. */
352
+ function resolveConnectRoutesDir(projectRoot) {
353
+ const dir = join(projectRoot, HOST_ROUTES_DIR);
354
+ return existsSync(dir) ? dir : void 0;
355
+ }
356
+ /** Connect's pathless sidebar layout; a project's routes in `_app/` render in it. */
357
+ const APP_LAYOUT = "_app";
358
+ function findRouteFile(dir, name) {
359
+ return [
360
+ ".tsx",
361
+ ".ts",
362
+ ".jsx",
363
+ ".js"
364
+ ].map((ext) => join(dir, `${name}${ext}`)).find((file) => existsSync(file));
365
+ }
366
+ /** The router plugin's config: Connect's root, its core routes and the project's. */
367
+ function connectRoutesGeneratorConfig(connectDir, projectRoot, projectRoutes) {
368
+ const routesDirectory = join(connectDir, "src/routes");
369
+ const fromRoutes = (path) => relative(routesDirectory, path);
370
+ const appLayout = findRouteFile(projectRoutes, APP_LAYOUT);
371
+ const appRoutes = join(projectRoutes, APP_LAYOUT);
372
+ return getConfig({
373
+ target: "react",
374
+ routesDirectory,
375
+ generatedRouteTree: join(projectRoot, CONNECT_ROUTES_GEN),
376
+ virtualRouteConfig: rootRoute("__root.tsx", [layout(APP_LAYOUT, appLayout ? fromRoutes(appLayout) : `core/${APP_LAYOUT}.tsx`, [physical("", `core/${APP_LAYOUT}`), ...existsSync(appRoutes) ? [physical("", fromRoutes(appRoutes))] : []]), physical("", fromRoutes(projectRoutes))]),
377
+ routeFileIgnorePattern: `^${APP_LAYOUT}(\\.[cm]?[jt]sx?)?$`,
378
+ quoteStyle: "double",
379
+ semicolons: true,
380
+ addExtensions: "js",
381
+ autoCodeSplitting: true,
382
+ disableLogging: true,
383
+ routeTreeFileFooter: [
384
+ "import type { ConnectRouterFor } from \"@powerhousedao/connect/router\";",
385
+ "declare module \"@tanstack/react-router\" {",
386
+ " interface Register {",
387
+ " router: ConnectRouterFor<typeof routeTree>;",
388
+ " }",
389
+ "}"
390
+ ]
391
+ }, projectRoot);
392
+ }
393
+ /** Writes the route tree of Connect's and the project's routes; throws on a
394
+ * collision with a core route, which the router plugin would only log. */
395
+ async function generateConnectRoutes(projectRoot, routesDir, config = connectRoutesGeneratorConfig(connectDirOf(projectRoot), projectRoot, routesDir)) {
396
+ mkdirSync(dirname(config.generatedRouteTree), { recursive: true });
397
+ try {
398
+ await new Generator({
399
+ config,
400
+ root: projectRoot
401
+ }).run();
402
+ assertNoDuplicateRoutes(config.generatedRouteTree);
403
+ importCoreRoutesByPackage(config.generatedRouteTree, config.routesDirectory);
404
+ } catch (error) {
405
+ const message = (Array.isArray(error) ? error : [error]).map((e) => e instanceof Error ? e.message : String(e)).join("\n");
406
+ const shown = relative(projectRoot, routesDir).split(sep).join("/");
407
+ throw new Error(`Connect could not mount the routes in ${shown}/:\n${message}`, { cause: error });
408
+ }
409
+ }
410
+ /** Connect's route files, as the generated tree imports them. */
411
+ const CONNECT_ROUTES_SPECIFIER = "@powerhousedao/connect/routes";
412
+ function importCoreRoutesByPackage(treeFile, routesDirectory) {
413
+ const prefix = `"./${relative(dirname(treeFile), routesDirectory).split(sep).join("/")}/`;
414
+ const tree = readFileSync(treeFile, "utf8");
415
+ const rewritten = tree.replace(new RegExp(`${escapeRegExp(prefix)}([^"]+)\\.js"`, "g"), `"${CONNECT_ROUTES_SPECIFIER}/$1"`);
416
+ if (rewritten !== tree) writeFileSync(treeFile, rewritten);
417
+ }
418
+ function assertNoDuplicateRoutes(treeFile) {
419
+ const tree = readFileSync(treeFile, "utf8");
420
+ const files = /* @__PURE__ */ new Map();
421
+ for (const [, name, file] of tree.matchAll(/^import \{ Route as (\w+) \} from "([^"]+)";$/gm)) files.set(name, [...files.get(name) ?? [], file]);
422
+ const conflicts = [...files.values()].filter((list) => list.length > 1);
423
+ if (conflicts.length === 0) return;
424
+ throw new Error(`Conflicting configuration paths were found for a layout route.\nConflicting files: \n ${conflicts.flat().join("\n ")}\n`);
425
+ }
426
+ function escapeRegExp(text) {
427
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
428
+ }
429
+ /** Generates the tree when the project has routes, else drops a stale one. */
430
+ async function syncConnectRoutes(projectRoot, routesDir = resolveConnectRoutesDir(projectRoot)) {
431
+ if (routesDir) await generateConnectRoutes(projectRoot, routesDir);
432
+ else rmSync(join(projectRoot, CONNECT_ROUTES_GEN), { force: true });
433
+ }
434
+ function connectDirOf(projectRoot) {
435
+ return dirname(resolvePackage("@powerhousedao/connect/package.json", projectRoot));
436
+ }
437
+ /** Mounts a project's routes folder beside Connect's core routes; without one,
438
+ * Connect keeps its prebuilt core tree and no router plugin runs. */
439
+ function connectRoutes(projectRoot, routesDir = resolveConnectRoutesDir(projectRoot)) {
440
+ const connectDir = connectDirOf(projectRoot);
441
+ const find = new RegExp(`^${ROUTE_TREE_SPECIFIER}$`);
442
+ if (!routesDir) {
443
+ rmSync(join(projectRoot, CONNECT_ROUTES_GEN), { force: true });
444
+ return {
445
+ alias: {
446
+ find,
447
+ replacement: join(connectDir, "dist/route-tree.js")
448
+ },
449
+ plugins: [],
450
+ dedupe: []
451
+ };
452
+ }
453
+ const config = connectRoutesGeneratorConfig(connectDir, projectRoot, routesDir);
454
+ mkdirSync(dirname(config.generatedRouteTree), { recursive: true });
455
+ return {
456
+ alias: {
457
+ find,
458
+ replacement: config.generatedRouteTree
459
+ },
460
+ plugins: [{
461
+ name: "ph-connect-routes-check",
462
+ buildStart: () => generateConnectRoutes(projectRoot, routesDir, config)
463
+ }, tanstackRouter(config)],
464
+ dedupe: ["@tanstack/react-router"]
465
+ };
466
+ }
467
+ const ROUTER_PACKAGES = /[\\/]node_modules[\\/]@tanstack[\\/](react-router|router-core|history|react-store|store)[\\/]/;
468
+ /** One chunk for the router and the project's route configs, which the split
469
+ * route components would otherwise spread over several shared chunks. */
470
+ function connectRouterChunkGroups(routesDir) {
471
+ const routes = routesDir && join(routesDir, sep);
472
+ return [
473
+ {
474
+ name: "preload-helper",
475
+ test: /vite[\\/]preload-helper/,
476
+ priority: 1
477
+ },
478
+ {
479
+ name: "use-sync-external-store",
480
+ test: /[\\/]node_modules[\\/]use-sync-external-store[\\/]/,
481
+ priority: 1
482
+ },
483
+ {
484
+ name: "router",
485
+ test: (id) => ROUTER_PACKAGES.test(id) || !!routes && id.startsWith(routes) && !id.includes("tsr-split")
486
+ }
487
+ ];
488
+ }
489
+ //#endregion
93
490
  //#region connect-utils/vite-plugins/dynamic-base.ts
94
491
  /**
95
492
  * Global the runtime (ph-clint proxy) must set before the entry bundle loads.
@@ -103,6 +500,15 @@ function workerPrelude(stripPrefix) {
103
500
  return `${RUNTIME_GLOBAL}=self.location.pathname.replace(/${prefix}assets\\/[^/]*$/,"");\n`;
104
501
  }
105
502
  const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}((?:(?!\\1)[^$])*)\\1`, "g");
503
+ const PLACEHOLDER_CSS_URL = new RegExp(`url\\(\\s*(["']?)${escapeForRegExp(DYNAMIC_BASE_PLACEHOLDER)}([^"')]*)\\1\\s*\\)`, "g");
504
+ /** Rewrites `url(<placeholder>assets/x)` relative to the stylesheet at `fileName`. */
505
+ function relativeCssUrls(css, fileName) {
506
+ const dir = path.posix.dirname(fileName);
507
+ return css.replace(PLACEHOLDER_CSS_URL, (_match, quote, target) => {
508
+ const relative = path.posix.relative(dir, target);
509
+ return `url(${quote}${relative.startsWith(".") ? relative : `./${relative}`}${quote})`;
510
+ });
511
+ }
106
512
  /**
107
513
  * Rewrites the placeholder base in emitted JS chunks to a runtime expression so
108
514
  * one built `dist/connect` serves under any subpath. Rolldown-native: per-match
@@ -127,9 +533,9 @@ const PLACEHOLDER_LITERAL = new RegExp(`(["'\`])${escapeForRegExp(DYNAMIC_BASE_P
127
533
  * (the same proxy also sets the runtime global for the JS rewrite). See the
128
534
  * plugin's module doc / report for the exact serve-time contract.
129
535
  *
130
- * CSS `url(...)` references resolve relative to the stylesheet's own URL, so
131
- * they need no rewrite once the stylesheet itself is loaded from the right
132
- * prefix (which the HTML substitution handles).
536
+ * CSS `url(...)` references that Vite emits with the placeholder (e.g. bundled
537
+ * fonts) are rewritten relative to the stylesheet, which the HTML substitution
538
+ * already loads from the right prefix.
133
539
  */
134
540
  function connectDynamicBasePlugin(options = {}) {
135
541
  return {
@@ -153,6 +559,7 @@ function connectDynamicBasePlugin(options = {}) {
153
559
  };
154
560
  },
155
561
  generateBundle(_options, bundle) {
562
+ for (const file of Object.values(bundle)) if (file.type === "asset" && file.fileName.endsWith(".css")) file.source = relativeCssUrls(String(file.source), file.fileName);
156
563
  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}`);
157
564
  }
158
565
  };
@@ -229,13 +636,19 @@ const VENDOR_DYNAMIC_BASE = `${DYNAMIC_BASE_PLACEHOLDER}${VENDOR_URL_PREFIX.repl
229
636
  */
230
637
  async function prebuildConnectVendor(options) {
231
638
  const include = expandIncludeSubpaths(options.dirname, options.include ?? DEFAULT_VENDOR_INCLUDE);
639
+ const own = options.ownDeps ? resolveOwnDeps(options.ownDeps.from, options.ownDeps.specs) : {};
640
+ for (const spec of Object.keys(own)) if (!include.includes(spec)) include.push(spec);
232
641
  const external = options.external ?? VENDOR_EXTERNAL;
233
642
  const vendorDir = options.vendorDir ?? join(options.dirname, "node_modules/.ph-vendor");
234
643
  const importMapPath = join(vendorDir, "import-map.json");
235
- const versionDigest = resolveVersionDigest(options.dirname, [...include, ...external]);
236
- const versions = resolveDepVersions(options.dirname, [...include, ...external]);
644
+ const stamps = /* @__PURE__ */ new Map();
645
+ const versions = resolveDepVersions(options.dirname, [...include, ...external], stamps);
237
646
  const base = options.base ?? VENDOR_DYNAMIC_BASE;
238
647
  const nodeEnv = options.nodeEnv ?? "development";
648
+ const versionDigest = resolveVersionDigest(versions, own, stamps, {
649
+ base,
650
+ nodeEnv
651
+ });
239
652
  try {
240
653
  const hit = readCacheHit(importMapPath, include, external, versionDigest);
241
654
  if (hit) return {
@@ -257,7 +670,7 @@ async function prebuildConnectVendor(options) {
257
670
  vendorDir,
258
671
  ...raced
259
672
  };
260
- const result = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest, versions, base, nodeEnv);
673
+ const result = await buildVendorAtomic(options.dirname, vendorDir, include, external, versionDigest, versions, base, nodeEnv, own);
261
674
  return result ? {
262
675
  vendorDir,
263
676
  ...result
@@ -274,13 +687,41 @@ function readCacheHit(importMapPath, include, external, versionDigest) {
274
687
  if (!existsSync(importMapPath)) return null;
275
688
  try {
276
689
  const cached = JSON.parse(readFileSync(importMapPath, "utf8"));
277
- if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest) return {
690
+ if (sameSet(cached.include, include) && sameSet(cached.external, external) && cached.versionDigest === versionDigest && typeof cached.sharedDeps === "string") return {
278
691
  imports: cached.imports,
279
- versions: cached.versions ?? {}
692
+ versions: cached.versions ?? {},
693
+ sharedDeps: cached.sharedDeps
280
694
  };
281
695
  } catch {}
282
696
  return null;
283
697
  }
698
+ const NON_RUNTIME_FILE = /\.(map|d\.[cm]?ts|tsbuildinfo)$/;
699
+ function linkedBuildStamp(pkgRoot) {
700
+ if (pkgRoot.split(/[\\/]/).includes("node_modules")) return "";
701
+ let newest = 0;
702
+ try {
703
+ const entries = readdirSync(join(pkgRoot, "dist"), {
704
+ recursive: true,
705
+ withFileTypes: true
706
+ });
707
+ for (const entry of entries) {
708
+ if (!entry.isFile() || NON_RUNTIME_FILE.test(entry.name)) continue;
709
+ const { mtimeMs } = statSync(join(entry.parentPath, entry.name));
710
+ if (mtimeMs > newest) newest = mtimeMs;
711
+ }
712
+ } catch {
713
+ return "";
714
+ }
715
+ return newest ? `+dist.${Math.trunc(newest)}` : "";
716
+ }
717
+ function cachedStamp(stamps, pkgRoot) {
718
+ let stamp = stamps.get(pkgRoot);
719
+ if (stamp === void 0) {
720
+ stamp = linkedBuildStamp(pkgRoot);
721
+ stamps.set(pkgRoot, stamp);
722
+ }
723
+ return stamp;
724
+ }
284
725
  /**
285
726
  * Resolve the installed version of each spec's owning package, deduped by
286
727
  * package name. Resolution order per spec:
@@ -294,7 +735,7 @@ function readCacheHit(importMapPath, include, external, versionDigest) {
294
735
  * Unresolvable specs contribute a "missing" sentinel, unknown versions
295
736
  * "unknown".
296
737
  */
297
- function resolveDepVersions(dirname$3, specs) {
738
+ function resolveDepVersions(dirname$3, specs, stamps = /* @__PURE__ */ new Map()) {
298
739
  const req = createRequire(join(dirname$3, "noop.js"));
299
740
  const seen = {};
300
741
  let selfMeta = null;
@@ -313,7 +754,7 @@ function resolveDepVersions(dirname$3, specs) {
313
754
  try {
314
755
  const pkgRoot = realpathSync(join(dirname$3, "node_modules", pkg));
315
756
  const meta = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
316
- version = String(meta.version ?? "unknown");
757
+ version = String(meta.version ?? "unknown") + cachedStamp(stamps, pkgRoot);
317
758
  } catch {
318
759
  try {
319
760
  const entry = req.resolve(spec);
@@ -335,12 +776,16 @@ function resolveDepVersions(dirname$3, specs) {
335
776
  }
336
777
  return seen;
337
778
  }
338
- function resolveVersionDigest(dirname, specs) {
339
- const versions = resolveDepVersions(dirname, specs);
779
+ function resolveVersionDigest(versions, own, stamps, build) {
340
780
  const h = createHash("sha256");
781
+ h.update(`base:${build.base}\nnodeEnv:${build.nodeEnv}\n`);
341
782
  const workerHash = createHash("sha256").update(VENDOR_BUILD_WORKER).digest("hex");
342
783
  h.update(`worker:${workerHash}\n`);
343
784
  for (const pkg of Object.keys(versions).sort()) h.update(`${pkg}@${versions[pkg]}\n`);
785
+ for (const spec of Object.keys(own).sort()) {
786
+ const stamp = cachedStamp(stamps, ownPackageRoot(own[spec]));
787
+ h.update(`own:${spec}=${own[spec]}${stamp}\n`);
788
+ }
344
789
  return h.digest("hex").slice(0, 16);
345
790
  }
346
791
  function sameSet(a, b) {
@@ -425,6 +870,62 @@ function importTarget$1(value) {
425
870
  }
426
871
  return null;
427
872
  }
873
+ /** The package root at or above `from` that Node would load `pkg` from, or null. */
874
+ function findPackageRoot(from, pkg) {
875
+ let dir = from;
876
+ for (;;) {
877
+ const candidate = join(dir, "node_modules", pkg);
878
+ if (existsSync(join(candidate, "package.json"))) return realpathSync(candidate);
879
+ const parent = dirname(dir);
880
+ if (parent === dir) return null;
881
+ dir = parent;
882
+ }
883
+ }
884
+ function ownRoots(own) {
885
+ const roots = /* @__PURE__ */ new Set();
886
+ for (const file of Object.values(own)) {
887
+ const root = ownPackageRoot(file);
888
+ try {
889
+ const meta = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
890
+ if (Object.keys(meta.dependencies ?? {}).length === 0) roots.add(root + sep);
891
+ } catch {}
892
+ }
893
+ return [...roots];
894
+ }
895
+ function ownPackageRoot(file) {
896
+ let dir = dirname(file);
897
+ for (;;) {
898
+ try {
899
+ if (JSON.parse(readFileSync(join(dir, "package.json"), "utf8")).name) return dir;
900
+ } catch {}
901
+ const parent = dirname(dir);
902
+ if (parent === dir) return dir;
903
+ dir = parent;
904
+ }
905
+ }
906
+ /** Each spec's browser/import entry file when resolved from `from`; unresolvable specs are left out. */
907
+ function resolveOwnDeps(from, specs) {
908
+ const out = {};
909
+ for (const spec of specs) {
910
+ const { pkg, sub } = parsePkg(spec);
911
+ const root = findPackageRoot(from, pkg);
912
+ if (!root) continue;
913
+ try {
914
+ const meta = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
915
+ const key = sub ? `./${sub}` : ".";
916
+ let target = null;
917
+ if (meta.exports && typeof meta.exports === "object") {
918
+ const exp = meta.exports;
919
+ target = Object.keys(exp).some((k) => k.startsWith(".")) ? key in exp ? importTarget$1(exp[key]) : null : sub ? null : importTarget$1(exp);
920
+ } else if (typeof meta.exports === "string") target = sub ? null : meta.exports;
921
+ else if (!sub) target = meta.module ?? meta.main ?? null;
922
+ if (!target || /\.(css|json|scss)$/.test(target)) continue;
923
+ const file = join(root, target);
924
+ if (existsSync(file)) out[spec] = realpathSync(file);
925
+ } catch {}
926
+ }
927
+ return out;
928
+ }
428
929
  function parsePkg(spec) {
429
930
  if (spec.startsWith("@")) {
430
931
  const parts = spec.split("/");
@@ -520,12 +1021,12 @@ function withVendorBase(imports, base) {
520
1021
  * table, writes the runtime shared-deps module, and does the swap.
521
1022
  * Returns the published import map + versions, or null on failure.
522
1023
  */
523
- async function buildVendorAtomic(dirname$4, vendorDir, include, external, versionDigest, versions, base, nodeEnv) {
1024
+ async function buildVendorAtomic(dirname$4, vendorDir, include, external, versionDigest, versions, base, nodeEnv, own = {}) {
524
1025
  const parent = dirname(vendorDir);
525
1026
  mkdirSync(parent, { recursive: true });
526
1027
  const tmpDir = mkdtempSync(join(parent, ".ph-vendor.tmp-"));
527
1028
  try {
528
- const { ok, stderr } = await runBuildWorker(dirname$4, tmpDir, include, external, base, nodeEnv);
1029
+ const { ok, stderr } = await runBuildWorker(dirname$4, tmpDir, include, external, base, nodeEnv, own);
529
1030
  const tmpMap = join(tmpDir, "import-map.json");
530
1031
  if (!ok || !existsSync(tmpMap)) {
531
1032
  const detail = stderr.trim();
@@ -536,8 +1037,14 @@ async function buildVendorAtomic(dirname$4, vendorDir, include, external, versio
536
1037
  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"));
537
1038
  meta.versionDigest = versionDigest;
538
1039
  meta.versions = versions;
1040
+ meta.base = base;
1041
+ meta.nodeEnv = nodeEnv;
1042
+ const sharedDepsSource = `export const imports = ${JSON.stringify(withVendorBase(meta.imports, base))};\nexport const versions = ${JSON.stringify(versions)};\n`;
1043
+ const sharedDepsFile = `shared-deps-${createHash("sha256").update(sharedDepsSource).digest("hex").slice(0, 12)}.js`;
1044
+ writeFileSync(join(tmpDir, sharedDepsFile), sharedDepsSource);
1045
+ writeFileSync(join(tmpDir, "shared-deps.js"), sharedDepsSource);
1046
+ meta.sharedDeps = VENDOR_URL_PREFIX + sharedDepsFile;
539
1047
  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`);
541
1048
  const oldDir = `${vendorDir}.old-${process.pid}-${Date.now()}`;
542
1049
  if (existsSync(vendorDir)) renameSync(vendorDir, oldDir);
543
1050
  renameSync(tmpDir, vendorDir);
@@ -548,7 +1055,8 @@ async function buildVendorAtomic(dirname$4, vendorDir, include, external, versio
548
1055
  });
549
1056
  return {
550
1057
  imports: meta.imports,
551
- versions
1058
+ versions,
1059
+ sharedDeps: meta.sharedDeps
552
1060
  };
553
1061
  } catch (err) {
554
1062
  rmSync(tmpDir, {
@@ -577,7 +1085,7 @@ function resolveSelfModulePath() {
577
1085
  * given out dir, and writes the import map. Captures stdout/stderr so a failure
578
1086
  * is debuggable; the normal path stays quiet.
579
1087
  */
580
- function runBuildWorker(dirname, outDir, include, external, base, nodeEnv) {
1088
+ function runBuildWorker(dirname, outDir, include, external, base, nodeEnv, own = {}) {
581
1089
  const workerPath = join(outDir, "build-worker.mjs");
582
1090
  writeFileSync(workerPath, VENDOR_BUILD_WORKER);
583
1091
  const selfModulePath = resolveSelfModulePath();
@@ -592,7 +1100,11 @@ function runBuildWorker(dirname, outDir, include, external, base, nodeEnv) {
592
1100
  VENDOR_DYNAMIC_BASE,
593
1101
  selfModulePath,
594
1102
  base,
595
- nodeEnv
1103
+ nodeEnv,
1104
+ JSON.stringify({
1105
+ files: own,
1106
+ roots: ownRoots(own)
1107
+ })
596
1108
  ], {
597
1109
  cwd: dirname,
598
1110
  stdio: [
@@ -630,7 +1142,12 @@ import { createRequire } from 'node:module';
630
1142
  import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
631
1143
  import { join, isAbsolute } from 'node:path';
632
1144
  import { fileURLToPath, pathToFileURL } from 'node:url';
633
- const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON, dynamicBase, selfModulePath, baseArg, nodeEnvArg] = process.argv.slice(2);
1145
+ const [dirname, vendorDir, includeJSON, urlPrefix, externalJSON, dynamicBase, selfModulePath, baseArg, nodeEnvArg, ownJSON] = process.argv.slice(2);
1146
+ // ownDeps: spec -> absolute entry file, plus each one's package root.
1147
+ const { files: own = {}, roots: ownRootList = [] } = JSON.parse(ownJSON || '{}');
1148
+ // One chunk per own-dep package: split between an entry and a shared chunk, a
1149
+ // barrel can re-export from the entry and run before the classes it extends.
1150
+ const ownGroups = ownRootList.map((root, i) => ({ name: 'own-' + i, test: (id) => id.startsWith(root) }));
634
1151
  // Trailing argv are optional (older callers): base defaults to the dynamic
635
1152
  // placeholder behavior, nodeEnv to development. baseArg is the APP base —
636
1153
  // the same string the app build uses; the build base appends the vendor
@@ -655,23 +1172,42 @@ const RESERVED = new Set('enum void null function in instanceof typeof new delet
655
1172
  const input = {};
656
1173
  for (const spec of include) {
657
1174
  const name = entryName(spec);
1175
+ const from = own[spec] ?? spec;
658
1176
  let src;
659
1177
  try {
660
1178
  // Import the bare spec (not reqProj.resolve(spec)) so Node picks the same
661
1179
  // import/browser-condition module the build resolves — resolving first can
662
1180
  // pick a CJS sibling whose default-export shape differs from the ESM build.
663
- const ns = await import(spec);
1181
+ const ns = await import(own[spec] ? pathToFileURL(own[spec]).href : spec);
664
1182
  // Only fall back to re-exporting from default when the module exposes NO
665
1183
  // top-level named exports (a true CJS-interop module). If it does (e.g. zod
666
1184
  // exposes \`z\`), \`export *\` captures them; destructuring default would miss
667
1185
  // top-level names that aren't keys of the default object.
668
1186
  const named = Object.keys(ns).filter((k) => k !== 'default');
669
1187
  const cjs = named.length === 0 && ns.default && typeof ns.default === 'object';
670
- if (cjs) {
1188
+ // A strict entry keeps every export, and zod's \`locales\` (also reachable via \`z\`) pins
1189
+ // ~50 locales; publish them without it. zod/locales stays its own entry.
1190
+ const zodNs = (spec === 'zod' || spec.startsWith('zod/')) && !spec.includes('locales') && named.includes('locales');
1191
+ if (zodNs) {
1192
+ const keep = named.filter((k) => k !== 'locales' && k !== 'z' && /^[A-Za-z_$][\\w$]*$/.test(k));
1193
+ // \`core\` is zod/v4/core's namespace, which exports locales too: rebuild it without them.
1194
+ const coreKeys = keep.includes('core') ? Object.keys(await import('zod/v4/core')).filter((k) => k !== 'locales' && k !== 'default') : [];
1195
+ const reexport = keep.filter((k) => !(k === 'core' && coreKeys.length));
1196
+ src = 'export { ' + reexport.join(', ') + ' } from ' + JSON.stringify(from) + ';\\n'
1197
+ + 'import { ' + reexport.map((k) => k + ' as __z_' + k).join(', ') + ' } from ' + JSON.stringify(from) + ';\\n';
1198
+ if (coreKeys.length) {
1199
+ src += 'import { ' + coreKeys.map((k) => k + ' as __c_' + k).join(', ') + ' } from "zod/v4/core";\\n'
1200
+ + 'const __z_core = Object.freeze({ ' + coreKeys.map((k) => JSON.stringify(k) + ': __c_' + k).join(', ') + ' });\\n'
1201
+ + 'export { __z_core as core };\\n';
1202
+ }
1203
+ src += 'const z = Object.freeze({ ' + keep.map((k) => JSON.stringify(k) + ': __z_' + k).join(', ') + ' });\\n'
1204
+ + (named.includes('z') ? 'export { z };\\n' : '')
1205
+ + (('default' in ns) && ns.default === ns.z ? 'export { z as default };\\n' : '');
1206
+ } else if (cjs) {
671
1207
  const names = Object.keys(ns.default).filter((k) => k !== 'default' && k !== '__esModule' && /^[A-Za-z_$][\\w$]*$/.test(k));
672
1208
  const plain = names.filter((n) => !RESERVED.has(n));
673
1209
  const reserved = names.filter((n) => RESERVED.has(n));
674
- src = 'import d from ' + JSON.stringify(spec) + ';\\nexport default d;\\n'
1210
+ src = 'import d from ' + JSON.stringify(from) + ';\\nexport default d;\\n'
675
1211
  + (plain.length ? 'export const { ' + plain.join(', ') + ' } = d;\\n' : '');
676
1212
  // reserved words are invalid as const-binding names but valid as export
677
1213
  // aliases (export { x as enum }).
@@ -679,11 +1215,11 @@ for (const spec of include) {
679
1215
  src += 'const __r' + i + ' = d[' + JSON.stringify(n) + '];\\nexport { __r' + i + ' as ' + n + ' };\\n';
680
1216
  });
681
1217
  } else {
682
- src = 'export * from ' + JSON.stringify(spec) + ';\\n'
683
- + (('default' in ns) ? 'export { default } from ' + JSON.stringify(spec) + ';\\n' : '');
1218
+ src = 'export * from ' + JSON.stringify(from) + ';\\n'
1219
+ + (('default' in ns) ? 'export { default } from ' + JSON.stringify(from) + ';\\n' : '');
684
1220
  }
685
1221
  } catch {
686
- src = 'export * from ' + JSON.stringify(spec) + ';\\n';
1222
+ src = 'export * from ' + JSON.stringify(from) + ';\\n';
687
1223
  }
688
1224
  const file = join(srcDir, name + '.js');
689
1225
  writeFileSync(file, src);
@@ -708,7 +1244,7 @@ const phVendorResolve = {
708
1244
  return resolved;
709
1245
  },
710
1246
  };
711
- await build({
1247
+ const result = await build({
712
1248
  root: dirname, configFile: false, logLevel: 'error',
713
1249
  // Caller-provided build base (or the dynamic placeholder default):
714
1250
  // connectDynamicBasePlugin rewrites emitted chunk/asset URLs to resolve
@@ -734,13 +1270,21 @@ await build({
734
1270
  outDir: vendorDir, emptyOutDir: false, minify: nodeEnv === 'production', target: 'esnext', cssCodeSplit: true,
735
1271
  rollupOptions: {
736
1272
  input, preserveEntrySignatures: 'strict',
737
- output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
1273
+ // Content-hashed entry names: each build's import map points at new URLs.
1274
+ output: { format: 'es', entryFileNames: '[name]-[hash].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]', ...(ownGroups.length ? { codeSplitting: { groups: ownGroups } } : {}) },
738
1275
  },
739
1276
  },
740
1277
  });
741
1278
  rmSync(srcDir, { recursive: true, force: true });
1279
+ const emitted = {};
1280
+ for (const out of Array.isArray(result) ? result : [result]) {
1281
+ for (const chunk of out?.output ?? []) {
1282
+ if (chunk.type === 'chunk' && chunk.isEntry) emitted[chunk.name] = chunk.fileName;
1283
+ }
1284
+ }
742
1285
  const imports = {};
743
- for (const spec of include) imports[spec] = urlPrefix + entryName(spec) + '.js';
1286
+ // An entry the build never emitted keeps its unhashed name; the caller reports it as missing.
1287
+ for (const spec of include) imports[spec] = urlPrefix + (emitted[entryName(spec)] ?? entryName(spec) + '.js');
744
1288
  writeFileSync(join(vendorDir, 'import-map.json'), JSON.stringify({ include, external, imports }, null, 2));
745
1289
  `;
746
1290
  //#endregion
@@ -975,6 +1519,19 @@ function readWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv) {
975
1519
  return false;
976
1520
  }
977
1521
  }
1522
+ /** Content digest of a directory tree (relative paths + bytes; source maps skipped). */
1523
+ function directoryContentDigest(dir) {
1524
+ const h = createHash("sha256");
1525
+ const files = readdirSync(dir, {
1526
+ recursive: true,
1527
+ withFileTypes: true
1528
+ }).filter((e) => e.isFile() && !e.name.endsWith(".map")).map((e) => join(e.parentPath, e.name)).sort();
1529
+ for (const file of files) {
1530
+ h.update(`${file.slice(dir.length).split("\\").join("/")}\n`);
1531
+ h.update(readFileSync(file));
1532
+ }
1533
+ return h.digest("hex").slice(0, 16);
1534
+ }
978
1535
  async function waitForWorkerCacheHit(metaPath, sourceDigest, vendorKey, nodeEnv, lockDir) {
979
1536
  const deadline = Date.now() + LOCK_STALE_MS;
980
1537
  while (Date.now() < deadline) {
@@ -1462,189 +2019,6 @@ async function prebuildWorkerPackages(options) {
1462
2019
  }
1463
2020
  }
1464
2021
  //#endregion
1465
- //#region connect-utils/helpers.ts
1466
- const DEFAULT_CONNECT_OUTDIR = ".ph/connect-build/dist/";
1467
- function resolveViteConfigPath(options) {
1468
- const { projectRoot = cwd(), viteConfigFile } = options;
1469
- return viteConfigFile || join(projectRoot, "vite.config.ts");
1470
- }
1471
- function resolvePackage(packageName, root = process.cwd()) {
1472
- return createRequire(root).resolve(packageName, { paths: [root] });
1473
- }
1474
- function resolveConnectPackageJson(root = process.cwd()) {
1475
- try {
1476
- const connectPackageJsonPath = resolvePackage("@powerhousedao/connect/package.json", root);
1477
- const fileContents = fs.readFileSync(connectPackageJsonPath, "utf-8");
1478
- return JSON.parse(fileContents);
1479
- } catch (error) {
1480
- console.error(`Error reading Connect package.json:`, error);
1481
- return null;
1482
- }
1483
- }
1484
- /**
1485
- * Finds the dist dir of Connect on the local machine
1486
- */
1487
- function resolveConnectBundle(root = process.cwd()) {
1488
- const connectIndexPath = resolvePackage("@powerhousedao/connect", root);
1489
- const connectRootPath = connectIndexPath.substring(0, connectIndexPath.indexOf("connect") + 7);
1490
- return join(connectRootPath, "dist/");
1491
- }
1492
- function resolveConnectPublicDir(root = process.cwd()) {
1493
- const connectIconPath = resolvePackage("@powerhousedao/connect/public/icon.ico", root);
1494
- return path.join(connectIconPath, "../");
1495
- }
1496
- /**
1497
- * Copies the Connect dist dir to the target path
1498
- */
1499
- function copyConnect(sourcePath, targetPath) {
1500
- try {
1501
- fs.rmSync(targetPath, {
1502
- recursive: true,
1503
- force: true
1504
- });
1505
- fs.cpSync(sourcePath, targetPath, { recursive: true });
1506
- } catch (error) {
1507
- console.error(`❌ Error copying ${sourcePath} to ${targetPath}:`, error);
1508
- }
1509
- }
1510
- /**
1511
- * Backs up the index.html file
1512
- *
1513
- * Needed when running the Connect Studio dev server on Windows
1514
- */
1515
- function backupIndexHtml(appPath, restore = false) {
1516
- const filePath = join(appPath, "index.html");
1517
- const backupPath = join(appPath, "index.html.bak");
1518
- const paths = restore ? [backupPath, filePath] : [filePath, backupPath];
1519
- if (fs.existsSync(paths[0])) fs.copyFileSync(paths[0], paths[1]);
1520
- }
1521
- function removeBase64EnvValues(appPath) {
1522
- backupIndexHtml(appPath);
1523
- const filePath = join(appPath, "index.html");
1524
- fs.readFile(filePath, "utf-8", (err, data) => {
1525
- if (err) {
1526
- console.error("Error reading file:", err);
1527
- return;
1528
- }
1529
- const modifiedData = data.replace(/"LOCAL_DOCUMENT_MODELS":\s*".*?",/, `"LOCAL_DOCUMENT_MODELS": "",`).replace(/"LOCAL_DOCUMENT_EDITORS":\s*".*?"/, `"LOCAL_DOCUMENT_EDITORS": ""`);
1530
- console.log("Modified data:", modifiedData);
1531
- fs.writeFile(filePath, modifiedData, "utf-8", (err) => {
1532
- if (err) {
1533
- console.error("Error writing file:", err);
1534
- return;
1535
- }
1536
- });
1537
- });
1538
- }
1539
- function readJsonFile(filePath) {
1540
- try {
1541
- const absolutePath = resolve(filePath);
1542
- const fileContents = fs.readFileSync(absolutePath, "utf-8");
1543
- return JSON.parse(fileContents);
1544
- } catch (_error) {
1545
- console.error(`Error reading file: ${filePath}`);
1546
- return null;
1547
- }
1548
- }
1549
- /**
1550
- * Takes a list of Powerhouse project packages and optionally local Powerhouse packages and outputs a js file which exports those packages for use in Connect Studio.
1551
- */
1552
- function makeImportScriptFromPackages(args) {
1553
- const { packages, localJsPath, localCssPath, importStyles = true } = args;
1554
- const imports = [];
1555
- const moduleNames = [];
1556
- let counter = 0;
1557
- for (const packageName of packages) {
1558
- const moduleName = `module${counter}`;
1559
- moduleNames.push(moduleName);
1560
- imports.push(`import * as ${moduleName} from '${packageName}';`);
1561
- if (importStyles) imports.push(`import '${packageName}/style.css';`);
1562
- counter++;
1563
- }
1564
- const exports = moduleNames.map((name, index) => `{
1565
- id: "${packages[index]}",
1566
- ...${name},
1567
- }`);
1568
- const hasModule = localJsPath !== void 0;
1569
- const hasStyles = importStyles && localCssPath !== void 0;
1570
- if (hasModule || hasStyles) {
1571
- if (hasStyles) imports.push(`import '${localCssPath}';`);
1572
- if (hasModule) {
1573
- const moduleName = `module${counter}`;
1574
- imports.push(`import * as ${moduleName} from '${localJsPath}';`);
1575
- exports.push(`{
1576
- id: "${LOCAL_PACKAGE_ID}",
1577
- ...${moduleName},
1578
- }`);
1579
- }
1580
- }
1581
- const exportStatement = `export default [${exports.length ? `
1582
- ${exports.join(",\n")}
1583
- ` : ""}];`;
1584
- return `${imports.join("\n")}\n\n${exportStatement}`;
1585
- }
1586
- function ensureNodeVersion(minVersion = "24") {
1587
- const version = process.versions.node;
1588
- if (!version) return;
1589
- if (version < minVersion) {
1590
- console.error(`Node version ${minVersion} or higher is required. Current version: ${version}`);
1591
- process.exit(1);
1592
- }
1593
- }
1594
- function runShellScriptPlugin(scriptName, connectPath) {
1595
- return {
1596
- name: "vite-plugin-run-shell-script",
1597
- buildStart() {
1598
- const scriptPath = join(connectPath, scriptName);
1599
- if (fs.existsSync(scriptPath)) exec(`sh ${scriptPath}`, (error, stdout, stderr) => {
1600
- if (error) {
1601
- console.error(`Error executing the script: ${error.message}`);
1602
- removeBase64EnvValues(connectPath);
1603
- return;
1604
- }
1605
- if (stderr) console.error(stderr);
1606
- });
1607
- }
1608
- };
1609
- }
1610
- /**
1611
- * Shared helper to modify the <head> tag of an HTML file by transforming its contents.
1612
- */
1613
- async function modifyHtmlHead(pathToHtml, contents, transform) {
1614
- if (!existsSync(pathToHtml)) throw new Error(`File ${pathToHtml} does not exist.`);
1615
- let html = await readFile(pathToHtml, "utf8");
1616
- html = transform(html, contents);
1617
- await writeFile(pathToHtml, html, "utf8");
1618
- }
1619
- /**
1620
- * Appends the contents to the <head> tag of the index.html file
1621
- */
1622
- async function appendToHtmlHead(pathToHtml, contents) {
1623
- return modifyHtmlHead(pathToHtml, contents, (html, contents) => {
1624
- if (!html.includes("</head>")) throw new Error("No </head> tag found in the HTML file.");
1625
- return html.replace("</head>", `\n${contents}\n</head>`);
1626
- });
1627
- }
1628
- /**
1629
- * Prepends the contents to the <head> tag of the index.html file
1630
- */
1631
- async function prependToHtmlHead(pathToHtml, contents) {
1632
- return modifyHtmlHead(pathToHtml, contents, (html, contents) => {
1633
- if (!html.includes("</head>")) throw new Error("No </head> tag found in the HTML file.");
1634
- return html.replace("<head>", `<head>\n${contents}\n`);
1635
- });
1636
- }
1637
- function runTsc(outDir) {
1638
- execSync(`npx tsc --outDir ${outDir}`, { stdio: "inherit" });
1639
- }
1640
- function stripVersionFromPackage(packageName) {
1641
- const trimmed = packageName.trim();
1642
- if (!trimmed) return "";
1643
- const lastAtIndex = trimmed.lastIndexOf("@");
1644
- if (lastAtIndex > 0) return trimmed.substring(0, lastAtIndex);
1645
- return trimmed;
1646
- }
1647
- //#endregion
1648
2022
  //#region connect-utils/runtime-config-schema.ts
1649
2023
  const RUNTIME_CONFIG_SCHEMA_ID = "https://powerhouse.inc/schemas/powerhouse.config.json";
1650
2024
  const RUNTIME_CONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/powerhouse-inc/powerhouse/main/packages/builder-tools/connect-utils/runtime-config.schema.json";
@@ -1771,6 +2145,7 @@ const VENDOR_MIME = {
1771
2145
  const VENDOR_MODE = process.env.PH_CONNECT_EXTERNALIZE_VENDOR === "1";
1772
2146
  const VENDOR_EXTRA = (process.env.PH_CONNECT_VENDOR_EXTRA ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1773
2147
  const HEAVY_LIBS = [...DEFAULT_VENDOR_INCLUDE, ...VENDOR_EXTRA];
2148
+ const heavyLibsFor = (routesDir) => routesDir ? HEAVY_LIBS.filter((lib) => lib !== "@powerhousedao/connect") : HEAVY_LIBS;
1774
2149
  function withBase$1(base, p) {
1775
2150
  return `${base}${p}`.replace(/\/{2,}/g, "/");
1776
2151
  }
@@ -1806,15 +2181,16 @@ function externalizePlugin(externals) {
1806
2181
  * the React shims above → one React instance across Connect, the vendor, and
1807
2182
  * CDN editors.
1808
2183
  */
1809
- async function devReactImportmapPlugin(projectRoot = process.cwd()) {
2184
+ async function devReactImportmapPlugin(projectRoot = process.cwd(), routesDir = resolveConnectRoutesDir(projectRoot)) {
1810
2185
  let namedExports = /* @__PURE__ */ new Map();
1811
2186
  let base = "/";
1812
2187
  let vendor = null;
2188
+ const heavyLibs = heavyLibsFor(routesDir);
1813
2189
  if (VENDOR_MODE) {
1814
2190
  const errorRef = {};
1815
2191
  vendor = await prebuildConnectVendor({
1816
2192
  dirname: projectRoot,
1817
- include: HEAVY_LIBS,
2193
+ include: heavyLibs,
1818
2194
  errorRef
1819
2195
  });
1820
2196
  if (!vendor) {
@@ -1833,8 +2209,8 @@ async function devReactImportmapPlugin(projectRoot = process.cwd()) {
1833
2209
  const include = new Set(cfg.optimizeDeps.include ?? []);
1834
2210
  REACT_DEPS.forEach((d) => include.add(d));
1835
2211
  if (vendorActive) {
1836
- HEAVY_LIBS.forEach((d) => include.delete(d));
1837
- cfg.optimizeDeps.exclude = [.../* @__PURE__ */ new Set([...cfg.optimizeDeps.exclude ?? [], ...HEAVY_LIBS])];
2212
+ heavyLibs.forEach((d) => include.delete(d));
2213
+ cfg.optimizeDeps.exclude = [.../* @__PURE__ */ new Set([...cfg.optimizeDeps.exclude ?? [], ...heavyLibs])];
1838
2214
  }
1839
2215
  cfg.optimizeDeps.include = [...include];
1840
2216
  },
@@ -1988,6 +2364,12 @@ function readProjectPackageInfo(projectRoot) {
1988
2364
  return null;
1989
2365
  }
1990
2366
  }
2367
+ /** `html` with its `<title>` text set to `appName`, when one is configured. */
2368
+ function withAppTitle(html, appName) {
2369
+ if (!appName) return html;
2370
+ const escaped = appName.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2371
+ return html.replace(/<title>[^<]*<\/title>/, () => `<title>${escaped}</title>`);
2372
+ }
1991
2373
  function phConfigPlugin(options) {
1992
2374
  const localPackage = readProjectPackageInfo(options.projectRoot ?? process.cwd());
1993
2375
  const sourceSettings = {
@@ -2030,6 +2412,9 @@ function phConfigPlugin(options) {
2030
2412
  });
2031
2413
  }
2032
2414
  },
2415
+ transformIndexHtml(html) {
2416
+ return withAppTitle(html, runtimeConfig.connect.branding?.appName);
2417
+ },
2033
2418
  generateBundle() {
2034
2419
  this.emitFile({
2035
2420
  type: "asset",
@@ -2455,17 +2840,40 @@ function resolveReactEntries(dirname$1) {
2455
2840
  }
2456
2841
  return out;
2457
2842
  }
2843
+ function reactImportMap(base, files) {
2844
+ const map = {};
2845
+ for (const [spec, file] of Object.entries(files)) {
2846
+ map[spec] = `${base}${REACT_URL_DIR}/${file}`;
2847
+ if (spec.includes("/")) map[`${spec}.js`] = map[spec];
2848
+ }
2849
+ return map;
2850
+ }
2458
2851
  function reactSelfHostPlugin(options) {
2459
2852
  const entries = resolveReactEntries(options.dirname);
2460
2853
  let absOutDir = resolve(options.dirname, "dist");
2854
+ let base = "/";
2855
+ let staging;
2856
+ let files = {};
2461
2857
  let importMap = {};
2462
2858
  return {
2463
2859
  name: "ph-react-self-host",
2464
2860
  apply: "build",
2465
2861
  configResolved(config) {
2466
2862
  absOutDir = resolve(config.root, config.build.outDir);
2467
- importMap = Object.fromEntries(Object.keys(entries).map((spec) => [spec, `${config.base}${REACT_URL_DIR}/${spec}.js`]));
2468
- for (const pkg of REACT_PACKAGES) importMap[`${pkg}/`] = `${config.base}${REACT_URL_DIR}/${pkg}/`;
2863
+ base = config.base;
2864
+ },
2865
+ async buildStart() {
2866
+ files = {};
2867
+ importMap = {};
2868
+ if (!Object.keys(entries).length) return;
2869
+ const stagingParent = join(options.dirname, "node_modules");
2870
+ staging = mkdtempSync(join(existsSync(stagingParent) ? stagingParent : options.dirname, ".ph-react-self-host-"));
2871
+ const { ok, stderr } = await runReactBuild(options.dirname, staging, entries, options.dev ? "development" : "production");
2872
+ if (!ok) this.error(`react self-host build failed${stderr.trim() ? `:\n${stderr.trim()}` : ""}`);
2873
+ const manifest = join(staging, FILES_MANIFEST);
2874
+ files = JSON.parse(readFileSync(manifest, "utf8"));
2875
+ rmSync(manifest, { force: true });
2876
+ importMap = reactImportMap(base, files);
2469
2877
  },
2470
2878
  transformIndexHtml() {
2471
2879
  if (!Object.keys(importMap).length) return;
@@ -2476,13 +2884,27 @@ function reactSelfHostPlugin(options) {
2476
2884
  injectTo: "head-prepend"
2477
2885
  }];
2478
2886
  },
2479
- async closeBundle() {
2480
- if (!Object.keys(entries).length) return;
2481
- const { ok, stderr } = await runReactBuild(options.dirname, join(absOutDir, REACT_URL_DIR), entries, options.dev ? "development" : "production");
2482
- if (!ok) this.error(`react self-host build failed${stderr.trim() ? `:\n${stderr.trim()}` : ""}`);
2887
+ closeBundle() {
2888
+ if (!staging) return;
2889
+ const dest = join(absOutDir, REACT_URL_DIR);
2890
+ rmSync(dest, {
2891
+ recursive: true,
2892
+ force: true
2893
+ });
2894
+ mkdirSync(absOutDir, { recursive: true });
2895
+ cpSync(staging, dest, { recursive: true });
2896
+ chmodSync(dest, 493);
2897
+ rmSync(staging, {
2898
+ recursive: true,
2899
+ force: true
2900
+ });
2901
+ staging = void 0;
2902
+ const missing = Object.entries(files).filter(([, file]) => !existsSync(join(dest, file)));
2903
+ if (missing.length) this.error(`react self-host: import map entries have no file in ${dest}:\n` + missing.map(([spec, file]) => ` ${spec} -> ${file}`).join("\n"));
2483
2904
  }
2484
2905
  };
2485
2906
  }
2907
+ const FILES_MANIFEST = "files.json";
2486
2908
  function runReactBuild(dirname, outDir, entries, nodeEnv) {
2487
2909
  mkdirSync(outDir, { recursive: true });
2488
2910
  const workerPath = join(outDir, "build-worker.mjs");
@@ -2557,7 +2979,7 @@ for (const [spec, file] of Object.entries(entries)) {
2557
2979
  input[spec] = entryFile;
2558
2980
  }
2559
2981
  try {
2560
- await build({
2982
+ const result = await build({
2561
2983
  root: dirname, configFile: false, logLevel: 'error',
2562
2984
  base: './', publicDir: false,
2563
2985
  define: { 'process.env.NODE_ENV': JSON.stringify(nodeEnv) },
@@ -2566,11 +2988,18 @@ try {
2566
2988
  outDir, emptyOutDir: false, minify: nodeEnv === 'production', target: 'esnext',
2567
2989
  rollupOptions: {
2568
2990
  input, preserveEntrySignatures: 'strict',
2569
- output: { format: 'es', entryFileNames: '[name].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
2991
+ output: { format: 'es', entryFileNames: '[name]-[hash].js', chunkFileNames: 'chunks/[name]-[hash].js', assetFileNames: 'assets/[name]-[hash][extname]' },
2570
2992
  },
2571
2993
  },
2572
2994
  });
2573
2995
  rmSync(srcDir, { recursive: true, force: true });
2996
+ const files = {};
2997
+ for (const out of Array.isArray(result) ? result : [result]) {
2998
+ for (const chunk of out?.output ?? []) {
2999
+ if (chunk.type === 'chunk' && chunk.isEntry) files[chunk.name] = chunk.fileName;
3000
+ }
3001
+ }
3002
+ writeFileSync(join(outDir, 'files.json'), JSON.stringify(files));
2574
3003
  // Force exit: rolldown can leave native worker threads alive that keep the
2575
3004
  // event loop open, hanging the parent's spawn() promise indefinitely.
2576
3005
  process.exit(0);
@@ -2679,7 +3108,7 @@ const THEME_BOOT_MARKER = "data-ph-theme-boot";
2679
3108
  * Keep semantically identical to the runtime store in
2680
3109
  * `@powerhousedao/reactor-browser` (hooks/theme.ts).
2681
3110
  */
2682
- const THEME_BOOT_SCRIPT = "(function(){try{var p=new URLSearchParams(location.search).get('theme');if(p==='dark'||p==='light')localStorage.setItem('ph:theme',p);var s=localStorage.getItem('ph:theme');var d=s==='dark'||((!s||s==='system')&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.classList.toggle('dark',d);}catch(e){}})();";
3111
+ const THEME_BOOT_SCRIPT = "(function(){try{var p=new URLSearchParams(location.search).get('theme');if(p==='dark'||p==='light')localStorage.setItem('ph:theme',p);var s=localStorage.getItem('ph:theme');var m=matchMedia('(prefers-color-scheme: dark)');var d=s==='dark'||((!s||s==='system')&&m.matches);document.documentElement.classList.toggle('dark',d);if(!s||s==='system'){var f=function(){var t=localStorage.getItem('ph:theme');if(!t||t==='system')document.documentElement.classList.toggle('dark',m.matches);};m.addEventListener('change',f);requestAnimationFrame(f);}}catch(e){}})();";
2683
3112
  /**
2684
3113
  * Injects the theme boot script at the top of `<head>` of every emitted
2685
3114
  * index.html, so built Connect apps render the stored theme from first paint
@@ -2873,7 +3302,7 @@ function getConnectBaseViteConfig(options) {
2873
3302
  });
2874
3303
  setConnectEnv(env);
2875
3304
  const phConfigPath = join(options.dirname, "powerhouse.config.json");
2876
- const phConfig = options.powerhouseConfig ?? getConfig(phConfigPath);
3305
+ const phConfig = options.powerhouseConfig ?? getConfig$1(phConfigPath);
2877
3306
  const packagesFromConfig = phConfig.packages ?? [];
2878
3307
  const localPackagesFromConfig = getLocalPackageNamesFromPowerhouseConfig(phConfig);
2879
3308
  const phPackagesStr = env.PH_PACKAGES;
@@ -2961,6 +3390,8 @@ function getConnectBaseViteConfig(options) {
2961
3390
  return null;
2962
3391
  }
2963
3392
  }).filter((p) => p !== null);
3393
+ const routesDir = resolveConnectRoutesDir(options.dirname);
3394
+ const routes = connectRoutes(options.dirname, routesDir);
2964
3395
  return {
2965
3396
  configFile: false,
2966
3397
  mode,
@@ -2970,12 +3401,19 @@ function getConnectBaseViteConfig(options) {
2970
3401
  fs: { allow: [searchForWorkspaceRoot(options.dirname), ...linkedRoots] }
2971
3402
  },
2972
3403
  resolve: {
2973
- dedupe: ["react", "react-dom"],
3404
+ alias: [routes.alias],
3405
+ dedupe: [
3406
+ "react",
3407
+ "react-dom",
3408
+ ...routes.dedupe
3409
+ ],
2974
3410
  tsconfigPaths: true
2975
3411
  },
2976
3412
  define: {
2977
3413
  PH_CONNECT_SENTRY_RELEASE: JSON.stringify(release || "unknown"),
2978
- PH_CONNECT_BUILD_HASH: JSON.stringify(buildHash)
3414
+ PH_CONNECT_BUILD_HASH: JSON.stringify(buildHash),
3415
+ ...options.vendor?.sharedDeps && mode === "production" ? { PH_CONNECT_SHARED_DEPS_URL: JSON.stringify(options.vendor.sharedDeps) } : {},
3416
+ ...options.reactorWorkerVersion ? { PH_CONNECT_REACTOR_WORKER_VERSION: JSON.stringify(options.reactorWorkerVersion) } : {}
2979
3417
  },
2980
3418
  customLogger,
2981
3419
  envPrefix: ["PH_CONNECT_"],
@@ -3002,8 +3440,9 @@ function getConnectBaseViteConfig(options) {
3002
3440
  packages: localPackagesFromConfig,
3003
3441
  projectRoot: options.dirname
3004
3442
  }),
3005
- devReactImportmapPlugin(options.dirname),
3443
+ devReactImportmapPlugin(options.dirname, routesDir),
3006
3444
  reactorWorkerDevPlugin(options.dirname),
3445
+ ...routes.plugins,
3007
3446
  ...plugins,
3008
3447
  esmExternalRequirePlugin({ external: [...reactExternal, ...options.vendor && mode === "production" ? Object.keys(options.vendor.imports) : []] }),
3009
3448
  reactSelfHostPlugin({
@@ -3013,6 +3452,7 @@ function getConnectBaseViteConfig(options) {
3013
3452
  ...options.vendor && mode === "production" ? [vendorImportMapPlugin({ imports: options.vendor.imports })] : [],
3014
3453
  connectFaviconPlugin({ faviconPath: options.favicon }),
3015
3454
  connectThemeBootPlugin(),
3455
+ connectAppShellPlugin(options.dirname),
3016
3456
  connectBuildHashPlugin(buildHash),
3017
3457
  ...options.dynamicBase ? [connectDynamicBasePlugin()] : [],
3018
3458
  pwaPlugins
@@ -3023,11 +3463,14 @@ function getConnectBaseViteConfig(options) {
3023
3463
  },
3024
3464
  build: {
3025
3465
  sourcemap: true,
3026
- rolldownOptions: { shimMissingExports: true }
3466
+ rolldownOptions: {
3467
+ shimMissingExports: true,
3468
+ output: { codeSplitting: { groups: connectRouterChunkGroups(routesDir) } }
3469
+ }
3027
3470
  }
3028
3471
  };
3029
3472
  }
3030
3473
  //#endregion
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 };
3474
+ export { APP_LAYOUT, APP_SHELL_MARKER, BOOT_RECOVER_KEY, BOOT_RECOVER_MARKER, BOOT_RECOVER_SCRIPT, BUILDER_TOOLS_VERSION, BUILD_HASH_FILE, CONNECT_ROUTES_GEN, CONNECT_ROUTES_SPECIFIER, DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, HOST_ROUTES_DIR, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, LOCK_STALE_MS, PH_DIR_NAME, REACTOR_WORKER_ENTRY, REACTOR_WORKER_MIME, REACTOR_WORKER_URL_PREFIX, ROUTE_TREE_SPECIFIER, 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, connectAppShellPlugin, connectBuildHashPlugin, connectDynamicBasePlugin, connectRouterChunkGroups, connectRoutes, connectRoutesGeneratorConfig, connectThemeBootPlugin, copyConnect, directoryContentDigest, distDirFingerprint, ensureNodeVersion, escapeForRegExp, findBundleSpecifierOffenders, findDisallowedSpecifiers, findPackageRoot, findWorkerUnsafeMarkers, generateConnectRoutes, getConnectBaseViteConfig, getConnectHtmlTags, injectAppShell, linkedBuildStamp, makeImportScriptFromPackages, missingVendorEntries, ownProjectPackage, phConfigPlugin, prebuildConnectVendor, prebuildReactorWorker, prebuildWorkerPackages, prependToHtmlHead, publishDirAtomic, readAppShell, readJsonFile, rebaseShellLinks, relativeCssUrls, releaseLock, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolveConnectRoutesDir, resolveOwnDeps, resolveOwnPackageVersion, resolvePackage, resolveReactorWorkerSource, resolveVersionDigest, resolveViteConfigPath, resolveWorkspacePackageDir, runShellScriptPlugin, runTsc, runWorkerBuildEntries, runtimeConfigSchema, stripVersionFromPackage, syncConnectRoutes, upstreamWorkerPackages, vendorImportMapPlugin, vendorRelativePath, withAppTitle, withVendorBase, workerSafeVendorImports };
3032
3475
 
3033
3476
  //# sourceMappingURL=index.mjs.map