portapack 0.3.0 → 0.3.1

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.
@@ -23,11 +23,12 @@ jobs:
23
23
  cache: 'npm'
24
24
 
25
25
  - run: npm ci
26
- # - run: npm run test:ci
27
- # - run: npm run lint
28
- # - run: npm run format:check
29
- - run: npm run build # for now we keep it simple to release
26
+ - run: npm run test:ci
30
27
 
28
+ - name: Upload coverage to Codecov
29
+ uses: codecov/codecov-action@v4
30
+ with:
31
+ token: ${{ secrets.CODECOV_TOKEN }}
31
32
  # coverage:
32
33
  # needs: build
33
34
  # runs-on: ubuntu-latest
package/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.3.1](https://github.com/manicinc/portapack/compare/v0.3.0...v0.3.1) (2025-04-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ci:** update Codecov config [skip release] ([593b126](https://github.com/manicinc/portapack/commit/593b1262183d05a9a7099463b6da0f4deb916576))
7
+ * **extractor:** resolve test failures and coverage issues ([40ea42c](https://github.com/manicinc/portapack/commit/40ea42cbdbeec67657225c50eb97ef0965cd2769))
8
+
1
9
  # [0.3.0](https://github.com/manicinc/portapack/compare/v0.2.1...v0.3.0) (2025-04-13)
2
10
 
3
11
 
package/README.md CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/portapack.svg?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/portapack)
4
4
  [![Build Status](https://img.shields.io/github/actions/workflow/status/manicinc/portapack/ci.yml?branch=master&style=for-the-badge&logo=github)](https://github.com/manicinc/portapack/actions)
5
- [![Coverage Status](https://img.shields.io/coveralls/github/manicinc/portapack?style=for-the-badge&logo=codecov)](https://coveralls.io/github/manicinc/portapack)
5
+ [![Codecov](https://img.shields.io/codecov/c/github/manicinc/portapack?style=for-the-badge&logo=codecov)](https://codecov.io/gh/manicinc/portapack)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg?style=for-the-badge)](./LICENSE)
6
7
 
7
8
  **PortaPack** bundles your entire website — HTML, CSS, JS, images, and fonts — into one self-contained HTML file. Perfect for snapshots, demos, testing, and offline apps.
8
9
 
@@ -125,15 +126,6 @@ import {
125
126
  | `fetchAndPackWebPage()` | Just fetch HTML (no asset processing) |
126
127
  | `bundleMultiPageHTML()` | Combine multiple HTMLs with router |
127
128
 
128
- ## 🧪 Use Cases
129
-
130
- - Archive pages for offline use
131
- - Create demo bundles without a web server
132
- - Simplify distribution of small apps
133
- - QA test static assets
134
- - Embed pages in PDFs or ebooks
135
- - Analyze asset weight impact
136
-
137
129
  ## 🤝 Contribute
138
130
 
139
131
  ```bash
@@ -146,10 +138,13 @@ npm run dev
146
138
 
147
139
  ## 📊 Project Health
148
140
 
149
- (Metrics auto-generated coming soon)
141
+ | Metric | Value |
142
+ |--------------|-------|
143
+ | 📦 Version | [![npm](https://img.shields.io/npm/v/portapack.svg)](https://www.npmjs.com/package/portapack) |
144
+ | ✅ Build | [![Build Status](https://img.shields.io/github/actions/workflow/status/manicinc/portapack/ci.yml?branch=master)](https://github.com/manicinc/portapack/actions) |
145
+ | 🧪 Coverage | [![Codecov](https://img.shields.io/codecov/c/github/manicinc/portapack)](https://codecov.io/gh/manicinc/portapack) |
150
146
 
151
147
  ## 📄 License
152
148
 
153
- MIT — Built with ✨ by Manic Agency
149
+ MIT — Built by Manic.agency
154
150
 
155
- *Open Source Empowering Designers and Developers 🖥️*
@@ -326,7 +326,6 @@ function isUtf8DecodingLossy(originalBuffer, decodedString) {
326
326
  }
327
327
  }
328
328
  function determineBaseUrl(inputPathOrUrl, logger) {
329
- console.log(`[DEBUG determineBaseUrl] Input: "${inputPathOrUrl}"`);
330
329
  logger?.debug(`Determining base URL for input: ${inputPathOrUrl}`);
331
330
  if (!inputPathOrUrl) {
332
331
  logger?.warn("Cannot determine base URL: inputPathOrUrl is empty or invalid.");
@@ -340,11 +339,9 @@ function determineBaseUrl(inputPathOrUrl, logger) {
340
339
  url.hash = "";
341
340
  const baseUrl = url.href;
342
341
  logger?.debug(`Determined remote base URL: ${baseUrl}`);
343
- console.log(`[DEBUG determineBaseUrl] Determined Remote URL: "${baseUrl}"`);
344
342
  return baseUrl;
345
343
  } else if (inputPathOrUrl.includes("://") && !inputPathOrUrl.startsWith("file:")) {
346
344
  logger?.warn(`Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`);
347
- console.log(`[DEBUG determineBaseUrl] Unsupported protocol.`);
348
345
  return void 0;
349
346
  } else {
350
347
  let resourcePath;
@@ -360,9 +357,7 @@ function determineBaseUrl(inputPathOrUrl, logger) {
360
357
  isInputLikelyDirectory = false;
361
358
  }
362
359
  }
363
- console.log(`[DEBUG determineBaseUrl] resourcePath: "${resourcePath}", isInputLikelyDirectory: ${isInputLikelyDirectory}`);
364
360
  const baseDirPath = isInputLikelyDirectory ? resourcePath : import_path2.default.dirname(resourcePath);
365
- console.log(`[DEBUG determineBaseUrl] Calculated baseDirPath: "${baseDirPath}"`);
366
361
  let normalizedPathForURL = baseDirPath.replace(/\\/g, "/");
367
362
  if (/^[A-Z]:\//i.test(normalizedPathForURL) && !normalizedPathForURL.startsWith("/")) {
368
363
  normalizedPathForURL = "/" + normalizedPathForURL;
@@ -373,12 +368,10 @@ function determineBaseUrl(inputPathOrUrl, logger) {
373
368
  const fileUrl = new import_url.URL("file://" + normalizedPathForURL);
374
369
  const fileUrlString = fileUrl.href;
375
370
  logger?.debug(`Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`);
376
- console.log(`[DEBUG determineBaseUrl] Determined File URL: "${fileUrlString}"`);
377
371
  return fileUrlString;
378
372
  }
379
373
  } catch (error) {
380
374
  const message = error instanceof Error ? error.message : String(error);
381
- console.error(`[DEBUG determineBaseUrl] Error determining base URL: ${message}`);
382
375
  logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`);
383
376
  return void 0;
384
377
  }
@@ -416,82 +409,69 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
416
409
  }
417
410
  }
418
411
  function resolveCssRelativeUrl(relativeUrl, cssBaseContextUrl, logger) {
419
- console.log(`[DEBUG resolveCssRelativeUrl] Input: relative="${relativeUrl}", base="${cssBaseContextUrl}"`);
420
412
  if (!relativeUrl || relativeUrl.startsWith("data:") || relativeUrl.startsWith("#")) {
421
413
  return null;
422
414
  }
423
415
  try {
424
416
  const resolvedUrl = new import_url.URL(relativeUrl, cssBaseContextUrl);
425
- console.log(`[DEBUG resolveCssRelativeUrl] Resolved URL object href: "${resolvedUrl.href}"`);
426
417
  return resolvedUrl.href;
427
418
  } catch (error) {
428
419
  logger?.warn(
429
420
  `Failed to resolve CSS URL: "${relativeUrl}" relative to "${cssBaseContextUrl}": ${String(error)}`
430
421
  );
431
- console.error(`[DEBUG resolveCssRelativeUrl] Error resolving: ${String(error)}`);
432
422
  return null;
433
423
  }
434
424
  }
435
425
  async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
436
- console.log(`[DEBUG fetchAsset] Attempting fetch for URL: ${resolvedUrl.href}`);
437
426
  logger?.debug(`Attempting to fetch asset: ${resolvedUrl.href}`);
438
427
  const protocol = resolvedUrl.protocol;
439
428
  try {
440
429
  if (protocol === "http:" || protocol === "https:") {
441
430
  const response = await axiosNs.default.get(resolvedUrl.href, {
442
431
  responseType: "arraybuffer",
432
+ // Fetch as binary data
443
433
  timeout
434
+ // Apply network timeout
444
435
  });
445
436
  logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data?.byteLength ?? 0} bytes)`);
446
- console.log(`[DEBUG fetchAsset] HTTP fetch SUCCESS for: ${resolvedUrl.href}, Status: ${response.status}`);
447
437
  return Buffer.from(response.data);
448
438
  } else if (protocol === "file:") {
449
439
  let filePath;
450
440
  try {
451
441
  filePath = (0, import_url.fileURLToPath)(resolvedUrl);
452
442
  } catch (e) {
453
- console.error(`[DEBUG fetchAsset] fileURLToPath FAILED for: ${resolvedUrl.href}`, e);
454
443
  logger?.error(`Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`);
455
444
  return null;
456
445
  }
457
446
  const normalizedForLog = import_path2.default.normalize(filePath);
458
- console.log(`[DEBUG fetchAsset] Attempting readFile with path: "${normalizedForLog}" (Original from URL: "${filePath}")`);
459
447
  const data = await (0, import_promises.readFile)(filePath);
460
- console.log(`[DEBUG fetchAsset] readFile call SUCCEEDED for path: "${normalizedForLog}". Data length: ${data?.byteLength}`);
461
448
  logger?.debug(`Read local file ${filePath} (${data.byteLength} bytes)`);
462
449
  return data;
463
450
  } else {
464
- console.log(`[DEBUG fetchAsset] Unsupported protocol: ${protocol}`);
465
451
  logger?.warn(`Unsupported protocol "${protocol}" in URL: ${resolvedUrl.href}`);
466
452
  return null;
467
453
  }
468
454
  } catch (error) {
469
455
  const failedId = protocol === "file:" ? import_path2.default.normalize((0, import_url.fileURLToPath)(resolvedUrl)) : resolvedUrl.href;
470
- console.error(`[DEBUG fetchAsset] fetch/read FAILED for: "${failedId}". Error:`, error);
471
- if ((protocol === "http:" || protocol === "https:") && axiosNs.isAxiosError(error)) {
472
- const status = error.response?.status ?? "N/A";
473
- const statusText = error.response?.statusText ?? "Error";
474
- const code = error.code ?? "N/A";
475
- const message = error.message;
476
- const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}: Status ${status} - ${statusText}. Code: ${code}, Message: ${message}`;
456
+ if ((protocol === "http:" || protocol === "https:") && error?.isAxiosError === true) {
457
+ const axiosError = error;
458
+ const status = axiosError.response?.status ?? "N/A";
459
+ const code = axiosError.code ?? "N/A";
460
+ const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}: ${axiosError.message} (Code: ${code})`;
477
461
  logger?.warn(logMessage);
478
- }
479
- if (error instanceof Error && error.code === "ENOENT") {
462
+ } else if (protocol === "file:" && error instanceof Error) {
480
463
  let failedPath = resolvedUrl.href;
481
464
  try {
482
465
  failedPath = (0, import_url.fileURLToPath)(resolvedUrl);
483
466
  } catch {
484
467
  }
485
468
  failedPath = import_path2.default.normalize(failedPath);
486
- if (error instanceof Error && error.code === "ENOENT") {
469
+ if (error.code === "ENOENT") {
487
470
  logger?.warn(`\u26A0\uFE0F File not found (ENOENT) for asset: ${failedPath}.`);
488
- } else if (error instanceof Error && error.code === "EACCES") {
471
+ } else if (error.code === "EACCES") {
489
472
  logger?.warn(`\u26A0\uFE0F Permission denied (EACCES) reading asset: ${failedPath}.`);
490
- logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
491
- } else if (error instanceof Error) {
492
- logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
493
473
  } else {
494
- logger?.warn(`\u26A0\uFE0F An unknown error occurred while reading local asset ${failedPath}: ${String(error)}`);
474
+ logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
495
475
  }
496
476
  } else if (error instanceof Error) {
497
477
  logger?.warn(`\u26A0\uFE0F An unexpected error occurred processing asset ${resolvedUrl.href}: ${error.message}`);
@@ -507,7 +487,7 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
507
487
  const urlRegex = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
508
488
  const importRegex = /@import\s+(?:url\(\s*(['"]?)(.*?)\1\s*\)|(['"])(.*?)\3)\s*;/gi;
509
489
  const processFoundUrl = (rawUrl, ruleType) => {
510
- if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:")) return;
490
+ if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:") || rawUrl.startsWith("#")) return;
511
491
  const resolvedUrl = resolveCssRelativeUrl(rawUrl, cssBaseContextUrl, logger);
512
492
  if (resolvedUrl && !processedInThisParse.has(resolvedUrl)) {
513
493
  processedInThisParse.add(resolvedUrl);
@@ -515,7 +495,7 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
515
495
  newlyDiscovered.push({
516
496
  type: assetType,
517
497
  url: resolvedUrl,
518
- // The resolved absolute URL string
498
+ // Store the resolved absolute URL string
519
499
  content: void 0
520
500
  // Content will be fetched later if needed
521
501
  });
@@ -552,7 +532,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
552
532
  continue;
553
533
  }
554
534
  const urlToQueue = resolvedUrlObj.href;
555
- if (!urlToQueue.startsWith("data:") && !processedOrQueuedUrls.has(urlToQueue)) {
535
+ if (!processedOrQueuedUrls.has(urlToQueue)) {
556
536
  processedOrQueuedUrls.add(urlToQueue);
557
537
  const { assetType: guessedType } = guessMimeType(urlToQueue);
558
538
  const initialType = asset.type ?? guessedType;
@@ -561,10 +541,9 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
561
541
  // Use the resolved URL
562
542
  type: initialType,
563
543
  content: void 0
544
+ // Content is initially undefined
564
545
  });
565
546
  logger?.debug(` -> Queued initial asset: ${urlToQueue} (Original raw: ${asset.url})`);
566
- } else if (urlToQueue.startsWith("data:")) {
567
- logger?.debug(` -> Skipping data URI: ${urlToQueue.substring(0, 50)}...`);
568
547
  } else {
569
548
  logger?.debug(` -> Skipping already processed/queued initial asset: ${urlToQueue}`);
570
549
  }
@@ -684,7 +663,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
684
663
  const newlyDiscoveredAssets = extractUrlsFromCSS(
685
664
  cssContentForParsing,
686
665
  cssBaseContextUrl,
687
- // Use CSS file's base URL
666
+ // Use the CSS file's own URL as the base
688
667
  logger
689
668
  );
690
669
  if (newlyDiscoveredAssets.length > 0) {
@@ -705,7 +684,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
705
684
  }
706
685
  }
707
686
  }
708
- const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ? "MAX+" : iterationCount;
687
+ const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ? `${MAX_ASSET_EXTRACTION_ITERATIONS}+ (limit hit)` : iterationCount;
709
688
  logger?.info(`\u2705 Asset extraction COMPLETE! Found ${finalAssetsMap.size} unique assets in ${finalIterationCount} iterations.`);
710
689
  return {
711
690
  htmlContent: parsed.htmlContent,