@powerhousedao/builder-tools 6.2.2-dev.7 → 6.2.2-dev.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -7,12 +7,14 @@ 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";
@@ -465,7 +467,9 @@ const include = JSON.parse(includeJSON);
465
467
  const external = JSON.parse(externalJSON ?? '[]');
466
468
  const externalSet = new Set(external);
467
469
  const reqProj = createRequire(join(dirname, 'noop.js'));
468
- const { build, esmExternalRequirePlugin } = await import(reqProj.resolve('vite'));
470
+ // pathToFileURL: require.resolve returns an absolute path, and on Windows
471
+ // import('D:\\...') parses "D:" as a URL scheme (ERR_UNSUPPORTED_ESM_URL_SCHEME).
472
+ const { build, esmExternalRequirePlugin } = await import(pathToFileURL(reqProj.resolve('vite')).href);
469
473
  // Load the dynamic-base plugin from builder-tools' own built bundle (passed as
470
474
  // an absolute path) — it isn't resolvable as a bare specifier from the worker.
471
475
  const { connectDynamicBasePlugin, DYNAMIC_BASE_PLACEHOLDER } = await import(pathToFileURL(selfModulePath));
@@ -1130,8 +1134,170 @@ function phConfigPlugin(options) {
1130
1134
  };
1131
1135
  }
1132
1136
  //#endregion
1137
+ //#region connect-utils/vite-plugins/pwa-packages.ts
1138
+ const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
1139
+ function isPlainObject(value) {
1140
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1141
+ }
1142
+ function formatZodIssues(error) {
1143
+ return error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
1144
+ }
1145
+ /** Turn a parsed manifest JSON into a contribution: its `pwa` fragment (a
1146
+ * malformed one → warn + skip that fragment, not the whole contribution) plus a
1147
+ * `categories` entry derived from the manifest's `category` field. Returns null
1148
+ * only when the manifest yields neither — no `pwa` and no `category`. */
1149
+ function toPwaContribution(manifest, fallbackLabel, onWarn) {
1150
+ if (!isPlainObject(manifest)) return null;
1151
+ const label = typeof manifest.name === "string" && manifest.name ? manifest.name : fallbackLabel;
1152
+ let config = {};
1153
+ if (manifest.pwa !== void 0) if (!isPlainObject(manifest.pwa)) onWarn(`PWA config: ${label} declares a non-object 'pwa' field in its manifest; ignored.`);
1154
+ else {
1155
+ const parsed = PwaConfigSchema.safeParse(manifest.pwa);
1156
+ if (!parsed.success) onWarn(`PWA config: ${label}'s pwa fragment is invalid; ignored. ${formatZodIssues(parsed.error)}`);
1157
+ else config = parsed.data;
1158
+ }
1159
+ config = withInferredCategory(config, manifest.category);
1160
+ if (Object.keys(config).length === 0) return null;
1161
+ return {
1162
+ source: label,
1163
+ config
1164
+ };
1165
+ }
1166
+ /** Read the first parseable manifest among `candidates` (relative to `dir`)
1167
+ * and extract its pwa contribution. Candidates that resolve outside `dir`
1168
+ * (a hostile `./manifest` export) are warned about and skipped. */
1169
+ function readPwaFragmentFromDir(dir, candidates, fallbackLabel, onWarn) {
1170
+ for (const rel of candidates) {
1171
+ const manifestPath = path.resolve(dir, rel);
1172
+ if (path.relative(dir, manifestPath).startsWith("..")) {
1173
+ onWarn(`PWA config: ${fallbackLabel} declares a manifest path outside its package directory (${rel}); ignored.`);
1174
+ continue;
1175
+ }
1176
+ if (!fs.existsSync(manifestPath)) continue;
1177
+ try {
1178
+ return toPwaContribution(JSON.parse(fs.readFileSync(manifestPath, "utf-8")), fallbackLabel, onWarn);
1179
+ } catch {
1180
+ onWarn(`PWA config: could not parse ${fallbackLabel}'s manifest at ${rel}; ignored.`);
1181
+ return null;
1182
+ }
1183
+ }
1184
+ return null;
1185
+ }
1186
+ function readLocalPackagePwaFragment(projectRoot, name, onWarn) {
1187
+ const pkgDir = path.join(projectRoot, "node_modules", name);
1188
+ let manifestRel;
1189
+ try {
1190
+ const exported = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf-8")).exports?.["./manifest"];
1191
+ if (typeof exported === "string") manifestRel = exported;
1192
+ } catch {}
1193
+ return readPwaFragmentFromDir(pkgDir, [
1194
+ manifestRel,
1195
+ "dist/powerhouse.manifest.json",
1196
+ "powerhouse.manifest.json"
1197
+ ].filter((rel) => typeof rel === "string"), name, onWarn);
1198
+ }
1199
+ async function fetchRegistryPwaFragment(cdnUrl, pkg, onWarn, fetchImpl) {
1200
+ const spec = pkg.version ? `${pkg.packageName}@${pkg.version}` : pkg.packageName;
1201
+ const url = `${cdnUrl}/${spec}/powerhouse.manifest.json`;
1202
+ try {
1203
+ const response = await fetchImpl(url, { signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS) });
1204
+ if (response.status === 404) return null;
1205
+ if (!response.ok) {
1206
+ onWarn(`PWA config: registry returned ${response.status} for ${spec}'s manifest; its pwa fragment (if any) is skipped.`);
1207
+ return null;
1208
+ }
1209
+ return toPwaContribution(await response.json(), pkg.packageName, onWarn);
1210
+ } catch (error) {
1211
+ 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.`);
1212
+ return null;
1213
+ }
1214
+ }
1215
+ /**
1216
+ * Read the `pwa` fragment of each package in `packages`, in the given order
1217
+ * (which becomes their merge precedence — later packages win scalar
1218
+ * conflicts). `provider: "local"` packages are read from node_modules;
1219
+ * everything else is fetched from the registry CDN (skipped with a warning
1220
+ * when `registryUrl` is missing or the registry is unreachable). Packages
1221
+ * with no `pwa` fragment are simply absent from the result.
1222
+ */
1223
+ async function collectPackagePwaContributions(options) {
1224
+ const projectRoot = options.projectRoot ?? process.cwd();
1225
+ const onWarn = options.onWarn ?? (() => {});
1226
+ const fetchImpl = options.fetchImpl ?? fetch;
1227
+ const cdnUrl = options.registryUrl ? toCdnUrl(options.registryUrl) : null;
1228
+ return (await Promise.all(options.packages.map(async (pkg) => {
1229
+ if (pkg.provider === "local") return readLocalPackagePwaFragment(projectRoot, pkg.packageName, onWarn);
1230
+ if (!cdnUrl) {
1231
+ onWarn(`PWA config: no packageRegistryUrl configured; cannot read ${pkg.packageName}'s pwa fragment (if any) from the registry.`);
1232
+ return null;
1233
+ }
1234
+ return fetchRegistryPwaFragment(cdnUrl, pkg, onWarn, fetchImpl);
1235
+ }))).filter((contribution) => contribution !== null);
1236
+ }
1237
+ /**
1238
+ * Read the pwa fragment from the project's OWN manifest
1239
+ * (`powerhouse.manifest.json` under the project root, falling back to the
1240
+ * `dist/` copy the project build emits). The root file comes first — it is
1241
+ * the source the dist copy is made from, and a stale dist left by an older
1242
+ * build must not shadow it. Returns null silently when no manifest exists —
1243
+ * not every project ships one — or when it carries neither a `pwa` block nor a
1244
+ * `category` to derive `categories` from.
1245
+ *
1246
+ * The `pwa` block is parsed STRICTLY, like `connect.pwa` and unlike third-party
1247
+ * package fragments: the project's own manifest is the developer's config, so
1248
+ * an invalid `pwa` block (a typo, or a removed/unknown field such as
1249
+ * `protocol_handlers`) FAILS the build instead of being silently skipped — a
1250
+ * silently-dropped field would be far harder to notice.
1251
+ */
1252
+ function collectProjectPwaContribution(options) {
1253
+ const projectRoot = options.projectRoot ?? process.cwd();
1254
+ for (const rel of ["powerhouse.manifest.json", "dist/powerhouse.manifest.json"]) {
1255
+ const manifestPath = path.resolve(projectRoot, rel);
1256
+ if (!fs.existsSync(manifestPath)) continue;
1257
+ let manifest;
1258
+ try {
1259
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
1260
+ } catch (error) {
1261
+ const message = error instanceof Error ? error.message : String(error);
1262
+ throw new Error(`Could not parse ${manifestPath}: ${message}`);
1263
+ }
1264
+ if (!isPlainObject(manifest)) return null;
1265
+ const label = typeof manifest.name === "string" && manifest.name ? manifest.name : "project manifest";
1266
+ const withCategory = withInferredCategory(manifest.pwa === void 0 ? {} : parsePwaConfigStrict(manifest.pwa, `pwa in ${manifestPath}`), manifest.category);
1267
+ if (Object.keys(withCategory).length === 0) return null;
1268
+ return {
1269
+ source: label,
1270
+ config: withCategory
1271
+ };
1272
+ }
1273
+ return null;
1274
+ }
1275
+ /** Strictly parse a PWA config block, throwing a build-failing error (naming
1276
+ * the offending field) when it is invalid. Used for the developer's own config
1277
+ * (`connect.pwa` and the project manifest's `pwa`), where a silent drop would
1278
+ * be a footgun — unlike third-party package fragments, which warn + skip. */
1279
+ function parsePwaConfigStrict(config, description) {
1280
+ const parsed = PwaConfigSchema.safeParse(config);
1281
+ if (!parsed.success) throw new Error(`Invalid ${description}: ${formatZodIssues(parsed.error)}. Fix it or remove the field.`);
1282
+ return parsed.data;
1283
+ }
1284
+ /**
1285
+ * Validate the project's `connect.pwa` block. Unlike package fragments
1286
+ * (third-party, warn + skip), the user's own config fails the build: a typo
1287
+ * that silently dropped offline coverage would be far harder to notice.
1288
+ */
1289
+ function validateProjectPwaConfig(config, configPath) {
1290
+ return parsePwaConfigStrict(config, `connect.pwa in ${configPath}`);
1291
+ }
1292
+ //#endregion
1133
1293
  //#region connect-utils/vite-plugins/pwa-icons.ts
1134
- const PWA_ICONS = ["pwa-192x192.png", "pwa-512x512.png"];
1294
+ const PWA_ICONS = [
1295
+ "pwa-192x192.png",
1296
+ "pwa-512x512.png",
1297
+ "pwa-512x512-maskable.png",
1298
+ "document-icon-192x192.png",
1299
+ "document-icon-512x512.png"
1300
+ ];
1135
1301
  function connectPwaIconsPlugin() {
1136
1302
  return {
1137
1303
  name: "copy-connect-pwa-icons",
@@ -1150,23 +1316,154 @@ function connectPwaIconsPlugin() {
1150
1316
  };
1151
1317
  }
1152
1318
  //#endregion
1319
+ //#region connect-utils/vite-plugins/pwa-overrides.ts
1320
+ /**
1321
+ * Lay an effective PWA fragment over the plugin's hardcoded manifest + precache
1322
+ * base. The manifest is merged by the shared `mergeManifest` with
1323
+ * `fragment-wins` scalars (the build-time effective config is the authority);
1324
+ * icons and file handlers concatenate after the base set (contributed handlers
1325
+ * get Connect's fixed action injected — the open route is not configurable);
1326
+ * globs union; the size ceiling takes the max.
1327
+ *
1328
+ * The serialisable `runtimeCaching` / `navigateFallbackDenylist` overrides are
1329
+ * NOT handled here — they are passed straight to the service worker (which
1330
+ * registers them after its built-in rules), because injectManifest has no
1331
+ * declarative runtimeCaching and Workbox is first-match-wins, so an override
1332
+ * can only be appended after the built-ins (intentional for v1).
1333
+ */
1334
+ function applyPwaOverrides(base, override) {
1335
+ return {
1336
+ manifest: mergeManifest(base.manifest, override.manifest, { scalarPolicy: "fragment-wins" }),
1337
+ precache: {
1338
+ globPatterns: override.globPatterns?.length ? unionStrings(base.precache.globPatterns, override.globPatterns) : base.precache.globPatterns,
1339
+ globIgnores: override.globIgnores?.length ? unionStrings(base.precache.globIgnores, override.globIgnores) : base.precache.globIgnores,
1340
+ maximumFileSizeToCacheInBytes: typeof override.maximumFileSizeToCacheInBytes === "number" ? Math.max(base.precache.maximumFileSizeToCacheInBytes, override.maximumFileSizeToCacheInBytes) : base.precache.maximumFileSizeToCacheInBytes
1341
+ }
1342
+ };
1343
+ }
1344
+ //#endregion
1153
1345
  //#region connect-utils/vite-plugins/pwa.ts
1154
1346
  /**
1347
+ * Connect's hardcoded PWA manifest. The base layer of the override ladder —
1348
+ * package `pwa` fragments and the project's `connect.pwa` block are laid on
1349
+ * top of this by `applyPwaOverrides`.
1350
+ */
1351
+ const BASE_MANIFEST = {
1352
+ name: "Powerhouse Connect",
1353
+ short_name: "Connect",
1354
+ description: "A navigation, collaboration and reporting tool for decentralised and open organisations.",
1355
+ theme_color: "#ffffff",
1356
+ background_color: "#ffffff",
1357
+ display: "standalone",
1358
+ start_url: ".",
1359
+ scope: ".",
1360
+ icons: [
1361
+ {
1362
+ src: "pwa-192x192.png",
1363
+ sizes: "192x192",
1364
+ type: "image/png"
1365
+ },
1366
+ {
1367
+ src: "pwa-512x512.png",
1368
+ sizes: "512x512",
1369
+ type: "image/png"
1370
+ },
1371
+ {
1372
+ src: "pwa-512x512-maskable.png",
1373
+ sizes: "512x512",
1374
+ type: "image/png",
1375
+ purpose: "maskable"
1376
+ }
1377
+ ],
1378
+ file_handlers: [{
1379
+ action: PWA_FILE_HANDLER_ACTION,
1380
+ accept: {
1381
+ "application/vnd.powerhouse.document+zip": [".phd"],
1382
+ "application/vnd.powerhouse.document-model+zip": [".phdm"]
1383
+ },
1384
+ icons: [{
1385
+ src: "document-icon-192x192.png",
1386
+ sizes: "192x192",
1387
+ type: "image/png"
1388
+ }, {
1389
+ src: "document-icon-512x512.png",
1390
+ sizes: "512x512",
1391
+ type: "image/png"
1392
+ }]
1393
+ }],
1394
+ launch_handler: { client_mode: "focus-existing" }
1395
+ };
1396
+ /**
1397
+ * Connect's hardcoded precache config — the base layer for the `injectManifest`
1398
+ * precache. Overridable additively (extra globs) or by raising the size ceiling
1399
+ * via package/project `pwa` config. The rest of the old Workbox config
1400
+ * (runtime-caching rules with their function urlPatterns, the navigation
1401
+ * fallback, clientsClaim/skipWaiting/cleanupOutdatedCaches) now lives as
1402
+ * imperative code in the hand-written service worker
1403
+ * (../service-worker/service-worker.ts), because `injectManifest` has no
1404
+ * declarative runtimeCaching option.
1405
+ */
1406
+ const BASE_PRECACHE = {
1407
+ maximumFileSizeToCacheInBytes: 16 * 1024 * 1024,
1408
+ globPatterns: ["**/*.{js,css,html,wasm,data,ico,png,svg,webp,woff,woff2}"],
1409
+ globIgnores: ["**/powerhouse.config.json", "**/*.map"]
1410
+ };
1411
+ const SW_FILENAME = "service-worker.ts";
1412
+ /**
1413
+ * Absolute directory holding the hand-written service-worker source. Resolved
1414
+ * relative to THIS module so it works from source
1415
+ * (connect-utils/vite-plugins → ../service-worker) and from the bundled dist
1416
+ * (dist/index.mjs → ./service-worker, where tsdown copies it). vite-plugin-pwa
1417
+ * resolves `swSrc = path.resolve(root, srcDir, filename)`, and `path.resolve`
1418
+ * ignores `root` when `srcDir` is absolute — so the SW ships with
1419
+ * builder-tools instead of every project needing its own copy.
1420
+ */
1421
+ function resolveServiceWorkerDir() {
1422
+ const here = dirname(fileURLToPath(import.meta.url));
1423
+ const candidates = [resolve(here, "../service-worker"), resolve(here, "service-worker")];
1424
+ return candidates.find((dir) => existsSync(join(dir, SW_FILENAME))) ?? candidates[0];
1425
+ }
1426
+ /**
1427
+ * Virtual module that feeds the hand-written SW its build-time data. Passed
1428
+ * into vite-plugin-pwa's SEPARATE injectManifest build via
1429
+ * `injectManifest.buildPlugins.vite`: that build runs with `configFile: false`,
1430
+ * so the app's own plugins aren't present, but buildPlugins (and `define`) are.
1431
+ */
1432
+ function phSwConfigPlugin(data) {
1433
+ const virtualId = "virtual:ph-sw-config";
1434
+ const resolvedId = `\0${virtualId}`;
1435
+ return {
1436
+ name: "ph-sw-config",
1437
+ resolveId(id) {
1438
+ if (id === virtualId) return resolvedId;
1439
+ },
1440
+ load(id) {
1441
+ if (id !== resolvedId) return;
1442
+ return Object.entries(data).map(([key, value]) => `export const ${key} = ${JSON.stringify(value)};`).join("\n");
1443
+ }
1444
+ };
1445
+ }
1446
+ /**
1155
1447
  * Service-worker / PWA support for Connect, gated by `connect.app.offline`.
1156
1448
  *
1157
- * When enabled (the default), Workbox `generateSW` precaches the built app
1158
- * shell so Connect loads with no network, and runtime-caches the Google-hosted
1159
- * Inter font + the runtime config. Registration is left to the Connect SPA
1160
- * (`serviceWorkerManager`, `injectRegister: null`) rather than the plugin's
1161
- * `virtual:pwa-register` module, so the published `@powerhousedao/connect`
1162
- * tsdown build never has to resolve that virtual import.
1449
+ * When enabled (the default), Workbox `injectManifest` bundles Connect's
1450
+ * hand-written service worker (../service-worker/service-worker.ts), which
1451
+ * precaches the built app shell so Connect loads with no network, runtime-
1452
+ * caches the Google-hosted Inter font + the registry CDN + the runtime config,
1453
+ * AND serves a dynamic web-app manifest so packages installed AT RUNTIME can
1454
+ * extend it (their fragments are mirrored into IndexedDB by the SPA; the base
1455
+ * the SW merges onto is embedded here at build time). The manifest and precache
1456
+ * config start from the BASE_* defaults above and are extended by `pwa` — the
1457
+ * effective, already-merged overrides from build-time packages and the
1458
+ * project's `connect.pwa` block (see mergePwaConfig). Registration is left to
1459
+ * the Connect SPA (`serviceWorkerManager`, `injectRegister: null`).
1163
1460
  *
1164
1461
  * When disabled, a self-destroying worker is emitted at the same URL so any
1165
1462
  * worker a previous offline-enabled build installed unregisters itself and
1166
1463
  * clears its caches on the browser's next service-worker update check.
1167
1464
  */
1168
1465
  function connectPwaPlugins(options) {
1169
- const { offlineEnabled } = options;
1466
+ const { offlineEnabled, pwa } = options;
1170
1467
  if (!offlineEnabled) return [VitePWA({
1171
1468
  selfDestroying: true,
1172
1469
  strategies: "generateSW",
@@ -1174,102 +1471,29 @@ function connectPwaPlugins(options) {
1174
1471
  filename: "service-worker.js",
1175
1472
  devOptions: { enabled: false }
1176
1473
  })];
1474
+ const { manifest, precache } = applyPwaOverrides({
1475
+ manifest: BASE_MANIFEST,
1476
+ precache: BASE_PRECACHE
1477
+ }, pwa ?? {});
1478
+ const swConfig = {
1479
+ EMBEDDED_BASE_MANIFEST: manifest,
1480
+ EXTRA_RUNTIME_CACHING: pwa?.runtimeCaching ?? [],
1481
+ NAVIGATE_FALLBACK_DENYLIST_EXTRA: pwa?.navigateFallbackDenylist ?? []
1482
+ };
1177
1483
  return [connectPwaIconsPlugin(), VitePWA({
1178
- strategies: "generateSW",
1484
+ strategies: "injectManifest",
1485
+ srcDir: resolveServiceWorkerDir(),
1486
+ filename: SW_FILENAME,
1179
1487
  registerType: "prompt",
1180
1488
  injectRegister: null,
1181
- filename: "service-worker.js",
1182
1489
  devOptions: { enabled: false },
1183
1490
  includeManifestIcons: false,
1184
- manifest: {
1185
- name: "Powerhouse Connect",
1186
- short_name: "Connect",
1187
- description: "A navigation, collaboration and reporting tool for decentralised and open organisations.",
1188
- theme_color: "#ffffff",
1189
- background_color: "#ffffff",
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
- ]
1491
+ manifest,
1492
+ injectManifest: {
1493
+ globPatterns: precache.globPatterns,
1494
+ globIgnores: [...precache.globIgnores, "**/manifest.webmanifest"],
1495
+ maximumFileSizeToCacheInBytes: precache.maximumFileSizeToCacheInBytes,
1496
+ buildPlugins: { vite: [phSwConfigPlugin(swConfig)] }
1273
1497
  }
1274
1498
  })];
1275
1499
  }
@@ -1390,7 +1614,9 @@ import { pathToFileURL } from 'node:url';
1390
1614
  const [dirname, outDir, entriesJSON, nodeEnv] = process.argv.slice(2);
1391
1615
  const entries = JSON.parse(entriesJSON);
1392
1616
  const reqProj = createRequire(join(dirname, 'noop.js'));
1393
- const { build } = await import(reqProj.resolve('vite'));
1617
+ // pathToFileURL: require.resolve returns an absolute path, and on Windows
1618
+ // import('D:\\...') parses "D:" as a URL scheme (ERR_UNSUPPORTED_ESM_URL_SCHEME).
1619
+ const { build } = await import(pathToFileURL(reqProj.resolve('vite')).href);
1394
1620
  const srcDir = join(outDir, '.entries');
1395
1621
  mkdirSync(srcDir, { recursive: true });
1396
1622
  const entryName = (spec) => spec.replace(/[^\\w]+/g, '_');
@@ -1650,6 +1876,24 @@ function getConnectBaseViteConfig(options) {
1650
1876
  warnings: ["@import must precede all other statements (besides @charset or empty @layer)"],
1651
1877
  errors: ["Unterminated string literal"]
1652
1878
  } });
1879
+ const projectPwa = validateProjectPwaConfig(deepMerge(phConfig.connect?.pwa ?? {}, options.cliConnectOverride?.pwa ?? {}), phConfigPath);
1880
+ const pwaWarn = (msg) => (customLogger ?? console).warn(msg);
1881
+ const pwaPackagesForFragments = mode === "production" ? phPackages : phPackages.filter((p) => p.provider === "local");
1882
+ const pwaPlugins = (async () => {
1883
+ if (!offlineEnabled) return connectPwaPlugins({ offlineEnabled });
1884
+ const contributions = await collectPackagePwaContributions({
1885
+ packages: pwaPackagesForFragments,
1886
+ projectRoot: options.dirname,
1887
+ registryUrl: phPackageRegistryUrl,
1888
+ onWarn: pwaWarn
1889
+ });
1890
+ const projectContribution = collectProjectPwaContribution({ projectRoot: options.dirname });
1891
+ if (projectContribution) contributions.push(projectContribution);
1892
+ return connectPwaPlugins({
1893
+ offlineEnabled,
1894
+ pwa: mergePwaConfig(contributions, projectPwa, pwaWarn)
1895
+ });
1896
+ })();
1653
1897
  const reactExternal = [
1654
1898
  "react",
1655
1899
  "react-dom",
@@ -1714,7 +1958,7 @@ function getConnectBaseViteConfig(options) {
1714
1958
  connectFaviconPlugin({ faviconPath: options.favicon }),
1715
1959
  connectThemeBootPlugin(),
1716
1960
  ...options.dynamicBase ? [connectDynamicBasePlugin()] : [],
1717
- ...connectPwaPlugins({ offlineEnabled })
1961
+ pwaPlugins
1718
1962
  ],
1719
1963
  worker: {
1720
1964
  format: "es",
@@ -1724,6 +1968,6 @@ function getConnectBaseViteConfig(options) {
1724
1968
  };
1725
1969
  }
1726
1970
  //#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 };
1971
+ 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, escapeForRegExp, getConnectBaseViteConfig, getConnectHtmlTags, makeImportScriptFromPackages, phConfigPlugin, prebuildConnectVendor, prependToHtmlHead, readJsonFile, removeBase64EnvValues, resolveConnectBundle, resolveConnectPackageJson, resolveConnectPublicDir, resolvePackage, resolveViteConfigPath, runShellScriptPlugin, runTsc, runtimeConfigSchema, stripVersionFromPackage };
1728
1972
 
1729
1973
  //# sourceMappingURL=index.mjs.map