@powerhousedao/builder-tools 6.2.2-dev.8 → 6.2.2-dev.80
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 +398 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +433 -106
- package/dist/index.mjs.map +1 -1
- package/dist/service-worker/service-worker.ts +383 -0
- package/dist/service-worker/virtual-sw-config.d.ts +22 -0
- package/package.json +10 -4
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { exec, execSync, spawn } from "node:child_process";
|
|
3
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
3
|
import fs, { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
5
4
|
import path, { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
5
|
+
import { exec, execSync, spawn } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import MagicString from "magic-string";
|
|
8
8
|
import { readFile, writeFile } from "node:fs/promises";
|
|
9
9
|
import { cwd } from "node:process";
|
|
10
|
-
import { DEFAULT_CONNECT_CONFIG, buildRuntimeConfig, deepMerge, loadConnectEnv, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, setConnectEnv } from "@powerhousedao/shared/connect";
|
|
10
|
+
import { DEFAULT_CONNECT_CONFIG, PWA_FILE_HANDLER_ACTION, buildRuntimeConfig, deepMerge, loadConnectEnv, mergeManifest, mergePwaConfig, normalizeBasePath, phConnectRuntimeConfigSchema, powerhousePackageSchema, setConnectEnv, unionStrings, withInferredCategory } from "@powerhousedao/shared/connect";
|
|
11
11
|
import { getConfig } from "@powerhousedao/config/node";
|
|
12
12
|
import tailwind from "@tailwindcss/vite";
|
|
13
13
|
import react from "@vitejs/plugin-react";
|
|
14
14
|
import { createLogger, esmExternalRequirePlugin, loadEnv, searchForWorkspaceRoot } from "vite";
|
|
15
15
|
import { createHtmlPlugin } from "vite-plugin-html";
|
|
16
|
+
import { PwaConfigSchema } from "@powerhousedao/shared/document-model";
|
|
17
|
+
import { toCdnUrl } from "@powerhousedao/shared/registry/urls";
|
|
16
18
|
import { VitePWA } from "vite-plugin-pwa";
|
|
17
19
|
//#region connect-utils/constants.ts
|
|
18
20
|
const EXTERNAL_PACKAGES_IMPORT = "PH:EXTERNAL_PACKAGES";
|
|
@@ -20,6 +22,73 @@ const IMPORT_SCRIPT_FILE = "external-packages.js";
|
|
|
20
22
|
const LOCAL_PACKAGE_ID = "ph:local-package";
|
|
21
23
|
const PH_DIR_NAME = ".ph";
|
|
22
24
|
//#endregion
|
|
25
|
+
//#region connect-utils/vite-plugins/build-hash.ts
|
|
26
|
+
/** Name of the build-identity file emitted into the build output. */
|
|
27
|
+
const BUILD_HASH_FILE = "build-hash.json";
|
|
28
|
+
/**
|
|
29
|
+
* Deterministic identity of a Connect build. Differs whenever the deployed
|
|
30
|
+
* content differs (published Connect version, project config, package list,
|
|
31
|
+
* build options) so the SPA can detect "the server is serving a different
|
|
32
|
+
* build than the one this bundle was built from" by comparing its baked copy
|
|
33
|
+
* (define `PH_CONNECT_BUILD_HASH`) to `build-hash.json`, which the build
|
|
34
|
+
* emits alongside the app and the server must serve no-cache.
|
|
35
|
+
*/
|
|
36
|
+
function computeBuildHash(input) {
|
|
37
|
+
return createHash("sha256").update(JSON.stringify(input)).digest("hex").slice(0, 16);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read the installed @powerhousedao/connect version. The package's `exports`
|
|
41
|
+
* map does not expose a `./package.json` subpath, so `require.resolve(
|
|
42
|
+
* "…/package.json")` throws — resolve the main entry and walk up to the
|
|
43
|
+
* package root instead.
|
|
44
|
+
*/
|
|
45
|
+
function readConnectVersion(projectRoot) {
|
|
46
|
+
try {
|
|
47
|
+
let dir = dirname(createRequire(join(projectRoot, "package.json")).resolve("@powerhousedao/connect", { paths: [projectRoot] }));
|
|
48
|
+
for (;;) {
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
51
|
+
if (pkg.name === "@powerhousedao/connect") return pkg.version ?? "unknown";
|
|
52
|
+
} catch {}
|
|
53
|
+
const parent = dirname(dir);
|
|
54
|
+
if (parent === dir) break;
|
|
55
|
+
dir = parent;
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
return "unknown";
|
|
59
|
+
}
|
|
60
|
+
function buildHashFromBuildOptions(options) {
|
|
61
|
+
return computeBuildHash({
|
|
62
|
+
workspaceGitSha: process.env.WORKSPACE_GIT_SHA ?? "",
|
|
63
|
+
workspaceVersion: process.env.WORKSPACE_VERSION ?? "",
|
|
64
|
+
connectPackageVersion: readConnectVersion(options.dirname),
|
|
65
|
+
projectConfig: options.phConfig,
|
|
66
|
+
packages: options.packages.map((p) => `${p.packageName}@${p.version ?? ""}:${p.provider ?? ""}`),
|
|
67
|
+
connectBasePath: options.connectBasePath ?? "",
|
|
68
|
+
offlineEnabled: options.offlineEnabled,
|
|
69
|
+
packageRegistryUrl: options.packageRegistryUrl ?? ""
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Emits `build-hash.json` (`{"hash": "<build id>"}`) into the build output.
|
|
74
|
+
* Emitted as an asset so it lands at the deploy base like every other file.
|
|
75
|
+
* Build-only: dev (studio) never runs generateBundle, and the SPA check
|
|
76
|
+
* treats a missing file as "nothing to compare" and stays silent.
|
|
77
|
+
*/
|
|
78
|
+
function connectBuildHashPlugin(buildHash) {
|
|
79
|
+
return {
|
|
80
|
+
name: "ph-connect-build-hash",
|
|
81
|
+
apply: "build",
|
|
82
|
+
generateBundle() {
|
|
83
|
+
this.emitFile({
|
|
84
|
+
type: "asset",
|
|
85
|
+
fileName: BUILD_HASH_FILE,
|
|
86
|
+
source: `${JSON.stringify({ hash: buildHash }, null, 2)}\n`
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
23
92
|
//#region connect-utils/vite-plugins/dynamic-base.ts
|
|
24
93
|
/**
|
|
25
94
|
* Placeholder base used when Connect is built in dynamic-base mode. The Vite
|
|
@@ -465,7 +534,9 @@ const include = JSON.parse(includeJSON);
|
|
|
465
534
|
const external = JSON.parse(externalJSON ?? '[]');
|
|
466
535
|
const externalSet = new Set(external);
|
|
467
536
|
const reqProj = createRequire(join(dirname, 'noop.js'));
|
|
468
|
-
|
|
537
|
+
// pathToFileURL: require.resolve returns an absolute path, and on Windows
|
|
538
|
+
// import('D:\\...') parses "D:" as a URL scheme (ERR_UNSUPPORTED_ESM_URL_SCHEME).
|
|
539
|
+
const { build, esmExternalRequirePlugin } = await import(pathToFileURL(reqProj.resolve('vite')).href);
|
|
469
540
|
// Load the dynamic-base plugin from builder-tools' own built bundle (passed as
|
|
470
541
|
// an absolute path) — it isn't resolvable as a bare specifier from the worker.
|
|
471
542
|
const { connectDynamicBasePlugin, DYNAMIC_BASE_PLACEHOLDER } = await import(pathToFileURL(selfModulePath));
|
|
@@ -1130,8 +1201,170 @@ function phConfigPlugin(options) {
|
|
|
1130
1201
|
};
|
|
1131
1202
|
}
|
|
1132
1203
|
//#endregion
|
|
1204
|
+
//#region connect-utils/vite-plugins/pwa-packages.ts
|
|
1205
|
+
const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
|
|
1206
|
+
function isPlainObject(value) {
|
|
1207
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1208
|
+
}
|
|
1209
|
+
function formatZodIssues(error) {
|
|
1210
|
+
return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
1211
|
+
}
|
|
1212
|
+
/** Turn a parsed manifest JSON into a contribution: its `pwa` fragment (a
|
|
1213
|
+
* malformed one → warn + skip that fragment, not the whole contribution) plus a
|
|
1214
|
+
* `categories` entry derived from the manifest's `category` field. Returns null
|
|
1215
|
+
* only when the manifest yields neither — no `pwa` and no `category`. */
|
|
1216
|
+
function toPwaContribution(manifest, fallbackLabel, onWarn) {
|
|
1217
|
+
if (!isPlainObject(manifest)) return null;
|
|
1218
|
+
const label = typeof manifest.name === "string" && manifest.name ? manifest.name : fallbackLabel;
|
|
1219
|
+
let config = {};
|
|
1220
|
+
if (manifest.pwa !== void 0) if (!isPlainObject(manifest.pwa)) onWarn(`PWA config: ${label} declares a non-object 'pwa' field in its manifest; ignored.`);
|
|
1221
|
+
else {
|
|
1222
|
+
const parsed = PwaConfigSchema.safeParse(manifest.pwa);
|
|
1223
|
+
if (!parsed.success) onWarn(`PWA config: ${label}'s pwa fragment is invalid; ignored. ${formatZodIssues(parsed.error)}`);
|
|
1224
|
+
else config = parsed.data;
|
|
1225
|
+
}
|
|
1226
|
+
config = withInferredCategory(config, manifest.category);
|
|
1227
|
+
if (Object.keys(config).length === 0) return null;
|
|
1228
|
+
return {
|
|
1229
|
+
source: label,
|
|
1230
|
+
config
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
/** Read the first parseable manifest among `candidates` (relative to `dir`)
|
|
1234
|
+
* and extract its pwa contribution. Candidates that resolve outside `dir`
|
|
1235
|
+
* (a hostile `./manifest` export) are warned about and skipped. */
|
|
1236
|
+
function readPwaFragmentFromDir(dir, candidates, fallbackLabel, onWarn) {
|
|
1237
|
+
for (const rel of candidates) {
|
|
1238
|
+
const manifestPath = path.resolve(dir, rel);
|
|
1239
|
+
if (path.relative(dir, manifestPath).startsWith("..")) {
|
|
1240
|
+
onWarn(`PWA config: ${fallbackLabel} declares a manifest path outside its package directory (${rel}); ignored.`);
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
1244
|
+
try {
|
|
1245
|
+
return toPwaContribution(JSON.parse(fs.readFileSync(manifestPath, "utf-8")), fallbackLabel, onWarn);
|
|
1246
|
+
} catch {
|
|
1247
|
+
onWarn(`PWA config: could not parse ${fallbackLabel}'s manifest at ${rel}; ignored.`);
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
function readLocalPackagePwaFragment(projectRoot, name, onWarn) {
|
|
1254
|
+
const pkgDir = path.join(projectRoot, "node_modules", name);
|
|
1255
|
+
let manifestRel;
|
|
1256
|
+
try {
|
|
1257
|
+
const exported = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8")).exports?.["./manifest"];
|
|
1258
|
+
if (typeof exported === "string") manifestRel = exported;
|
|
1259
|
+
} catch {}
|
|
1260
|
+
return readPwaFragmentFromDir(pkgDir, [
|
|
1261
|
+
manifestRel,
|
|
1262
|
+
"dist/powerhouse.manifest.json",
|
|
1263
|
+
"powerhouse.manifest.json"
|
|
1264
|
+
].filter((rel) => typeof rel === "string"), name, onWarn);
|
|
1265
|
+
}
|
|
1266
|
+
async function fetchRegistryPwaFragment(cdnUrl, pkg, onWarn, fetchImpl) {
|
|
1267
|
+
const spec = pkg.version ? `${pkg.packageName}@${pkg.version}` : pkg.packageName;
|
|
1268
|
+
const url = `${cdnUrl}/${spec}/powerhouse.manifest.json`;
|
|
1269
|
+
try {
|
|
1270
|
+
const response = await fetchImpl(url, { signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS) });
|
|
1271
|
+
if (response.status === 404) return null;
|
|
1272
|
+
if (!response.ok) {
|
|
1273
|
+
onWarn(`PWA config: registry returned ${response.status} for ${spec}'s manifest; its pwa fragment (if any) is skipped.`);
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
return toPwaContribution(await response.json(), pkg.packageName, onWarn);
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
onWarn(`PWA config: could not fetch ${spec}'s manifest from the registry (${error instanceof Error ? error.message : String(error)}); its pwa fragment (if any) is skipped.`);
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Read the `pwa` fragment of each package in `packages`, in the given order
|
|
1284
|
+
* (which becomes their merge precedence — later packages win scalar
|
|
1285
|
+
* conflicts). `provider: "local"` packages are read from node_modules;
|
|
1286
|
+
* everything else is fetched from the registry CDN (skipped with a warning
|
|
1287
|
+
* when `registryUrl` is missing or the registry is unreachable). Packages
|
|
1288
|
+
* with no `pwa` fragment are simply absent from the result.
|
|
1289
|
+
*/
|
|
1290
|
+
async function collectPackagePwaContributions(options) {
|
|
1291
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
1292
|
+
const onWarn = options.onWarn ?? (() => {});
|
|
1293
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
1294
|
+
const cdnUrl = options.registryUrl ? toCdnUrl(options.registryUrl) : null;
|
|
1295
|
+
return (await Promise.all(options.packages.map(async (pkg) => {
|
|
1296
|
+
if (pkg.provider === "local") return readLocalPackagePwaFragment(projectRoot, pkg.packageName, onWarn);
|
|
1297
|
+
if (!cdnUrl) {
|
|
1298
|
+
onWarn(`PWA config: no packageRegistryUrl configured; cannot read ${pkg.packageName}'s pwa fragment (if any) from the registry.`);
|
|
1299
|
+
return null;
|
|
1300
|
+
}
|
|
1301
|
+
return fetchRegistryPwaFragment(cdnUrl, pkg, onWarn, fetchImpl);
|
|
1302
|
+
}))).filter((contribution) => contribution !== null);
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Read the pwa fragment from the project's OWN manifest
|
|
1306
|
+
* (`powerhouse.manifest.json` under the project root, falling back to the
|
|
1307
|
+
* `dist/` copy the project build emits). The root file comes first — it is
|
|
1308
|
+
* the source the dist copy is made from, and a stale dist left by an older
|
|
1309
|
+
* build must not shadow it. Returns null silently when no manifest exists —
|
|
1310
|
+
* not every project ships one — or when it carries neither a `pwa` block nor a
|
|
1311
|
+
* `category` to derive `categories` from.
|
|
1312
|
+
*
|
|
1313
|
+
* The `pwa` block is parsed STRICTLY, like `connect.pwa` and unlike third-party
|
|
1314
|
+
* package fragments: the project's own manifest is the developer's config, so
|
|
1315
|
+
* an invalid `pwa` block (a typo, or a removed/unknown field such as
|
|
1316
|
+
* `protocol_handlers`) FAILS the build instead of being silently skipped — a
|
|
1317
|
+
* silently-dropped field would be far harder to notice.
|
|
1318
|
+
*/
|
|
1319
|
+
function collectProjectPwaContribution(options) {
|
|
1320
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
1321
|
+
for (const rel of ["powerhouse.manifest.json", "dist/powerhouse.manifest.json"]) {
|
|
1322
|
+
const manifestPath = path.resolve(projectRoot, rel);
|
|
1323
|
+
if (!fs.existsSync(manifestPath)) continue;
|
|
1324
|
+
let manifest;
|
|
1325
|
+
try {
|
|
1326
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1329
|
+
throw new Error(`Could not parse ${manifestPath}: ${message}`);
|
|
1330
|
+
}
|
|
1331
|
+
if (!isPlainObject(manifest)) return null;
|
|
1332
|
+
const label = typeof manifest.name === "string" && manifest.name ? manifest.name : "project manifest";
|
|
1333
|
+
const withCategory = withInferredCategory(manifest.pwa === void 0 ? {} : parsePwaConfigStrict(manifest.pwa, `pwa in ${manifestPath}`), manifest.category);
|
|
1334
|
+
if (Object.keys(withCategory).length === 0) return null;
|
|
1335
|
+
return {
|
|
1336
|
+
source: label,
|
|
1337
|
+
config: withCategory
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
/** Strictly parse a PWA config block, throwing a build-failing error (naming
|
|
1343
|
+
* the offending field) when it is invalid. Used for the developer's own config
|
|
1344
|
+
* (`connect.pwa` and the project manifest's `pwa`), where a silent drop would
|
|
1345
|
+
* be a footgun — unlike third-party package fragments, which warn + skip. */
|
|
1346
|
+
function parsePwaConfigStrict(config, description) {
|
|
1347
|
+
const parsed = PwaConfigSchema.safeParse(config);
|
|
1348
|
+
if (!parsed.success) throw new Error(`Invalid ${description}: ${formatZodIssues(parsed.error)}. Fix it or remove the field.`);
|
|
1349
|
+
return parsed.data;
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Validate the project's `connect.pwa` block. Unlike package fragments
|
|
1353
|
+
* (third-party, warn + skip), the user's own config fails the build: a typo
|
|
1354
|
+
* that silently dropped offline coverage would be far harder to notice.
|
|
1355
|
+
*/
|
|
1356
|
+
function validateProjectPwaConfig(config, configPath) {
|
|
1357
|
+
return parsePwaConfigStrict(config, `connect.pwa in ${configPath}`);
|
|
1358
|
+
}
|
|
1359
|
+
//#endregion
|
|
1133
1360
|
//#region connect-utils/vite-plugins/pwa-icons.ts
|
|
1134
|
-
const PWA_ICONS = [
|
|
1361
|
+
const PWA_ICONS = [
|
|
1362
|
+
"pwa-192x192.png",
|
|
1363
|
+
"pwa-512x512.png",
|
|
1364
|
+
"pwa-512x512-maskable.png",
|
|
1365
|
+
"document-icon-192x192.png",
|
|
1366
|
+
"document-icon-512x512.png"
|
|
1367
|
+
];
|
|
1135
1368
|
function connectPwaIconsPlugin() {
|
|
1136
1369
|
return {
|
|
1137
1370
|
name: "copy-connect-pwa-icons",
|
|
@@ -1150,23 +1383,158 @@ function connectPwaIconsPlugin() {
|
|
|
1150
1383
|
};
|
|
1151
1384
|
}
|
|
1152
1385
|
//#endregion
|
|
1386
|
+
//#region connect-utils/vite-plugins/pwa-overrides.ts
|
|
1387
|
+
/**
|
|
1388
|
+
* Lay an effective PWA fragment over the plugin's hardcoded manifest + precache
|
|
1389
|
+
* base. The manifest is merged by the shared `mergeManifest` with
|
|
1390
|
+
* `fragment-wins` scalars (the build-time effective config is the authority);
|
|
1391
|
+
* icons and file handlers concatenate after the base set (contributed handlers
|
|
1392
|
+
* get Connect's fixed action injected — the open route is not configurable);
|
|
1393
|
+
* globs union; the size ceiling takes the max.
|
|
1394
|
+
*
|
|
1395
|
+
* The serialisable `runtimeCaching` / `navigateFallbackDenylist` overrides are
|
|
1396
|
+
* NOT handled here — they are passed straight to the service worker (which
|
|
1397
|
+
* registers them after its built-in rules), because injectManifest has no
|
|
1398
|
+
* declarative runtimeCaching and Workbox is first-match-wins, so an override
|
|
1399
|
+
* can only be appended after the built-ins (intentional for v1).
|
|
1400
|
+
*/
|
|
1401
|
+
function applyPwaOverrides(base, override) {
|
|
1402
|
+
return {
|
|
1403
|
+
manifest: mergeManifest(base.manifest, override.manifest, { scalarPolicy: "fragment-wins" }),
|
|
1404
|
+
precache: {
|
|
1405
|
+
globPatterns: override.globPatterns?.length ? unionStrings(base.precache.globPatterns, override.globPatterns) : base.precache.globPatterns,
|
|
1406
|
+
globIgnores: override.globIgnores?.length ? unionStrings(base.precache.globIgnores, override.globIgnores) : base.precache.globIgnores,
|
|
1407
|
+
maximumFileSizeToCacheInBytes: typeof override.maximumFileSizeToCacheInBytes === "number" ? Math.max(base.precache.maximumFileSizeToCacheInBytes, override.maximumFileSizeToCacheInBytes) : base.precache.maximumFileSizeToCacheInBytes
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
//#endregion
|
|
1153
1412
|
//#region connect-utils/vite-plugins/pwa.ts
|
|
1154
1413
|
/**
|
|
1414
|
+
* Connect's hardcoded PWA manifest. The base layer of the override ladder —
|
|
1415
|
+
* package `pwa` fragments and the project's `connect.pwa` block are laid on
|
|
1416
|
+
* top of this by `applyPwaOverrides`.
|
|
1417
|
+
*/
|
|
1418
|
+
const BASE_MANIFEST = {
|
|
1419
|
+
name: "Powerhouse Connect",
|
|
1420
|
+
short_name: "Connect",
|
|
1421
|
+
description: "A navigation, collaboration and reporting tool for decentralised and open organisations.",
|
|
1422
|
+
theme_color: "#ffffff",
|
|
1423
|
+
background_color: "#ffffff",
|
|
1424
|
+
display: "standalone",
|
|
1425
|
+
start_url: ".",
|
|
1426
|
+
scope: ".",
|
|
1427
|
+
icons: [
|
|
1428
|
+
{
|
|
1429
|
+
src: "pwa-192x192.png",
|
|
1430
|
+
sizes: "192x192",
|
|
1431
|
+
type: "image/png"
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
src: "pwa-512x512.png",
|
|
1435
|
+
sizes: "512x512",
|
|
1436
|
+
type: "image/png"
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
src: "pwa-512x512-maskable.png",
|
|
1440
|
+
sizes: "512x512",
|
|
1441
|
+
type: "image/png",
|
|
1442
|
+
purpose: "maskable"
|
|
1443
|
+
}
|
|
1444
|
+
],
|
|
1445
|
+
file_handlers: [{
|
|
1446
|
+
action: PWA_FILE_HANDLER_ACTION,
|
|
1447
|
+
accept: {
|
|
1448
|
+
"application/vnd.powerhouse.document+zip": [".phd"],
|
|
1449
|
+
"application/vnd.powerhouse.document-model+zip": [".phdm"]
|
|
1450
|
+
},
|
|
1451
|
+
icons: [{
|
|
1452
|
+
src: "document-icon-192x192.png",
|
|
1453
|
+
sizes: "192x192",
|
|
1454
|
+
type: "image/png"
|
|
1455
|
+
}, {
|
|
1456
|
+
src: "document-icon-512x512.png",
|
|
1457
|
+
sizes: "512x512",
|
|
1458
|
+
type: "image/png"
|
|
1459
|
+
}]
|
|
1460
|
+
}],
|
|
1461
|
+
launch_handler: { client_mode: "focus-existing" }
|
|
1462
|
+
};
|
|
1463
|
+
/**
|
|
1464
|
+
* Connect's hardcoded precache config — the base layer for the `injectManifest`
|
|
1465
|
+
* precache. Overridable additively (extra globs) or by raising the size ceiling
|
|
1466
|
+
* via package/project `pwa` config. The rest of the old Workbox config
|
|
1467
|
+
* (runtime-caching rules with their function urlPatterns, the navigation
|
|
1468
|
+
* fallback, clientsClaim/skipWaiting/cleanupOutdatedCaches) now lives as
|
|
1469
|
+
* imperative code in the hand-written service worker
|
|
1470
|
+
* (../service-worker/service-worker.ts), because `injectManifest` has no
|
|
1471
|
+
* declarative runtimeCaching option.
|
|
1472
|
+
*/
|
|
1473
|
+
const BASE_PRECACHE = {
|
|
1474
|
+
maximumFileSizeToCacheInBytes: 16 * 1024 * 1024,
|
|
1475
|
+
globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}"],
|
|
1476
|
+
globIgnores: [
|
|
1477
|
+
"**/powerhouse.config.json",
|
|
1478
|
+
"**/*.map",
|
|
1479
|
+
"index.html"
|
|
1480
|
+
]
|
|
1481
|
+
};
|
|
1482
|
+
const SW_FILENAME = "service-worker.ts";
|
|
1483
|
+
/**
|
|
1484
|
+
* Absolute directory holding the hand-written service-worker source. Resolved
|
|
1485
|
+
* relative to THIS module so it works from source
|
|
1486
|
+
* (connect-utils/vite-plugins → ../service-worker) and from the bundled dist
|
|
1487
|
+
* (dist/index.mjs → ./service-worker, where tsdown copies it). vite-plugin-pwa
|
|
1488
|
+
* resolves `swSrc = path.resolve(root, srcDir, filename)`, and `path.resolve`
|
|
1489
|
+
* ignores `root` when `srcDir` is absolute — so the SW ships with
|
|
1490
|
+
* builder-tools instead of every project needing its own copy.
|
|
1491
|
+
*/
|
|
1492
|
+
function resolveServiceWorkerDir() {
|
|
1493
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
1494
|
+
const candidates = [resolve(here, "../service-worker"), resolve(here, "service-worker")];
|
|
1495
|
+
return candidates.find((dir) => existsSync(join(dir, SW_FILENAME))) ?? candidates[0];
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Virtual module that feeds the hand-written SW its build-time data. Passed
|
|
1499
|
+
* into vite-plugin-pwa's SEPARATE injectManifest build via
|
|
1500
|
+
* `injectManifest.buildPlugins.vite`: that build runs with `configFile: false`,
|
|
1501
|
+
* so the app's own plugins aren't present, but buildPlugins (and `define`) are.
|
|
1502
|
+
*/
|
|
1503
|
+
function phSwConfigPlugin(data) {
|
|
1504
|
+
const virtualId = "virtual:ph-sw-config";
|
|
1505
|
+
const resolvedId = `\0${virtualId}`;
|
|
1506
|
+
return {
|
|
1507
|
+
name: "ph-sw-config",
|
|
1508
|
+
resolveId(id) {
|
|
1509
|
+
if (id === virtualId) return resolvedId;
|
|
1510
|
+
},
|
|
1511
|
+
load(id) {
|
|
1512
|
+
if (id !== resolvedId) return;
|
|
1513
|
+
return Object.entries(data).map(([key, value]) => `export const ${key} = ${JSON.stringify(value)};`).join("\n");
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1155
1518
|
* Service-worker / PWA support for Connect, gated by `connect.app.offline`.
|
|
1156
1519
|
*
|
|
1157
|
-
* When enabled (the default), Workbox `
|
|
1158
|
-
*
|
|
1159
|
-
*
|
|
1160
|
-
*
|
|
1161
|
-
*
|
|
1162
|
-
*
|
|
1520
|
+
* When enabled (the default), Workbox `injectManifest` bundles Connect's
|
|
1521
|
+
* hand-written service worker (../service-worker/service-worker.ts), which
|
|
1522
|
+
* precaches the built app shell so Connect loads with no network, runtime-
|
|
1523
|
+
* caches the Google-hosted Inter font + the registry CDN + the runtime config,
|
|
1524
|
+
* AND serves a dynamic web-app manifest so packages installed AT RUNTIME can
|
|
1525
|
+
* extend it (their fragments are mirrored into IndexedDB by the SPA; the base
|
|
1526
|
+
* the SW merges onto is embedded here at build time). The manifest and precache
|
|
1527
|
+
* config start from the BASE_* defaults above and are extended by `pwa` — the
|
|
1528
|
+
* effective, already-merged overrides from build-time packages and the
|
|
1529
|
+
* project's `connect.pwa` block (see mergePwaConfig). Registration is left to
|
|
1530
|
+
* the Connect SPA (`serviceWorkerManager`, `injectRegister: null`).
|
|
1163
1531
|
*
|
|
1164
1532
|
* When disabled, a self-destroying worker is emitted at the same URL so any
|
|
1165
1533
|
* worker a previous offline-enabled build installed unregisters itself and
|
|
1166
1534
|
* clears its caches on the browser's next service-worker update check.
|
|
1167
1535
|
*/
|
|
1168
1536
|
function connectPwaPlugins(options) {
|
|
1169
|
-
const { offlineEnabled } = options;
|
|
1537
|
+
const { offlineEnabled, pwa } = options;
|
|
1170
1538
|
if (!offlineEnabled) return [VitePWA({
|
|
1171
1539
|
selfDestroying: true,
|
|
1172
1540
|
strategies: "generateSW",
|
|
@@ -1174,102 +1542,29 @@ function connectPwaPlugins(options) {
|
|
|
1174
1542
|
filename: "service-worker.js",
|
|
1175
1543
|
devOptions: { enabled: false }
|
|
1176
1544
|
})];
|
|
1545
|
+
const { manifest, precache } = applyPwaOverrides({
|
|
1546
|
+
manifest: BASE_MANIFEST,
|
|
1547
|
+
precache: BASE_PRECACHE
|
|
1548
|
+
}, pwa ?? {});
|
|
1549
|
+
const swConfig = {
|
|
1550
|
+
EMBEDDED_BASE_MANIFEST: manifest,
|
|
1551
|
+
EXTRA_RUNTIME_CACHING: pwa?.runtimeCaching ?? [],
|
|
1552
|
+
NAVIGATE_FALLBACK_DENYLIST_EXTRA: pwa?.navigateFallbackDenylist ?? []
|
|
1553
|
+
};
|
|
1177
1554
|
return [connectPwaIconsPlugin(), VitePWA({
|
|
1178
|
-
strategies: "
|
|
1555
|
+
strategies: "injectManifest",
|
|
1556
|
+
srcDir: resolveServiceWorkerDir(),
|
|
1557
|
+
filename: SW_FILENAME,
|
|
1179
1558
|
registerType: "prompt",
|
|
1180
1559
|
injectRegister: null,
|
|
1181
|
-
filename: "service-worker.js",
|
|
1182
1560
|
devOptions: { enabled: false },
|
|
1183
1561
|
includeManifestIcons: false,
|
|
1184
|
-
manifest
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
display: "standalone",
|
|
1191
|
-
start_url: ".",
|
|
1192
|
-
scope: ".",
|
|
1193
|
-
icons: [
|
|
1194
|
-
{
|
|
1195
|
-
src: "pwa-192x192.png",
|
|
1196
|
-
sizes: "192x192",
|
|
1197
|
-
type: "image/png"
|
|
1198
|
-
},
|
|
1199
|
-
{
|
|
1200
|
-
src: "pwa-512x512.png",
|
|
1201
|
-
sizes: "512x512",
|
|
1202
|
-
type: "image/png"
|
|
1203
|
-
},
|
|
1204
|
-
{
|
|
1205
|
-
src: "pwa-512x512.png",
|
|
1206
|
-
sizes: "512x512",
|
|
1207
|
-
type: "image/png",
|
|
1208
|
-
purpose: "maskable"
|
|
1209
|
-
}
|
|
1210
|
-
]
|
|
1211
|
-
},
|
|
1212
|
-
workbox: {
|
|
1213
|
-
clientsClaim: true,
|
|
1214
|
-
skipWaiting: false,
|
|
1215
|
-
cleanupOutdatedCaches: true,
|
|
1216
|
-
maximumFileSizeToCacheInBytes: 16 * 1024 * 1024,
|
|
1217
|
-
globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}"],
|
|
1218
|
-
globIgnores: ["**/powerhouse.config.json", "**/*.map"],
|
|
1219
|
-
navigateFallback: "index.html",
|
|
1220
|
-
navigateFallbackDenylist: [
|
|
1221
|
-
/\/powerhouse\.config\.json$/,
|
|
1222
|
-
/^\/health$/,
|
|
1223
|
-
/\/__/
|
|
1224
|
-
],
|
|
1225
|
-
runtimeCaching: [
|
|
1226
|
-
{
|
|
1227
|
-
urlPattern: ({ url }) => url.origin === "https://fonts.googleapis.com",
|
|
1228
|
-
handler: "StaleWhileRevalidate",
|
|
1229
|
-
options: { cacheName: "google-fonts-stylesheets" }
|
|
1230
|
-
},
|
|
1231
|
-
{
|
|
1232
|
-
urlPattern: ({ url }) => url.origin === "https://fonts.gstatic.com",
|
|
1233
|
-
handler: "CacheFirst",
|
|
1234
|
-
options: {
|
|
1235
|
-
cacheName: "google-fonts-webfonts",
|
|
1236
|
-
expiration: {
|
|
1237
|
-
maxEntries: 30,
|
|
1238
|
-
maxAgeSeconds: 3600 * 24 * 365
|
|
1239
|
-
},
|
|
1240
|
-
cacheableResponse: { statuses: [0, 200] }
|
|
1241
|
-
}
|
|
1242
|
-
},
|
|
1243
|
-
{
|
|
1244
|
-
urlPattern: ({ url }) => url.pathname.includes("/-/cdn/") && (url.pathname.endsWith("/browser/index.js") || url.pathname.endsWith("/style.css") || url.pathname.endsWith("/package.json")),
|
|
1245
|
-
handler: "StaleWhileRevalidate",
|
|
1246
|
-
options: {
|
|
1247
|
-
cacheName: "ph-package-cdn-entry",
|
|
1248
|
-
expiration: {
|
|
1249
|
-
maxEntries: 60,
|
|
1250
|
-
maxAgeSeconds: 3600 * 24 * 30
|
|
1251
|
-
},
|
|
1252
|
-
cacheableResponse: { statuses: [0, 200] }
|
|
1253
|
-
}
|
|
1254
|
-
},
|
|
1255
|
-
{
|
|
1256
|
-
urlPattern: ({ url }) => url.pathname.includes("/-/cdn/"),
|
|
1257
|
-
handler: "CacheFirst",
|
|
1258
|
-
options: {
|
|
1259
|
-
cacheName: "ph-package-cdn",
|
|
1260
|
-
expiration: {
|
|
1261
|
-
maxEntries: 200,
|
|
1262
|
-
maxAgeSeconds: 3600 * 24 * 30
|
|
1263
|
-
},
|
|
1264
|
-
cacheableResponse: { statuses: [0, 200] }
|
|
1265
|
-
}
|
|
1266
|
-
},
|
|
1267
|
-
{
|
|
1268
|
-
urlPattern: ({ url }) => url.pathname.endsWith("/powerhouse.config.json"),
|
|
1269
|
-
handler: "NetworkFirst",
|
|
1270
|
-
options: { cacheName: "ph-runtime-config" }
|
|
1271
|
-
}
|
|
1272
|
-
]
|
|
1562
|
+
manifest,
|
|
1563
|
+
injectManifest: {
|
|
1564
|
+
globPatterns: precache.globPatterns,
|
|
1565
|
+
globIgnores: [...precache.globIgnores, "**/manifest.webmanifest"],
|
|
1566
|
+
maximumFileSizeToCacheInBytes: precache.maximumFileSizeToCacheInBytes,
|
|
1567
|
+
buildPlugins: { vite: [phSwConfigPlugin(swConfig)] }
|
|
1273
1568
|
}
|
|
1274
1569
|
})];
|
|
1275
1570
|
}
|
|
@@ -1390,7 +1685,9 @@ import { pathToFileURL } from 'node:url';
|
|
|
1390
1685
|
const [dirname, outDir, entriesJSON, nodeEnv] = process.argv.slice(2);
|
|
1391
1686
|
const entries = JSON.parse(entriesJSON);
|
|
1392
1687
|
const reqProj = createRequire(join(dirname, 'noop.js'));
|
|
1393
|
-
|
|
1688
|
+
// pathToFileURL: require.resolve returns an absolute path, and on Windows
|
|
1689
|
+
// import('D:\\...') parses "D:" as a URL scheme (ERR_UNSUPPORTED_ESM_URL_SCHEME).
|
|
1690
|
+
const { build } = await import(pathToFileURL(reqProj.resolve('vite')).href);
|
|
1394
1691
|
const srcDir = join(outDir, '.entries');
|
|
1395
1692
|
mkdirSync(srcDir, { recursive: true });
|
|
1396
1693
|
const entryName = (spec) => spec.replace(/[^\\w]+/g, '_');
|
|
@@ -1619,6 +1916,14 @@ function getConnectBaseViteConfig(options) {
|
|
|
1619
1916
|
const project = env.PH_SENTRY_PROJECT;
|
|
1620
1917
|
const release = process.env.WORKSPACE_VERSION ?? process.env.npm_package_version ?? env.PH_CONNECT_VERSION;
|
|
1621
1918
|
const uploadSentrySourcemaps = authToken && org && project;
|
|
1919
|
+
const buildHash = buildHashFromBuildOptions({
|
|
1920
|
+
dirname: options.dirname,
|
|
1921
|
+
phConfig,
|
|
1922
|
+
packages: phPackages,
|
|
1923
|
+
connectBasePath,
|
|
1924
|
+
offlineEnabled,
|
|
1925
|
+
packageRegistryUrl: phPackageRegistryUrl
|
|
1926
|
+
});
|
|
1622
1927
|
const connectHtmlTags = getConnectHtmlTags({ registryUrl: phPackageRegistryUrl });
|
|
1623
1928
|
const devImportmapTag = mode === "development" ? [{
|
|
1624
1929
|
tag: "script",
|
|
@@ -1650,6 +1955,24 @@ function getConnectBaseViteConfig(options) {
|
|
|
1650
1955
|
warnings: ["@import must precede all other statements (besides @charset or empty @layer)"],
|
|
1651
1956
|
errors: ["Unterminated string literal"]
|
|
1652
1957
|
} });
|
|
1958
|
+
const projectPwa = validateProjectPwaConfig(deepMerge(phConfig.connect?.pwa ?? {}, options.cliConnectOverride?.pwa ?? {}), phConfigPath);
|
|
1959
|
+
const pwaWarn = (msg) => (customLogger ?? console).warn(msg);
|
|
1960
|
+
const pwaPackagesForFragments = mode === "production" ? phPackages : phPackages.filter((p) => p.provider === "local");
|
|
1961
|
+
const pwaPlugins = (async () => {
|
|
1962
|
+
if (!offlineEnabled) return connectPwaPlugins({ offlineEnabled });
|
|
1963
|
+
const contributions = await collectPackagePwaContributions({
|
|
1964
|
+
packages: pwaPackagesForFragments,
|
|
1965
|
+
projectRoot: options.dirname,
|
|
1966
|
+
registryUrl: phPackageRegistryUrl,
|
|
1967
|
+
onWarn: pwaWarn
|
|
1968
|
+
});
|
|
1969
|
+
const projectContribution = collectProjectPwaContribution({ projectRoot: options.dirname });
|
|
1970
|
+
if (projectContribution) contributions.push(projectContribution);
|
|
1971
|
+
return connectPwaPlugins({
|
|
1972
|
+
offlineEnabled,
|
|
1973
|
+
pwa: mergePwaConfig(contributions, projectPwa, pwaWarn)
|
|
1974
|
+
});
|
|
1975
|
+
})();
|
|
1653
1976
|
const reactExternal = [
|
|
1654
1977
|
"react",
|
|
1655
1978
|
"react-dom",
|
|
@@ -1679,7 +2002,10 @@ function getConnectBaseViteConfig(options) {
|
|
|
1679
2002
|
dedupe: ["react", "react-dom"],
|
|
1680
2003
|
tsconfigPaths: true
|
|
1681
2004
|
},
|
|
1682
|
-
define: {
|
|
2005
|
+
define: {
|
|
2006
|
+
PH_CONNECT_SENTRY_RELEASE: JSON.stringify(release || "unknown"),
|
|
2007
|
+
PH_CONNECT_BUILD_HASH: JSON.stringify(buildHash)
|
|
2008
|
+
},
|
|
1683
2009
|
customLogger,
|
|
1684
2010
|
envPrefix: ["PH_CONNECT_"],
|
|
1685
2011
|
optimizeDeps: {
|
|
@@ -1713,8 +2039,9 @@ function getConnectBaseViteConfig(options) {
|
|
|
1713
2039
|
}),
|
|
1714
2040
|
connectFaviconPlugin({ faviconPath: options.favicon }),
|
|
1715
2041
|
connectThemeBootPlugin(),
|
|
2042
|
+
connectBuildHashPlugin(buildHash),
|
|
1716
2043
|
...options.dynamicBase ? [connectDynamicBasePlugin()] : [],
|
|
1717
|
-
|
|
2044
|
+
pwaPlugins
|
|
1718
2045
|
],
|
|
1719
2046
|
worker: {
|
|
1720
2047
|
format: "es",
|
|
@@ -1724,6 +2051,6 @@ function getConnectBaseViteConfig(options) {
|
|
|
1724
2051
|
};
|
|
1725
2052
|
}
|
|
1726
2053
|
//#endregion
|
|
1727
|
-
export { DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, THEME_BOOT_MARKER, VENDOR_EXTERNAL, VENDOR_URL_PREFIX, appendToHtmlHead, backupIndexHtml, connectDynamicBasePlugin, connectThemeBootPlugin, copyConnect, ensureNodeVersion, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
|
|
2054
|
+
export { BUILD_HASH_FILE, DEFAULT_CONNECT_OUTDIR, DEFAULT_VENDOR_INCLUDE, DYNAMIC_BASE_PLACEHOLDER, EXTERNAL_PACKAGES_IMPORT, IMPORT_SCRIPT_FILE, LOCAL_PACKAGE_ID, PH_DIR_NAME, RUNTIME_CONFIG_SCHEMA_ID, RUNTIME_CONFIG_SCHEMA_URL, THEME_BOOT_MARKER, VENDOR_EXTERNAL, VENDOR_URL_PREFIX, appendToHtmlHead, backupIndexHtml, buildHashFromBuildOptions, computeBuildHash, connectBuildHashPlugin, connectDynamicBasePlugin, connectThemeBootPlugin, copyConnect, ensureNodeVersion, escapeForRegExp, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
|
|
1728
2055
|
|
|
1729
2056
|
//# sourceMappingURL=index.mjs.map
|