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.
- package/.github/workflows/ci.yml +5 -4
- package/CHANGELOG.md +8 -0
- package/README.md +8 -13
- package/dist/cli/cli-entry.cjs +17 -38
- package/dist/cli/cli-entry.cjs.map +1 -1
- package/dist/index.js +17 -38
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +0 -1
- package/docs/cli.md +14 -67
- package/docs/configuration.md +101 -116
- package/docs/getting-started.md +74 -44
- package/package.json +1 -1
- package/src/core/extractor.ts +295 -248
- package/tests/unit/cli/cli.test.ts +1 -1
- package/tests/unit/core/extractor.test.ts +412 -208
- package/tests/unit/core/web-fetcher.test.ts +67 -67
- package/tsconfig.jest.json +1 -0
- package/docs/demo.md +0 -46
package/dist/index.js
CHANGED
@@ -194,7 +194,6 @@ function isUtf8DecodingLossy(originalBuffer, decodedString) {
|
|
194
194
|
}
|
195
195
|
}
|
196
196
|
function determineBaseUrl(inputPathOrUrl, logger) {
|
197
|
-
console.log(`[DEBUG determineBaseUrl] Input: "${inputPathOrUrl}"`);
|
198
197
|
logger?.debug(`Determining base URL for input: ${inputPathOrUrl}`);
|
199
198
|
if (!inputPathOrUrl) {
|
200
199
|
logger?.warn("Cannot determine base URL: inputPathOrUrl is empty or invalid.");
|
@@ -208,11 +207,9 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
208
207
|
url.hash = "";
|
209
208
|
const baseUrl = url.href;
|
210
209
|
logger?.debug(`Determined remote base URL: ${baseUrl}`);
|
211
|
-
console.log(`[DEBUG determineBaseUrl] Determined Remote URL: "${baseUrl}"`);
|
212
210
|
return baseUrl;
|
213
211
|
} else if (inputPathOrUrl.includes("://") && !inputPathOrUrl.startsWith("file:")) {
|
214
212
|
logger?.warn(`Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`);
|
215
|
-
console.log(`[DEBUG determineBaseUrl] Unsupported protocol.`);
|
216
213
|
return void 0;
|
217
214
|
} else {
|
218
215
|
let resourcePath;
|
@@ -228,9 +225,7 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
228
225
|
isInputLikelyDirectory = false;
|
229
226
|
}
|
230
227
|
}
|
231
|
-
console.log(`[DEBUG determineBaseUrl] resourcePath: "${resourcePath}", isInputLikelyDirectory: ${isInputLikelyDirectory}`);
|
232
228
|
const baseDirPath = isInputLikelyDirectory ? resourcePath : path2.dirname(resourcePath);
|
233
|
-
console.log(`[DEBUG determineBaseUrl] Calculated baseDirPath: "${baseDirPath}"`);
|
234
229
|
let normalizedPathForURL = baseDirPath.replace(/\\/g, "/");
|
235
230
|
if (/^[A-Z]:\//i.test(normalizedPathForURL) && !normalizedPathForURL.startsWith("/")) {
|
236
231
|
normalizedPathForURL = "/" + normalizedPathForURL;
|
@@ -241,12 +236,10 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
241
236
|
const fileUrl = new URL2("file://" + normalizedPathForURL);
|
242
237
|
const fileUrlString = fileUrl.href;
|
243
238
|
logger?.debug(`Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`);
|
244
|
-
console.log(`[DEBUG determineBaseUrl] Determined File URL: "${fileUrlString}"`);
|
245
239
|
return fileUrlString;
|
246
240
|
}
|
247
241
|
} catch (error) {
|
248
242
|
const message = error instanceof Error ? error.message : String(error);
|
249
|
-
console.error(`[DEBUG determineBaseUrl] Error determining base URL: ${message}`);
|
250
243
|
logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`);
|
251
244
|
return void 0;
|
252
245
|
}
|
@@ -284,82 +277,69 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
284
277
|
}
|
285
278
|
}
|
286
279
|
function resolveCssRelativeUrl(relativeUrl, cssBaseContextUrl, logger) {
|
287
|
-
console.log(`[DEBUG resolveCssRelativeUrl] Input: relative="${relativeUrl}", base="${cssBaseContextUrl}"`);
|
288
280
|
if (!relativeUrl || relativeUrl.startsWith("data:") || relativeUrl.startsWith("#")) {
|
289
281
|
return null;
|
290
282
|
}
|
291
283
|
try {
|
292
284
|
const resolvedUrl = new URL2(relativeUrl, cssBaseContextUrl);
|
293
|
-
console.log(`[DEBUG resolveCssRelativeUrl] Resolved URL object href: "${resolvedUrl.href}"`);
|
294
285
|
return resolvedUrl.href;
|
295
286
|
} catch (error) {
|
296
287
|
logger?.warn(
|
297
288
|
`Failed to resolve CSS URL: "${relativeUrl}" relative to "${cssBaseContextUrl}": ${String(error)}`
|
298
289
|
);
|
299
|
-
console.error(`[DEBUG resolveCssRelativeUrl] Error resolving: ${String(error)}`);
|
300
290
|
return null;
|
301
291
|
}
|
302
292
|
}
|
303
293
|
async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
|
304
|
-
console.log(`[DEBUG fetchAsset] Attempting fetch for URL: ${resolvedUrl.href}`);
|
305
294
|
logger?.debug(`Attempting to fetch asset: ${resolvedUrl.href}`);
|
306
295
|
const protocol = resolvedUrl.protocol;
|
307
296
|
try {
|
308
297
|
if (protocol === "http:" || protocol === "https:") {
|
309
298
|
const response = await axiosNs.default.get(resolvedUrl.href, {
|
310
299
|
responseType: "arraybuffer",
|
300
|
+
// Fetch as binary data
|
311
301
|
timeout
|
302
|
+
// Apply network timeout
|
312
303
|
});
|
313
304
|
logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data?.byteLength ?? 0} bytes)`);
|
314
|
-
console.log(`[DEBUG fetchAsset] HTTP fetch SUCCESS for: ${resolvedUrl.href}, Status: ${response.status}`);
|
315
305
|
return Buffer.from(response.data);
|
316
306
|
} else if (protocol === "file:") {
|
317
307
|
let filePath;
|
318
308
|
try {
|
319
309
|
filePath = fileURLToPath(resolvedUrl);
|
320
310
|
} catch (e) {
|
321
|
-
console.error(`[DEBUG fetchAsset] fileURLToPath FAILED for: ${resolvedUrl.href}`, e);
|
322
311
|
logger?.error(`Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`);
|
323
312
|
return null;
|
324
313
|
}
|
325
314
|
const normalizedForLog = path2.normalize(filePath);
|
326
|
-
console.log(`[DEBUG fetchAsset] Attempting readFile with path: "${normalizedForLog}" (Original from URL: "${filePath}")`);
|
327
315
|
const data = await readFile(filePath);
|
328
|
-
console.log(`[DEBUG fetchAsset] readFile call SUCCEEDED for path: "${normalizedForLog}". Data length: ${data?.byteLength}`);
|
329
316
|
logger?.debug(`Read local file ${filePath} (${data.byteLength} bytes)`);
|
330
317
|
return data;
|
331
318
|
} else {
|
332
|
-
console.log(`[DEBUG fetchAsset] Unsupported protocol: ${protocol}`);
|
333
319
|
logger?.warn(`Unsupported protocol "${protocol}" in URL: ${resolvedUrl.href}`);
|
334
320
|
return null;
|
335
321
|
}
|
336
322
|
} catch (error) {
|
337
323
|
const failedId = protocol === "file:" ? path2.normalize(fileURLToPath(resolvedUrl)) : resolvedUrl.href;
|
338
|
-
|
339
|
-
|
340
|
-
const status =
|
341
|
-
const
|
342
|
-
const
|
343
|
-
const message = error.message;
|
344
|
-
const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}: Status ${status} - ${statusText}. Code: ${code}, Message: ${message}`;
|
324
|
+
if ((protocol === "http:" || protocol === "https:") && error?.isAxiosError === true) {
|
325
|
+
const axiosError = error;
|
326
|
+
const status = axiosError.response?.status ?? "N/A";
|
327
|
+
const code = axiosError.code ?? "N/A";
|
328
|
+
const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}: ${axiosError.message} (Code: ${code})`;
|
345
329
|
logger?.warn(logMessage);
|
346
|
-
}
|
347
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
330
|
+
} else if (protocol === "file:" && error instanceof Error) {
|
348
331
|
let failedPath = resolvedUrl.href;
|
349
332
|
try {
|
350
333
|
failedPath = fileURLToPath(resolvedUrl);
|
351
334
|
} catch {
|
352
335
|
}
|
353
336
|
failedPath = path2.normalize(failedPath);
|
354
|
-
if (error
|
337
|
+
if (error.code === "ENOENT") {
|
355
338
|
logger?.warn(`\u26A0\uFE0F File not found (ENOENT) for asset: ${failedPath}.`);
|
356
|
-
} else if (error
|
339
|
+
} else if (error.code === "EACCES") {
|
357
340
|
logger?.warn(`\u26A0\uFE0F Permission denied (EACCES) reading asset: ${failedPath}.`);
|
358
|
-
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
359
|
-
} else if (error instanceof Error) {
|
360
|
-
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
361
341
|
} else {
|
362
|
-
logger?.warn(`\u26A0\uFE0F
|
342
|
+
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
363
343
|
}
|
364
344
|
} else if (error instanceof Error) {
|
365
345
|
logger?.warn(`\u26A0\uFE0F An unexpected error occurred processing asset ${resolvedUrl.href}: ${error.message}`);
|
@@ -375,7 +355,7 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
|
|
375
355
|
const urlRegex = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
|
376
356
|
const importRegex = /@import\s+(?:url\(\s*(['"]?)(.*?)\1\s*\)|(['"])(.*?)\3)\s*;/gi;
|
377
357
|
const processFoundUrl = (rawUrl, ruleType) => {
|
378
|
-
if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:")) return;
|
358
|
+
if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:") || rawUrl.startsWith("#")) return;
|
379
359
|
const resolvedUrl = resolveCssRelativeUrl(rawUrl, cssBaseContextUrl, logger);
|
380
360
|
if (resolvedUrl && !processedInThisParse.has(resolvedUrl)) {
|
381
361
|
processedInThisParse.add(resolvedUrl);
|
@@ -383,7 +363,7 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
|
|
383
363
|
newlyDiscovered.push({
|
384
364
|
type: assetType,
|
385
365
|
url: resolvedUrl,
|
386
|
-
//
|
366
|
+
// Store the resolved absolute URL string
|
387
367
|
content: void 0
|
388
368
|
// Content will be fetched later if needed
|
389
369
|
});
|
@@ -420,7 +400,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
420
400
|
continue;
|
421
401
|
}
|
422
402
|
const urlToQueue = resolvedUrlObj.href;
|
423
|
-
if (!
|
403
|
+
if (!processedOrQueuedUrls.has(urlToQueue)) {
|
424
404
|
processedOrQueuedUrls.add(urlToQueue);
|
425
405
|
const { assetType: guessedType } = guessMimeType(urlToQueue);
|
426
406
|
const initialType = asset.type ?? guessedType;
|
@@ -429,10 +409,9 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
429
409
|
// Use the resolved URL
|
430
410
|
type: initialType,
|
431
411
|
content: void 0
|
412
|
+
// Content is initially undefined
|
432
413
|
});
|
433
414
|
logger?.debug(` -> Queued initial asset: ${urlToQueue} (Original raw: ${asset.url})`);
|
434
|
-
} else if (urlToQueue.startsWith("data:")) {
|
435
|
-
logger?.debug(` -> Skipping data URI: ${urlToQueue.substring(0, 50)}...`);
|
436
415
|
} else {
|
437
416
|
logger?.debug(` -> Skipping already processed/queued initial asset: ${urlToQueue}`);
|
438
417
|
}
|
@@ -552,7 +531,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
552
531
|
const newlyDiscoveredAssets = extractUrlsFromCSS(
|
553
532
|
cssContentForParsing,
|
554
533
|
cssBaseContextUrl,
|
555
|
-
// Use CSS file's
|
534
|
+
// Use the CSS file's own URL as the base
|
556
535
|
logger
|
557
536
|
);
|
558
537
|
if (newlyDiscoveredAssets.length > 0) {
|
@@ -573,7 +552,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
573
552
|
}
|
574
553
|
}
|
575
554
|
}
|
576
|
-
const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ?
|
555
|
+
const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ? `${MAX_ASSET_EXTRACTION_ITERATIONS}+ (limit hit)` : iterationCount;
|
577
556
|
logger?.info(`\u2705 Asset extraction COMPLETE! Found ${finalAssetsMap.size} unique assets in ${finalIterationCount} iterations.`);
|
578
557
|
return {
|
579
558
|
htmlContent: parsed.htmlContent,
|