portapack 0.3.1 → 0.3.3

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.
Files changed (74) hide show
  1. package/.eslintrc.json +67 -8
  2. package/.releaserc.js +25 -27
  3. package/CHANGELOG.md +14 -22
  4. package/LICENSE.md +21 -0
  5. package/README.md +22 -53
  6. package/commitlint.config.js +30 -34
  7. package/dist/cli/cli-entry.cjs +183 -98
  8. package/dist/cli/cli-entry.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -3
  10. package/dist/index.js +178 -97
  11. package/dist/index.js.map +1 -1
  12. package/docs/.vitepress/config.ts +38 -33
  13. package/docs/.vitepress/sidebar-generator.ts +89 -38
  14. package/docs/architecture.md +186 -0
  15. package/docs/cli.md +23 -23
  16. package/docs/code-of-conduct.md +7 -1
  17. package/docs/configuration.md +12 -11
  18. package/docs/contributing.md +6 -2
  19. package/docs/deployment.md +10 -5
  20. package/docs/development.md +8 -5
  21. package/docs/getting-started.md +13 -13
  22. package/docs/index.md +1 -1
  23. package/docs/public/android-chrome-192x192.png +0 -0
  24. package/docs/public/android-chrome-512x512.png +0 -0
  25. package/docs/public/apple-touch-icon.png +0 -0
  26. package/docs/public/favicon-16x16.png +0 -0
  27. package/docs/public/favicon-32x32.png +0 -0
  28. package/docs/public/favicon.ico +0 -0
  29. package/docs/roadmap.md +233 -0
  30. package/docs/site.webmanifest +1 -0
  31. package/docs/troubleshooting.md +12 -1
  32. package/examples/main.ts +5 -30
  33. package/examples/sample-project/script.js +1 -1
  34. package/jest.config.ts +8 -13
  35. package/nodemon.json +5 -10
  36. package/package.json +2 -5
  37. package/src/cli/cli-entry.ts +2 -2
  38. package/src/cli/cli.ts +21 -16
  39. package/src/cli/options.ts +127 -113
  40. package/src/core/bundler.ts +253 -222
  41. package/src/core/extractor.ts +632 -565
  42. package/src/core/minifier.ts +173 -162
  43. package/src/core/packer.ts +141 -137
  44. package/src/core/parser.ts +74 -73
  45. package/src/core/web-fetcher.ts +270 -258
  46. package/src/index.ts +18 -17
  47. package/src/types.ts +9 -11
  48. package/src/utils/font.ts +12 -6
  49. package/src/utils/logger.ts +110 -105
  50. package/src/utils/meta.ts +75 -76
  51. package/src/utils/mime.ts +50 -50
  52. package/src/utils/slugify.ts +33 -34
  53. package/tests/unit/cli/cli-entry.test.ts +72 -70
  54. package/tests/unit/cli/cli.test.ts +314 -278
  55. package/tests/unit/cli/options.test.ts +294 -301
  56. package/tests/unit/core/bundler.test.ts +426 -329
  57. package/tests/unit/core/extractor.test.ts +793 -549
  58. package/tests/unit/core/minifier.test.ts +374 -274
  59. package/tests/unit/core/packer.test.ts +298 -264
  60. package/tests/unit/core/parser.test.ts +538 -150
  61. package/tests/unit/core/web-fetcher.test.ts +389 -359
  62. package/tests/unit/index.test.ts +238 -197
  63. package/tests/unit/utils/font.test.ts +26 -21
  64. package/tests/unit/utils/logger.test.ts +267 -260
  65. package/tests/unit/utils/meta.test.ts +29 -28
  66. package/tests/unit/utils/mime.test.ts +73 -74
  67. package/tests/unit/utils/slugify.test.ts +14 -12
  68. package/tsconfig.build.json +9 -10
  69. package/tsconfig.jest.json +1 -1
  70. package/tsconfig.json +2 -2
  71. package/tsup.config.ts +8 -9
  72. package/typedoc.json +5 -9
  73. /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
  74. /package/docs/{portapack.jpg → public/portapack.jpg} +0 -0
package/dist/index.d.ts CHANGED
@@ -204,7 +204,6 @@ declare class Logger {
204
204
  /**
205
205
  * @file bundler.ts
206
206
  * @description Core bundling functions to handle both single and multi-page HTML documents. This includes asset extraction, optional minification, and full inlining into a self-contained HTML file.
207
- * @version 1.3.0 // Assuming version based on previous context
208
207
  */
209
208
 
210
209
  /**
@@ -221,8 +220,6 @@ declare function bundleMultiPageHTML(pages: PageEntry[], logger?: Logger): strin
221
220
  * @file index.ts
222
221
  * @description Public API surface for PortaPack.
223
222
  * Exposes the unified `pack()` method and advanced helpers like recursive crawling and multi-page bundling.
224
- * @version 1.0.0 - (Add version if applicable)
225
- * @date 2025-04-11
226
223
  */
227
224
 
228
225
  /**
package/dist/index.js CHANGED
@@ -112,7 +112,9 @@ var Logger = class _Logger {
112
112
  case "none":
113
113
  return new _Logger(0 /* NONE */);
114
114
  default:
115
- console.warn(`[Logger] Invalid log level name "${levelName}". Defaulting to ${LogLevel[defaultLevel]}.`);
115
+ console.warn(
116
+ `[Logger] Invalid log level name "${levelName}". Defaulting to ${LogLevel[defaultLevel]}.`
117
+ );
116
118
  return new _Logger(defaultLevel);
117
119
  }
118
120
  }
@@ -209,7 +211,9 @@ function determineBaseUrl(inputPathOrUrl, logger) {
209
211
  logger?.debug(`Determined remote base URL: ${baseUrl}`);
210
212
  return baseUrl;
211
213
  } else if (inputPathOrUrl.includes("://") && !inputPathOrUrl.startsWith("file:")) {
212
- logger?.warn(`Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`);
214
+ logger?.warn(
215
+ `Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`
216
+ );
213
217
  return void 0;
214
218
  } else {
215
219
  let resourcePath;
@@ -235,12 +239,16 @@ function determineBaseUrl(inputPathOrUrl, logger) {
235
239
  }
236
240
  const fileUrl = new URL2("file://" + normalizedPathForURL);
237
241
  const fileUrlString = fileUrl.href;
238
- logger?.debug(`Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`);
242
+ logger?.debug(
243
+ `Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`
244
+ );
239
245
  return fileUrlString;
240
246
  }
241
247
  } catch (error) {
242
248
  const message = error instanceof Error ? error.message : String(error);
243
- logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`);
249
+ logger?.error(
250
+ `\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`
251
+ );
244
252
  return void 0;
245
253
  }
246
254
  }
@@ -255,7 +263,9 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
255
263
  const base = new URL2(baseContextUrl);
256
264
  resolvableUrl = base.protocol + resolvableUrl;
257
265
  } catch (e) {
258
- logger?.warn(`Could not extract protocol from base "${baseContextUrl}" for protocol-relative URL "${trimmedUrl}". Skipping.`);
266
+ logger?.warn(
267
+ `Could not extract protocol from base "${baseContextUrl}" for protocol-relative URL "${trimmedUrl}". Skipping.`
268
+ );
259
269
  return null;
260
270
  }
261
271
  }
@@ -269,9 +279,13 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
269
279
  } catch (error) {
270
280
  const message = error instanceof Error ? error.message : String(error);
271
281
  if (!/^[a-z]+:/i.test(resolvableUrl) && !resolvableUrl.startsWith("/") && !baseContextUrl) {
272
- logger?.warn(`Cannot resolve relative URL "${resolvableUrl}" - Base context URL was not provided or determined.`);
282
+ logger?.warn(
283
+ `Cannot resolve relative URL "${resolvableUrl}" - Base context URL was not provided or determined.`
284
+ );
273
285
  } else {
274
- logger?.warn(`\u26A0\uFE0F Failed to parse/resolve URL "${resolvableUrl}" ${baseContextUrl ? 'against base "' + baseContextUrl + '"' : "(no base provided)"}: ${message}`);
286
+ logger?.warn(
287
+ `\u26A0\uFE0F Failed to parse/resolve URL "${resolvableUrl}" ${baseContextUrl ? 'against base "' + baseContextUrl + '"' : "(no base provided)"}: ${message}`
288
+ );
275
289
  }
276
290
  return null;
277
291
  }
@@ -301,14 +315,18 @@ async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
301
315
  timeout
302
316
  // Apply network timeout
303
317
  });
304
- logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data?.byteLength ?? 0} bytes)`);
318
+ logger?.debug(
319
+ `Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data?.byteLength ?? 0} bytes)`
320
+ );
305
321
  return Buffer.from(response.data);
306
322
  } else if (protocol === "file:") {
307
323
  let filePath;
308
324
  try {
309
325
  filePath = fileURLToPath(resolvedUrl);
310
326
  } catch (e) {
311
- logger?.error(`Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`);
327
+ logger?.error(
328
+ `Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`
329
+ );
312
330
  return null;
313
331
  }
314
332
  const normalizedForLog = path2.normalize(filePath);
@@ -342,9 +360,13 @@ async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
342
360
  logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
343
361
  }
344
362
  } else if (error instanceof Error) {
345
- logger?.warn(`\u26A0\uFE0F An unexpected error occurred processing asset ${resolvedUrl.href}: ${error.message}`);
363
+ logger?.warn(
364
+ `\u26A0\uFE0F An unexpected error occurred processing asset ${resolvedUrl.href}: ${error.message}`
365
+ );
346
366
  } else {
347
- logger?.warn(`\u26A0\uFE0F An unknown and unexpected error occurred processing asset ${resolvedUrl.href}: ${String(error)}`);
367
+ logger?.warn(
368
+ `\u26A0\uFE0F An unknown and unexpected error occurred processing asset ${resolvedUrl.href}: ${String(error)}`
369
+ );
348
370
  }
349
371
  return null;
350
372
  }
@@ -355,7 +377,8 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
355
377
  const urlRegex = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
356
378
  const importRegex = /@import\s+(?:url\(\s*(['"]?)(.*?)\1\s*\)|(['"])(.*?)\3)\s*;/gi;
357
379
  const processFoundUrl = (rawUrl, ruleType) => {
358
- if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:") || rawUrl.startsWith("#")) return;
380
+ if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:") || rawUrl.startsWith("#"))
381
+ return;
359
382
  const resolvedUrl = resolveCssRelativeUrl(rawUrl, cssBaseContextUrl, logger);
360
383
  if (resolvedUrl && !processedInThisParse.has(resolvedUrl)) {
361
384
  processedInThisParse.add(resolvedUrl);
@@ -367,7 +390,9 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
367
390
  content: void 0
368
391
  // Content will be fetched later if needed
369
392
  });
370
- logger?.debug(`Discovered nested ${assetType} asset (${ruleType}) in CSS ${cssBaseContextUrl}: ${resolvedUrl}`);
393
+ logger?.debug(
394
+ `Discovered nested ${assetType} asset (${ruleType}) in CSS ${cssBaseContextUrl}: ${resolvedUrl}`
395
+ );
371
396
  }
372
397
  };
373
398
  let match;
@@ -381,14 +406,20 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
381
406
  return newlyDiscovered;
382
407
  }
383
408
  async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger) {
384
- logger?.info(`\u{1F680} Starting asset extraction! Embed: ${embedAssets}. Input: ${inputPathOrUrl || "(HTML content only)"}`);
409
+ logger?.info(
410
+ `\u{1F680} Starting asset extraction! Embed: ${embedAssets}. Input: ${inputPathOrUrl || "(HTML content only)"}`
411
+ );
385
412
  const initialAssets = parsed.assets || [];
386
413
  const finalAssetsMap = /* @__PURE__ */ new Map();
387
414
  let assetsToProcess = [];
388
415
  const processedOrQueuedUrls = /* @__PURE__ */ new Set();
389
416
  const htmlBaseContextUrl = determineBaseUrl(inputPathOrUrl || "", logger);
390
- if (!htmlBaseContextUrl && initialAssets.some((a) => !/^[a-z]+:/i.test(a.url) && !a.url.startsWith("data:") && !a.url.startsWith("#") && !a.url.startsWith("/"))) {
391
- logger?.warn("\u{1F6A8} No valid base path/URL determined for the HTML source! Resolution of relative asset paths from HTML may fail.");
417
+ if (!htmlBaseContextUrl && initialAssets.some(
418
+ (a) => !/^[a-z]+:/i.test(a.url) && !a.url.startsWith("data:") && !a.url.startsWith("#") && !a.url.startsWith("/")
419
+ )) {
420
+ logger?.warn(
421
+ "\u{1F6A8} No valid base path/URL determined for the HTML source! Resolution of relative asset paths from HTML may fail."
422
+ );
392
423
  } else if (htmlBaseContextUrl) {
393
424
  logger?.debug(`Using HTML base context URL: ${htmlBaseContextUrl}`);
394
425
  }
@@ -420,9 +451,13 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
420
451
  while (assetsToProcess.length > 0) {
421
452
  iterationCount++;
422
453
  if (iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS) {
423
- logger?.error(`\u{1F6D1} Asset extraction loop limit hit (${MAX_ASSET_EXTRACTION_ITERATIONS})! Aborting.`);
454
+ logger?.error(
455
+ `\u{1F6D1} Asset extraction loop limit hit (${MAX_ASSET_EXTRACTION_ITERATIONS})! Aborting.`
456
+ );
424
457
  const remainingUrls = assetsToProcess.map((a) => a.url).slice(0, 10).join(", ");
425
- logger?.error(`Remaining queue sample (${assetsToProcess.length} items): ${remainingUrls}...`);
458
+ logger?.error(
459
+ `Remaining queue sample (${assetsToProcess.length} items): ${remainingUrls}...`
460
+ );
426
461
  assetsToProcess.forEach((asset) => {
427
462
  if (!finalAssetsMap.has(asset.url)) {
428
463
  finalAssetsMap.set(asset.url, { ...asset, content: void 0 });
@@ -448,7 +483,9 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
448
483
  try {
449
484
  assetUrlObj = new URL2(asset.url);
450
485
  } catch (urlError) {
451
- logger?.warn(`Cannot create URL object for "${asset.url}", skipping fetch. Error: ${urlError instanceof Error ? urlError.message : String(urlError)}`);
486
+ logger?.warn(
487
+ `Cannot create URL object for "${asset.url}", skipping fetch. Error: ${urlError instanceof Error ? urlError.message : String(urlError)}`
488
+ );
452
489
  finalAssetsMap.set(asset.url, { ...asset, content: void 0 });
453
490
  continue;
454
491
  }
@@ -484,7 +521,9 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
484
521
  cssContentForParsing = textContent;
485
522
  }
486
523
  } else {
487
- logger?.warn(`Could not decode ${asset.type} asset ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`);
524
+ logger?.warn(
525
+ `Could not decode ${asset.type} asset ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`
526
+ );
488
527
  cssContentForParsing = void 0;
489
528
  if (embedAssets) {
490
529
  finalContent = `data:${effectiveMime};base64,${assetContentBuffer.toString("base64")}`;
@@ -505,14 +544,18 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
505
544
  try {
506
545
  const attemptedTextContent = assetContentBuffer.toString("utf-8");
507
546
  if (isUtf8DecodingLossy(assetContentBuffer, attemptedTextContent)) {
508
- logger?.warn(`Couldn't embed unclassified asset ${asset.url} as text due to invalid UTF-8 sequences. Falling back to base64 (octet-stream).`);
547
+ logger?.warn(
548
+ `Couldn't embed unclassified asset ${asset.url} as text due to invalid UTF-8 sequences. Falling back to base64 (octet-stream).`
549
+ );
509
550
  finalContent = `data:application/octet-stream;base64,${assetContentBuffer.toString("base64")}`;
510
551
  } else {
511
552
  finalContent = attemptedTextContent;
512
553
  logger?.debug(`Successfully embedded unclassified asset ${asset.url} as text.`);
513
554
  }
514
555
  } catch (decodeError) {
515
- logger?.warn(`Error during text decoding for unclassified asset ${asset.url}: ${decodeError instanceof Error ? decodeError.message : String(decodeError)}. Falling back to base64.`);
556
+ logger?.warn(
557
+ `Error during text decoding for unclassified asset ${asset.url}: ${decodeError instanceof Error ? decodeError.message : String(decodeError)}. Falling back to base64.`
558
+ );
516
559
  finalContent = `data:application/octet-stream;base64,${assetContentBuffer.toString("base64")}`;
517
560
  }
518
561
  } else {
@@ -526,7 +569,9 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
526
569
  finalAssetsMap.set(asset.url, { ...asset, url: asset.url, content: finalContent });
527
570
  if (asset.type === "css" && cssContentForParsing) {
528
571
  const cssBaseContextUrl = determineBaseUrl(asset.url, logger);
529
- logger?.debug(`CSS base context for resolving nested assets within ${asset.url}: ${cssBaseContextUrl}`);
572
+ logger?.debug(
573
+ `CSS base context for resolving nested assets within ${asset.url}: ${cssBaseContextUrl}`
574
+ );
530
575
  if (cssBaseContextUrl) {
531
576
  const newlyDiscoveredAssets = extractUrlsFromCSS(
532
577
  cssContentForParsing,
@@ -535,25 +580,33 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
535
580
  logger
536
581
  );
537
582
  if (newlyDiscoveredAssets.length > 0) {
538
- logger?.debug(`Discovered ${newlyDiscoveredAssets.length} nested assets in CSS ${asset.url}. Checking against queue...`);
583
+ logger?.debug(
584
+ `Discovered ${newlyDiscoveredAssets.length} nested assets in CSS ${asset.url}. Checking against queue...`
585
+ );
539
586
  for (const newAsset of newlyDiscoveredAssets) {
540
587
  if (!processedOrQueuedUrls.has(newAsset.url)) {
541
588
  processedOrQueuedUrls.add(newAsset.url);
542
589
  assetsToProcess.push(newAsset);
543
590
  logger?.debug(` -> Queued new nested asset: ${newAsset.url}`);
544
591
  } else {
545
- logger?.debug(` -> Skipping already processed/queued nested asset: ${newAsset.url}`);
592
+ logger?.debug(
593
+ ` -> Skipping already processed/queued nested asset: ${newAsset.url}`
594
+ );
546
595
  }
547
596
  }
548
597
  }
549
598
  } else {
550
- logger?.warn(`Could not determine base URL context for CSS file ${asset.url}. Cannot resolve nested relative paths within it.`);
599
+ logger?.warn(
600
+ `Could not determine base URL context for CSS file ${asset.url}. Cannot resolve nested relative paths within it.`
601
+ );
551
602
  }
552
603
  }
553
604
  }
554
605
  }
555
606
  const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ? `${MAX_ASSET_EXTRACTION_ITERATIONS}+ (limit hit)` : iterationCount;
556
- logger?.info(`\u2705 Asset extraction COMPLETE! Found ${finalAssetsMap.size} unique assets in ${finalIterationCount} iterations.`);
607
+ logger?.info(
608
+ `\u2705 Asset extraction COMPLETE! Found ${finalAssetsMap.size} unique assets in ${finalIterationCount} iterations.`
609
+ );
557
610
  return {
558
611
  htmlContent: parsed.htmlContent,
559
612
  assets: Array.from(finalAssetsMap.values())
@@ -633,7 +686,7 @@ async function minifyAssets(parsed, options = {}, logger) {
633
686
  logger?.debug(`Minification flags: ${JSON.stringify(minifyFlags)}`);
634
687
  const minifiedAssets = await Promise.all(
635
688
  currentAssets.map(async (asset) => {
636
- let processedAsset = { ...asset };
689
+ const processedAsset = { ...asset };
637
690
  if (typeof processedAsset.content !== "string" || processedAsset.content.length === 0) {
638
691
  return processedAsset;
639
692
  }
@@ -648,13 +701,17 @@ async function minifyAssets(parsed, options = {}, logger) {
648
701
  logger?.warn(`\u26A0\uFE0F CleanCSS failed for ${assetIdentifier}: ${result.errors.join(", ")}`);
649
702
  } else {
650
703
  if (result.warnings && result.warnings.length > 0) {
651
- logger?.debug(`CleanCSS warnings for ${assetIdentifier}: ${result.warnings.join(", ")}`);
704
+ logger?.debug(
705
+ `CleanCSS warnings for ${assetIdentifier}: ${result.warnings.join(", ")}`
706
+ );
652
707
  }
653
708
  if (result.styles) {
654
709
  newContent = result.styles;
655
710
  logger?.debug(`CSS minified successfully: ${assetIdentifier}`);
656
711
  } else {
657
- logger?.warn(`\u26A0\uFE0F CleanCSS produced no styles but reported no errors for ${assetIdentifier}. Keeping original.`);
712
+ logger?.warn(
713
+ `\u26A0\uFE0F CleanCSS produced no styles but reported no errors for ${assetIdentifier}. Keeping original.`
714
+ );
658
715
  }
659
716
  }
660
717
  }
@@ -667,15 +724,21 @@ async function minifyAssets(parsed, options = {}, logger) {
667
724
  } else {
668
725
  const terserError = result.error;
669
726
  if (terserError) {
670
- logger?.warn(`\u26A0\uFE0F Terser failed for ${assetIdentifier}: ${terserError.message || terserError}`);
727
+ logger?.warn(
728
+ `\u26A0\uFE0F Terser failed for ${assetIdentifier}: ${terserError.message || terserError}`
729
+ );
671
730
  } else {
672
- logger?.warn(`\u26A0\uFE0F Terser produced no code but reported no errors for ${assetIdentifier}. Keeping original.`);
731
+ logger?.warn(
732
+ `\u26A0\uFE0F Terser produced no code but reported no errors for ${assetIdentifier}. Keeping original.`
733
+ );
673
734
  }
674
735
  }
675
736
  }
676
737
  } catch (err) {
677
738
  const errorMessage = err instanceof Error ? err.message : String(err);
678
- logger?.warn(`\u26A0\uFE0F Failed to minify asset ${assetIdentifier} (${processedAsset.type}): ${errorMessage}`);
739
+ logger?.warn(
740
+ `\u26A0\uFE0F Failed to minify asset ${assetIdentifier} (${processedAsset.type}): ${errorMessage}`
741
+ );
679
742
  }
680
743
  processedAsset.content = newContent;
681
744
  return processedAsset;
@@ -777,7 +840,9 @@ function inlineAssets($, assets, logger) {
777
840
  logger?.debug(`Inlining image via ${srcAttr}: ${asset.url}`);
778
841
  element.attr(srcAttr, asset.content);
779
842
  } else if (src) {
780
- logger?.warn(`Could not inline image via ${srcAttr}: ${src}. Content missing or not a data URI.`);
843
+ logger?.warn(
844
+ `Could not inline image via ${srcAttr}: ${src}. Content missing or not a data URI.`
845
+ );
781
846
  }
782
847
  });
783
848
  $("img[srcset], source[srcset]").each((_, el) => {
@@ -899,7 +964,9 @@ function bundleMultiPageHTML(pages, logger) {
899
964
  } else if (!baseSlug) {
900
965
  if (isRootIndex) {
901
966
  baseSlug = "index";
902
- logger?.debug(`URL "${page.url}" sanitized to empty string, using "index" as it is a root index.`);
967
+ logger?.debug(
968
+ `URL "${page.url}" sanitized to empty string, using "index" as it is a root index.`
969
+ );
903
970
  } else {
904
971
  baseSlug = "page";
905
972
  logger?.debug(`URL "${page.url}" sanitized to empty string, using fallback slug "page".`);
@@ -907,14 +974,18 @@ function bundleMultiPageHTML(pages, logger) {
907
974
  }
908
975
  if (!baseSlug) {
909
976
  baseSlug = `page-${pageCounterForFallback++}`;
910
- logger?.warn(`Could not determine a valid base slug for "${page.url}", using generated fallback "${baseSlug}".`);
977
+ logger?.warn(
978
+ `Could not determine a valid base slug for "${page.url}", using generated fallback "${baseSlug}".`
979
+ );
911
980
  }
912
981
  let slug = baseSlug;
913
982
  let collisionCounter = 1;
914
983
  const originalBaseSlugForLog = baseSlug;
915
984
  while (usedSlugs.has(slug)) {
916
985
  const newSlug = `${originalBaseSlugForLog}-${collisionCounter++}`;
917
- logger?.warn(`Slug collision detected for "${page.url}" (intended slug: '${originalBaseSlugForLog}'). Using "${newSlug}" instead.`);
986
+ logger?.warn(
987
+ `Slug collision detected for "${page.url}" (intended slug: '${originalBaseSlugForLog}'). Using "${newSlug}" instead.`
988
+ );
918
989
  slug = newSlug;
919
990
  }
920
991
  usedSlugs.add(slug);
@@ -924,7 +995,8 @@ function bundleMultiPageHTML(pages, logger) {
924
995
  }
925
996
  }
926
997
  const defaultPageSlug = usedSlugs.has("index") ? "index" : firstValidSlug || "page";
927
- let output = `<!DOCTYPE html>
998
+ const output = `
999
+ <!DOCTYPE html>
928
1000
  <html lang="en">
929
1001
  <head>
930
1002
  <meta charset="UTF-8">
@@ -940,74 +1012,74 @@ function bundleMultiPageHTML(pages, logger) {
940
1012
  </style>
941
1013
  </head>
942
1014
  <body>
943
- <nav id="main-nav">
944
- ${validPages.map((p) => {
1015
+ <nav id="main-nav">
1016
+ ${validPages.map((p) => {
945
1017
  const slug = slugMap.get(p.url);
946
1018
  const label = slug;
947
1019
  return `<a href="#${slug}" data-page="${slug}">${label}</a>`;
948
1020
  }).join("\n ")}
949
- </nav>
950
- <div id="page-container"></div>
951
- ${validPages.map((p) => {
1021
+ </nav>
1022
+ <div id="page-container"></div>
1023
+ ${validPages.map((p) => {
952
1024
  const slug = slugMap.get(p.url);
953
1025
  return `<template id="page-${slug}">${p.html}</template>`;
954
1026
  }).join("\n ")}
955
- <script id="router-script">
956
- document.addEventListener('DOMContentLoaded', function() {
957
- const pageContainer = document.getElementById('page-container');
958
- const navLinks = document.querySelectorAll('#main-nav a');
1027
+ <script id="router-script">
1028
+ document.addEventListener('DOMContentLoaded', function() {
1029
+ const pageContainer = document.getElementById('page-container');
1030
+ const navLinks = document.querySelectorAll('#main-nav a');
959
1031
 
960
- function navigateTo(slug) {
961
- const template = document.getElementById('page-' + slug);
962
- if (!template || !pageContainer) {
963
- console.warn('Navigation failed: Template or container not found for slug:', slug);
964
- // Maybe try navigating to default page? Or just clear container?
965
- if (pageContainer) pageContainer.innerHTML = '<p>Page not found.</p>';
966
- return;
967
- }
968
- // Clear previous content and append new content
969
- pageContainer.innerHTML = ''; // Clear reliably
970
- pageContainer.appendChild(template.content.cloneNode(true));
1032
+ function navigateTo(slug) {
1033
+ const template = document.getElementById('page-' + slug);
1034
+ if (!template || !pageContainer) {
1035
+ console.warn('Navigation failed: Template or container not found for slug:', slug);
1036
+ // Maybe try navigating to default page? Or just clear container?
1037
+ if (pageContainer) pageContainer.innerHTML = '<p>Page not found.</p>';
1038
+ return;
1039
+ }
1040
+ // Clear previous content and append new content
1041
+ pageContainer.innerHTML = ''; // Clear reliably
1042
+ pageContainer.appendChild(template.content.cloneNode(true));
971
1043
 
972
- // Update active link styling
973
- navLinks.forEach(link => {
974
- link.classList.toggle('active', link.getAttribute('data-page') === slug);
975
- });
1044
+ // Update active link styling
1045
+ navLinks.forEach(link => {
1046
+ link.classList.toggle('active', link.getAttribute('data-page') === slug);
1047
+ });
976
1048
 
977
- // Update URL hash without triggering hashchange if already correct
978
- if (window.location.hash.substring(1) !== slug) {
979
- // Use pushState for cleaner history
980
- history.pushState({ slug: slug }, '', '#' + slug);
981
- }
1049
+ // Update URL hash without triggering hashchange if already correct
1050
+ if (window.location.hash.substring(1) !== slug) {
1051
+ // Use pushState for cleaner history
1052
+ history.pushState({ slug: slug }, '', '#' + slug);
982
1053
  }
1054
+ }
983
1055
 
984
- // Handle back/forward navigation
985
- window.addEventListener('popstate', (event) => {
986
- let slug = window.location.hash.substring(1);
987
- // If popstate event has state use it, otherwise fallback to hash or default
988
- if (event && event.state && event.state.slug) { // Check event exists
989
- slug = event.state.slug;
990
- }
991
- // Ensure the target page exists before navigating, fallback to default slug
992
- const targetSlug = document.getElementById('page-' + slug) ? slug : '${defaultPageSlug}';
993
- navigateTo(targetSlug);
994
- });
1056
+ // Handle back/forward navigation
1057
+ window.addEventListener('popstate', (event) => {
1058
+ let slug = window.location.hash.substring(1);
1059
+ // If popstate event has state use it, otherwise fallback to hash or default
1060
+ if (event && event.state && event.state.slug) { // Check event exists
1061
+ slug = event.state.slug;
1062
+ }
1063
+ // Ensure the target page exists before navigating, fallback to default slug
1064
+ const targetSlug = document.getElementById('page-' + slug) ? slug : '${defaultPageSlug}';
1065
+ navigateTo(targetSlug);
1066
+ });
995
1067
 
996
- // Handle direct link clicks
997
- navLinks.forEach(link => {
998
- link.addEventListener('click', function(e) {
999
- e.preventDefault();
1000
- const slug = this.getAttribute('data-page');
1001
- if (slug) navigateTo(slug);
1002
- });
1068
+ // Handle direct link clicks
1069
+ navLinks.forEach(link => {
1070
+ link.addEventListener('click', function(e) {
1071
+ e.preventDefault();
1072
+ const slug = this.getAttribute('data-page');
1073
+ if (slug) navigateTo(slug);
1003
1074
  });
1004
-
1005
- // Initial page load
1006
- const initialHash = window.location.hash.substring(1);
1007
- const initialSlug = document.getElementById('page-' + initialHash) ? initialHash : '${defaultPageSlug}';
1008
- navigateTo(initialSlug);
1009
1075
  });
1010
- </script>
1076
+
1077
+ // Initial page load
1078
+ const initialHash = window.location.hash.substring(1);
1079
+ const initialSlug = document.getElementById('page-' + initialHash) ? initialHash : '${defaultPageSlug}';
1080
+ navigateTo(initialSlug);
1081
+ });
1082
+ </script>
1011
1083
  </body>
1012
1084
  </html>`;
1013
1085
  logger?.info(`Multi-page bundle generated. Size: ${Buffer.byteLength(output, "utf-8")} bytes.`);
@@ -1074,7 +1146,9 @@ async function fetchAndPackWebPage(url, logger, timeout = DEFAULT_PAGE_TIMEOUT,
1074
1146
  throw pageError;
1075
1147
  }
1076
1148
  } catch (launchError) {
1077
- logger?.error(`Critical error during browser launch or page setup for ${url}: ${launchError.message}`);
1149
+ logger?.error(
1150
+ `Critical error during browser launch or page setup for ${url}: ${launchError.message}`
1151
+ );
1078
1152
  if (browser) {
1079
1153
  try {
1080
1154
  await browser.close();
@@ -1087,7 +1161,9 @@ async function fetchAndPackWebPage(url, logger, timeout = DEFAULT_PAGE_TIMEOUT,
1087
1161
  throw launchError;
1088
1162
  } finally {
1089
1163
  if (browser) {
1090
- logger?.warn(`Closing browser in final cleanup for ${url}. This might indicate an unusual error path.`);
1164
+ logger?.warn(
1165
+ `Closing browser in final cleanup for ${url}. This might indicate an unusual error path.`
1166
+ );
1091
1167
  try {
1092
1168
  await browser.close();
1093
1169
  } catch (closeErr) {
@@ -1204,21 +1280,26 @@ async function crawlWebsite(startUrl, options) {
1204
1280
  }
1205
1281
  async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1, loggerInstance) {
1206
1282
  const logger = loggerInstance || new Logger();
1207
- logger.info(`Starting recursive site bundle for ${startUrl} to ${outputFile} (maxDepth: ${maxDepth})`);
1283
+ logger.info(
1284
+ `Starting recursive site bundle for ${startUrl} to ${outputFile} (maxDepth: ${maxDepth})`
1285
+ );
1208
1286
  try {
1209
1287
  const crawlOptions = {
1210
1288
  maxDepth,
1211
1289
  logger
1212
- /* Add other options like timeout, userAgent if needed */
1213
1290
  };
1214
1291
  const pages = await crawlWebsite(startUrl, crawlOptions);
1215
1292
  if (pages.length === 0) {
1216
- logger.warn("Crawl completed but found 0 pages. Output file may be empty or reflect an empty bundle.");
1293
+ logger.warn(
1294
+ "Crawl completed but found 0 pages. Output file may be empty or reflect an empty bundle."
1295
+ );
1217
1296
  } else {
1218
1297
  logger.info(`Crawl successful, found ${pages.length} pages. Starting bundling.`);
1219
1298
  }
1220
1299
  const bundledHtml = bundleMultiPageHTML(pages, logger);
1221
- logger.info(`Bundling complete. Output size: ${Buffer.byteLength(bundledHtml, "utf-8")} bytes.`);
1300
+ logger.info(
1301
+ `Bundling complete. Output size: ${Buffer.byteLength(bundledHtml, "utf-8")} bytes.`
1302
+ );
1222
1303
  logger.info(`Writing bundled HTML to ${outputFile}`);
1223
1304
  await fs2.writeFile(outputFile, bundledHtml, "utf-8");
1224
1305
  logger.info(`Successfully wrote bundled output to ${outputFile}`);