@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.d.mts +170 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +679 -236
- package/dist/index.mjs.map +1 -1
- package/dist/service-worker/service-worker.ts +5 -21
- package/package.json +8 -5
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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 {
|
|
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
|
|
131
|
-
*
|
|
132
|
-
*
|
|
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
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
683
|
-
+ (('default' in ns) ? 'export { default } from ' + JSON.stringify(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
1837
|
-
cfg.optimizeDeps.exclude = [.../* @__PURE__ */ new Set([...cfg.optimizeDeps.exclude ?? [], ...
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
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
|
-
|
|
2468
|
-
|
|
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
|
-
|
|
2480
|
-
if (!
|
|
2481
|
-
const
|
|
2482
|
-
|
|
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')&&
|
|
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
|
-
|
|
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: {
|
|
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
|