portapack 0.2.1 → 0.3.0
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/CHANGELOG.md +12 -0
- package/README.md +83 -216
- package/dist/cli/{cli-entry.js → cli-entry.cjs} +626 -498
- package/dist/cli/cli-entry.cjs.map +1 -0
- package/dist/index.d.ts +51 -56
- package/dist/index.js +523 -443
- package/dist/index.js.map +1 -1
- package/docs/cli.md +158 -42
- package/jest.config.ts +18 -8
- package/jest.setup.cjs +66 -146
- package/package.json +5 -5
- package/src/cli/cli-entry.ts +15 -15
- package/src/cli/cli.ts +130 -119
- package/src/core/bundler.ts +174 -63
- package/src/core/extractor.ts +243 -203
- package/src/core/web-fetcher.ts +205 -141
- package/src/index.ts +161 -224
- package/tests/unit/cli/cli-entry.test.ts +66 -77
- package/tests/unit/cli/cli.test.ts +243 -145
- package/tests/unit/core/bundler.test.ts +334 -258
- package/tests/unit/core/extractor.test.ts +391 -1051
- package/tests/unit/core/minifier.test.ts +130 -221
- package/tests/unit/core/packer.test.ts +255 -106
- package/tests/unit/core/parser.test.ts +89 -458
- package/tests/unit/core/web-fetcher.test.ts +330 -285
- package/tests/unit/index.test.ts +206 -300
- package/tests/unit/utils/logger.test.ts +32 -28
- package/tsconfig.jest.json +7 -7
- package/tsup.config.ts +34 -29
- package/dist/cli/cli-entry.js.map +0 -1
- package/output.html +0 -1
- package/site-packed.html +0 -1
- package/test-output.html +0 -0
@@ -1,6 +1,11 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
+
"use strict";
|
3
|
+
var __create = Object.create;
|
2
4
|
var __defProp = Object.defineProperty;
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
3
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
4
9
|
var __esm = (fn, res) => function __init() {
|
5
10
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
6
11
|
};
|
@@ -8,6 +13,23 @@ var __export = (target, all) => {
|
|
8
13
|
for (var name in all)
|
9
14
|
__defProp(target, name, { get: all[name], enumerable: true });
|
10
15
|
};
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
18
|
+
for (let key of __getOwnPropNames(from))
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
21
|
+
}
|
22
|
+
return to;
|
23
|
+
};
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
30
|
+
mod
|
31
|
+
));
|
32
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
11
33
|
|
12
34
|
// src/types.ts
|
13
35
|
var LogLevel;
|
@@ -26,15 +48,14 @@ var init_types = __esm({
|
|
26
48
|
});
|
27
49
|
|
28
50
|
// src/cli/options.ts
|
29
|
-
import { Command, Option } from "commander";
|
30
51
|
function parseRecursiveValue(val) {
|
31
52
|
if (val === void 0) return true;
|
32
53
|
const parsed = parseInt(val, 10);
|
33
54
|
return isNaN(parsed) || parsed < 0 ? true : parsed;
|
34
55
|
}
|
35
56
|
function parseOptions(argv = process.argv) {
|
36
|
-
const program = new Command();
|
37
|
-
program.name("portapack").version("0.0.0").description("\u{1F4E6} Bundle HTML and its dependencies into a portable file").argument("[input]", "Input HTML file or URL").option("-o, --output <file>", "Output file path").option("-m, --minify", "Enable all minification (HTML, CSS, JS)").option("--no-minify", "Disable all minification").option("--no-minify-html", "Disable HTML minification").option("--no-minify-css", "Disable CSS minification").option("--no-minify-js", "Disable JavaScript minification").option("-e, --embed-assets", "Embed assets as data URIs").option("--no-embed-assets", "Keep asset links relative/absolute").option("-r, --recursive [depth]", "Recursively crawl site (optional depth)", parseRecursiveValue).option("--max-depth <n>", "Set max depth for recursive crawl (alias for -r <n>)", parseInt).option("-b, --base-url <url>", "Base URL for resolving relative links").option("-d, --dry-run", "Run without writing output file").option("-v, --verbose", "Enable verbose (debug) logging").addOption(new Option("--log-level <level>", "Set logging level").choices(logLevels));
|
57
|
+
const program = new import_commander.Command();
|
58
|
+
program.name("portapack").version("0.0.0").description("\u{1F4E6} Bundle HTML and its dependencies into a portable file").argument("[input]", "Input HTML file or URL").option("-o, --output <file>", "Output file path").option("-m, --minify", "Enable all minification (HTML, CSS, JS)").option("--no-minify", "Disable all minification").option("--no-minify-html", "Disable HTML minification").option("--no-minify-css", "Disable CSS minification").option("--no-minify-js", "Disable JavaScript minification").option("-e, --embed-assets", "Embed assets as data URIs").option("--no-embed-assets", "Keep asset links relative/absolute").option("-r, --recursive [depth]", "Recursively crawl site (optional depth)", parseRecursiveValue).option("--max-depth <n>", "Set max depth for recursive crawl (alias for -r <n>)", parseInt).option("-b, --base-url <url>", "Base URL for resolving relative links").option("-d, --dry-run", "Run without writing output file").option("-v, --verbose", "Enable verbose (debug) logging").addOption(new import_commander.Option("--log-level <level>", "Set logging level").choices(logLevels));
|
38
59
|
program.parse(argv);
|
39
60
|
const opts = program.opts();
|
40
61
|
const inputArg = program.args.length > 0 ? program.args[0] : void 0;
|
@@ -110,17 +131,130 @@ function parseOptions(argv = process.argv) {
|
|
110
131
|
// minifyHtml, minifyCss, minifyJs (commander's raw boolean flags)
|
111
132
|
};
|
112
133
|
}
|
113
|
-
var logLevels;
|
134
|
+
var import_commander, logLevels;
|
114
135
|
var init_options = __esm({
|
115
136
|
"src/cli/options.ts"() {
|
116
137
|
"use strict";
|
138
|
+
import_commander = require("commander");
|
117
139
|
init_types();
|
118
140
|
logLevels = ["debug", "info", "warn", "error", "silent", "none"];
|
119
141
|
}
|
120
142
|
});
|
121
143
|
|
144
|
+
// src/utils/logger.ts
|
145
|
+
var Logger;
|
146
|
+
var init_logger = __esm({
|
147
|
+
"src/utils/logger.ts"() {
|
148
|
+
"use strict";
|
149
|
+
init_types();
|
150
|
+
Logger = class _Logger {
|
151
|
+
/** The current minimum log level required for a message to be output. */
|
152
|
+
level;
|
153
|
+
/**
|
154
|
+
* Creates a new Logger instance.
|
155
|
+
* Defaults to LogLevel.INFO if no level is provided.
|
156
|
+
*
|
157
|
+
* @param {LogLevel} [level=LogLevel.INFO] - The initial log level for this logger instance.
|
158
|
+
* Must be one of the values from the LogLevel enum.
|
159
|
+
*/
|
160
|
+
constructor(level = 3 /* INFO */) {
|
161
|
+
this.level = level !== void 0 && LogLevel[level] !== void 0 ? level : 3 /* INFO */;
|
162
|
+
}
|
163
|
+
/**
|
164
|
+
* Updates the logger's current level. Messages below this level will be suppressed.
|
165
|
+
*
|
166
|
+
* @param {LogLevel} level - The new log level to set. Must be a LogLevel enum member.
|
167
|
+
*/
|
168
|
+
setLevel(level) {
|
169
|
+
this.level = level;
|
170
|
+
}
|
171
|
+
/**
|
172
|
+
* Logs a debug message if the current log level is DEBUG or higher.
|
173
|
+
*
|
174
|
+
* @param {string} message - The debug message string.
|
175
|
+
*/
|
176
|
+
debug(message) {
|
177
|
+
if (this.level >= 4 /* DEBUG */) {
|
178
|
+
console.debug(`[DEBUG] ${message}`);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
/**
|
182
|
+
* Logs an informational message if the current log level is INFO or higher.
|
183
|
+
*
|
184
|
+
* @param {string} message - The informational message string.
|
185
|
+
*/
|
186
|
+
info(message) {
|
187
|
+
if (this.level >= 3 /* INFO */) {
|
188
|
+
console.info(`[INFO] ${message}`);
|
189
|
+
}
|
190
|
+
}
|
191
|
+
/**
|
192
|
+
* Logs a warning message if the current log level is WARN or higher.
|
193
|
+
*
|
194
|
+
* @param {string} message - The warning message string.
|
195
|
+
*/
|
196
|
+
warn(message) {
|
197
|
+
if (this.level >= 2 /* WARN */) {
|
198
|
+
console.warn(`[WARN] ${message}`);
|
199
|
+
}
|
200
|
+
}
|
201
|
+
/**
|
202
|
+
* Logs an error message if the current log level is ERROR or higher.
|
203
|
+
*
|
204
|
+
* @param {string} message - The error message string.
|
205
|
+
*/
|
206
|
+
error(message) {
|
207
|
+
if (this.level >= 1 /* ERROR */) {
|
208
|
+
console.error(`[ERROR] ${message}`);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
/**
|
212
|
+
* Static factory method to create a Logger instance based on a simple boolean `verbose` flag.
|
213
|
+
*
|
214
|
+
* @static
|
215
|
+
* @param {{ verbose?: boolean }} [options={}] - An object potentially containing a `verbose` flag.
|
216
|
+
* @returns {Logger} A new Logger instance set to LogLevel.DEBUG if options.verbose is true,
|
217
|
+
* otherwise set to LogLevel.INFO.
|
218
|
+
*/
|
219
|
+
static fromVerboseFlag(options = {}) {
|
220
|
+
return new _Logger(options.verbose ? 4 /* DEBUG */ : 3 /* INFO */);
|
221
|
+
}
|
222
|
+
/**
|
223
|
+
* Static factory method to create a Logger instance based on a LogLevel string name.
|
224
|
+
* Useful for creating a logger from config files or environments variables.
|
225
|
+
*
|
226
|
+
* @static
|
227
|
+
* @param {string | undefined} levelName - The name of the log level (e.g., 'debug', 'info', 'warn', 'error', 'silent'/'none'). Case-insensitive.
|
228
|
+
* @param {LogLevel} [defaultLevel=LogLevel.INFO] - The level to use if levelName is invalid or undefined.
|
229
|
+
* @returns {Logger} A new Logger instance set to the corresponding LogLevel.
|
230
|
+
*/
|
231
|
+
static fromLevelName(levelName, defaultLevel = 3 /* INFO */) {
|
232
|
+
if (!levelName) {
|
233
|
+
return new _Logger(defaultLevel);
|
234
|
+
}
|
235
|
+
switch (levelName.toLowerCase()) {
|
236
|
+
// Return enum members
|
237
|
+
case "debug":
|
238
|
+
return new _Logger(4 /* DEBUG */);
|
239
|
+
case "info":
|
240
|
+
return new _Logger(3 /* INFO */);
|
241
|
+
case "warn":
|
242
|
+
return new _Logger(2 /* WARN */);
|
243
|
+
case "error":
|
244
|
+
return new _Logger(1 /* ERROR */);
|
245
|
+
case "silent":
|
246
|
+
case "none":
|
247
|
+
return new _Logger(0 /* NONE */);
|
248
|
+
default:
|
249
|
+
console.warn(`[Logger] Invalid log level name "${levelName}". Defaulting to ${LogLevel[defaultLevel]}.`);
|
250
|
+
return new _Logger(defaultLevel);
|
251
|
+
}
|
252
|
+
}
|
253
|
+
};
|
254
|
+
}
|
255
|
+
});
|
256
|
+
|
122
257
|
// src/utils/mime.ts
|
123
|
-
import path from "path";
|
124
258
|
function guessMimeType(urlOrPath) {
|
125
259
|
if (!urlOrPath) {
|
126
260
|
return DEFAULT_MIME_TYPE;
|
@@ -128,16 +262,17 @@ function guessMimeType(urlOrPath) {
|
|
128
262
|
let ext = "";
|
129
263
|
try {
|
130
264
|
const parsedUrl = new URL(urlOrPath);
|
131
|
-
ext =
|
265
|
+
ext = import_path.default.extname(parsedUrl.pathname).toLowerCase();
|
132
266
|
} catch {
|
133
|
-
ext =
|
267
|
+
ext = import_path.default.extname(urlOrPath).toLowerCase();
|
134
268
|
}
|
135
269
|
return MIME_MAP[ext] || DEFAULT_MIME_TYPE;
|
136
270
|
}
|
137
|
-
var MIME_MAP, DEFAULT_MIME_TYPE;
|
271
|
+
var import_path, MIME_MAP, DEFAULT_MIME_TYPE;
|
138
272
|
var init_mime = __esm({
|
139
273
|
"src/utils/mime.ts"() {
|
140
274
|
"use strict";
|
275
|
+
import_path = __toESM(require("path"), 1);
|
141
276
|
MIME_MAP = {
|
142
277
|
// CSS
|
143
278
|
".css": { mime: "text/css", assetType: "css" },
|
@@ -181,84 +316,7 @@ var init_mime = __esm({
|
|
181
316
|
}
|
182
317
|
});
|
183
318
|
|
184
|
-
// src/core/parser.ts
|
185
|
-
import { readFile } from "fs/promises";
|
186
|
-
import * as cheerio from "cheerio";
|
187
|
-
async function parseHTML(entryFilePath, logger) {
|
188
|
-
logger?.debug(`Parsing HTML file: ${entryFilePath}`);
|
189
|
-
let htmlContent;
|
190
|
-
try {
|
191
|
-
htmlContent = await readFile(entryFilePath, "utf-8");
|
192
|
-
logger?.debug(`Successfully read HTML file (${Buffer.byteLength(htmlContent)} bytes).`);
|
193
|
-
} catch (err) {
|
194
|
-
logger?.error(`Failed to read HTML file "${entryFilePath}": ${err.message}`);
|
195
|
-
throw new Error(`Could not read input HTML file: ${entryFilePath}`, { cause: err });
|
196
|
-
}
|
197
|
-
const $ = cheerio.load(htmlContent);
|
198
|
-
const assets = [];
|
199
|
-
const addedUrls = /* @__PURE__ */ new Set();
|
200
|
-
const addAsset = (url, forcedType) => {
|
201
|
-
if (!url || url.trim() === "" || url.startsWith("data:")) {
|
202
|
-
return;
|
203
|
-
}
|
204
|
-
if (!addedUrls.has(url)) {
|
205
|
-
addedUrls.add(url);
|
206
|
-
const mimeInfo = guessMimeType(url);
|
207
|
-
const type = forcedType ?? mimeInfo.assetType;
|
208
|
-
assets.push({ type, url });
|
209
|
-
logger?.debug(`Discovered asset: Type='${type}', URL='${url}'`);
|
210
|
-
} else {
|
211
|
-
logger?.debug(`Skipping duplicate asset URL: ${url}`);
|
212
|
-
}
|
213
|
-
};
|
214
|
-
logger?.debug("Extracting assets from HTML tags...");
|
215
|
-
$('link[rel="stylesheet"][href]').each((_, el) => {
|
216
|
-
addAsset($(el).attr("href"), "css");
|
217
|
-
});
|
218
|
-
$("script[src]").each((_, el) => {
|
219
|
-
addAsset($(el).attr("src"), "js");
|
220
|
-
});
|
221
|
-
$("img[src]").each((_, el) => addAsset($(el).attr("src"), "image"));
|
222
|
-
$('input[type="image"][src]').each((_, el) => addAsset($(el).attr("src"), "image"));
|
223
|
-
$("img[srcset], picture source[srcset]").each((_, el) => {
|
224
|
-
const srcset = $(el).attr("srcset");
|
225
|
-
srcset?.split(",").forEach((entry) => {
|
226
|
-
const [url] = entry.trim().split(/\s+/);
|
227
|
-
addAsset(url, "image");
|
228
|
-
});
|
229
|
-
});
|
230
|
-
$("video[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
231
|
-
$("video[poster]").each((_, el) => addAsset($(el).attr("poster"), "image"));
|
232
|
-
$("audio[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
233
|
-
$("video > source[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
234
|
-
$("audio > source[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
235
|
-
$("link[href]").filter((_, el) => {
|
236
|
-
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
237
|
-
return ["icon", "shortcut icon", "apple-touch-icon", "manifest"].includes(rel);
|
238
|
-
}).each((_, el) => {
|
239
|
-
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
240
|
-
const isIcon = ["icon", "shortcut icon", "apple-touch-icon"].includes(rel);
|
241
|
-
addAsset($(el).attr("href"), isIcon ? "image" : void 0);
|
242
|
-
});
|
243
|
-
$('link[rel="preload"][as="font"][href]').each((_, el) => {
|
244
|
-
addAsset($(el).attr("href"), "font");
|
245
|
-
});
|
246
|
-
logger?.info(`HTML parsing complete. Discovered ${assets.length} unique asset links.`);
|
247
|
-
return { htmlContent, assets };
|
248
|
-
}
|
249
|
-
var init_parser = __esm({
|
250
|
-
"src/core/parser.ts"() {
|
251
|
-
"use strict";
|
252
|
-
init_mime();
|
253
|
-
}
|
254
|
-
});
|
255
|
-
|
256
319
|
// src/core/extractor.ts
|
257
|
-
import { readFile as readFile2 } from "fs/promises";
|
258
|
-
import * as fs from "fs";
|
259
|
-
import path2 from "path";
|
260
|
-
import { fileURLToPath, URL as URL2 } from "url";
|
261
|
-
import * as axios from "axios";
|
262
320
|
function isUtf8DecodingLossy(originalBuffer, decodedString) {
|
263
321
|
try {
|
264
322
|
const reEncodedBuffer = Buffer.from(decodedString, "utf-8");
|
@@ -268,6 +326,7 @@ function isUtf8DecodingLossy(originalBuffer, decodedString) {
|
|
268
326
|
}
|
269
327
|
}
|
270
328
|
function determineBaseUrl(inputPathOrUrl, logger) {
|
329
|
+
console.log(`[DEBUG determineBaseUrl] Input: "${inputPathOrUrl}"`);
|
271
330
|
logger?.debug(`Determining base URL for input: ${inputPathOrUrl}`);
|
272
331
|
if (!inputPathOrUrl) {
|
273
332
|
logger?.warn("Cannot determine base URL: inputPathOrUrl is empty or invalid.");
|
@@ -275,55 +334,52 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
275
334
|
}
|
276
335
|
try {
|
277
336
|
if (/^https?:\/\//i.test(inputPathOrUrl)) {
|
278
|
-
const url = new
|
337
|
+
const url = new import_url.URL(inputPathOrUrl);
|
279
338
|
url.pathname = url.pathname.substring(0, url.pathname.lastIndexOf("/") + 1);
|
280
339
|
url.search = "";
|
281
340
|
url.hash = "";
|
282
341
|
const baseUrl = url.href;
|
283
342
|
logger?.debug(`Determined remote base URL: ${baseUrl}`);
|
343
|
+
console.log(`[DEBUG determineBaseUrl] Determined Remote URL: "${baseUrl}"`);
|
284
344
|
return baseUrl;
|
285
345
|
} else if (inputPathOrUrl.includes("://") && !inputPathOrUrl.startsWith("file:")) {
|
286
346
|
logger?.warn(`Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`);
|
347
|
+
console.log(`[DEBUG determineBaseUrl] Unsupported protocol.`);
|
287
348
|
return void 0;
|
288
349
|
} else {
|
289
|
-
let
|
350
|
+
let resourcePath;
|
351
|
+
let isInputLikelyDirectory = false;
|
290
352
|
if (inputPathOrUrl.startsWith("file:")) {
|
291
|
-
|
292
|
-
|
293
|
-
} catch (e) {
|
294
|
-
logger?.error(`\u{1F480} Failed to convert file URL "${inputPathOrUrl}" to path: ${e.message}`);
|
295
|
-
return void 0;
|
296
|
-
}
|
353
|
+
resourcePath = (0, import_url.fileURLToPath)(inputPathOrUrl);
|
354
|
+
isInputLikelyDirectory = inputPathOrUrl.endsWith("/");
|
297
355
|
} else {
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
} catch (statError) {
|
304
|
-
if (statError instanceof Error && statError.code === "ENOENT") {
|
305
|
-
logger?.debug(`Path "${absolutePath}" not found. Assuming input represents a file, using its parent directory as base.`);
|
306
|
-
} else {
|
307
|
-
logger?.warn(`Could not stat local path "${absolutePath}" during base URL determination: ${statError instanceof Error ? statError.message : String(statError)}. Assuming input represents a file.`);
|
356
|
+
resourcePath = import_path2.default.resolve(inputPathOrUrl);
|
357
|
+
try {
|
358
|
+
isInputLikelyDirectory = fs.statSync(resourcePath).isDirectory();
|
359
|
+
} catch {
|
360
|
+
isInputLikelyDirectory = false;
|
308
361
|
}
|
309
|
-
isDirectory = false;
|
310
362
|
}
|
311
|
-
|
312
|
-
|
363
|
+
console.log(`[DEBUG determineBaseUrl] resourcePath: "${resourcePath}", isInputLikelyDirectory: ${isInputLikelyDirectory}`);
|
364
|
+
const baseDirPath = isInputLikelyDirectory ? resourcePath : import_path2.default.dirname(resourcePath);
|
365
|
+
console.log(`[DEBUG determineBaseUrl] Calculated baseDirPath: "${baseDirPath}"`);
|
366
|
+
let normalizedPathForURL = baseDirPath.replace(/\\/g, "/");
|
313
367
|
if (/^[A-Z]:\//i.test(normalizedPathForURL) && !normalizedPathForURL.startsWith("/")) {
|
314
368
|
normalizedPathForURL = "/" + normalizedPathForURL;
|
315
369
|
}
|
316
|
-
|
317
|
-
|
318
|
-
if (!fileUrlString.endsWith("/")) {
|
319
|
-
fileUrlString += "/";
|
370
|
+
if (!normalizedPathForURL.endsWith("/")) {
|
371
|
+
normalizedPathForURL += "/";
|
320
372
|
}
|
321
|
-
|
373
|
+
const fileUrl = new import_url.URL("file://" + normalizedPathForURL);
|
374
|
+
const fileUrlString = fileUrl.href;
|
375
|
+
logger?.debug(`Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`);
|
376
|
+
console.log(`[DEBUG determineBaseUrl] Determined File URL: "${fileUrlString}"`);
|
322
377
|
return fileUrlString;
|
323
378
|
}
|
324
379
|
} catch (error) {
|
325
380
|
const message = error instanceof Error ? error.message : String(error);
|
326
|
-
|
381
|
+
console.error(`[DEBUG determineBaseUrl] Error determining base URL: ${message}`);
|
382
|
+
logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`);
|
327
383
|
return void 0;
|
328
384
|
}
|
329
385
|
}
|
@@ -335,7 +391,7 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
335
391
|
let resolvableUrl = trimmedUrl;
|
336
392
|
if (resolvableUrl.startsWith("//") && baseContextUrl) {
|
337
393
|
try {
|
338
|
-
const base = new
|
394
|
+
const base = new import_url.URL(baseContextUrl);
|
339
395
|
resolvableUrl = base.protocol + resolvableUrl;
|
340
396
|
} catch (e) {
|
341
397
|
logger?.warn(`Could not extract protocol from base "${baseContextUrl}" for protocol-relative URL "${trimmedUrl}". Skipping.`);
|
@@ -343,7 +399,11 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
343
399
|
}
|
344
400
|
}
|
345
401
|
try {
|
346
|
-
const resolved = new
|
402
|
+
const resolved = new import_url.URL(resolvableUrl, baseContextUrl);
|
403
|
+
if (!["http:", "https:", "file:"].includes(resolved.protocol)) {
|
404
|
+
logger?.debug(`Skipping asset with unsupported protocol: ${resolved.href}`);
|
405
|
+
return null;
|
406
|
+
}
|
347
407
|
return resolved;
|
348
408
|
} catch (error) {
|
349
409
|
const message = error instanceof Error ? error.message : String(error);
|
@@ -356,83 +416,78 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
356
416
|
}
|
357
417
|
}
|
358
418
|
function resolveCssRelativeUrl(relativeUrl, cssBaseContextUrl, logger) {
|
359
|
-
|
419
|
+
console.log(`[DEBUG resolveCssRelativeUrl] Input: relative="${relativeUrl}", base="${cssBaseContextUrl}"`);
|
420
|
+
if (!relativeUrl || relativeUrl.startsWith("data:") || relativeUrl.startsWith("#")) {
|
360
421
|
return null;
|
361
422
|
}
|
362
423
|
try {
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
try {
|
367
|
-
const stat = fs.statSync(basePath);
|
368
|
-
if (stat.isDirectory()) {
|
369
|
-
cssDir = basePath;
|
370
|
-
} else {
|
371
|
-
cssDir = path2.dirname(basePath);
|
372
|
-
}
|
373
|
-
} catch {
|
374
|
-
cssDir = path2.dirname(basePath);
|
375
|
-
}
|
376
|
-
let resolvedPath = path2.resolve(cssDir, relativeUrl);
|
377
|
-
resolvedPath = resolvedPath.replace(/\\/g, "/");
|
378
|
-
if (/^[A-Z]:/i.test(resolvedPath) && !resolvedPath.startsWith("/")) {
|
379
|
-
resolvedPath = "/" + resolvedPath;
|
380
|
-
}
|
381
|
-
return `file://${resolvedPath}`;
|
382
|
-
} else {
|
383
|
-
return new URL2(relativeUrl, cssBaseContextUrl).href;
|
384
|
-
}
|
424
|
+
const resolvedUrl = new import_url.URL(relativeUrl, cssBaseContextUrl);
|
425
|
+
console.log(`[DEBUG resolveCssRelativeUrl] Resolved URL object href: "${resolvedUrl.href}"`);
|
426
|
+
return resolvedUrl.href;
|
385
427
|
} catch (error) {
|
386
428
|
logger?.warn(
|
387
|
-
`Failed to resolve CSS URL: "${relativeUrl}"
|
429
|
+
`Failed to resolve CSS URL: "${relativeUrl}" relative to "${cssBaseContextUrl}": ${String(error)}`
|
388
430
|
);
|
431
|
+
console.error(`[DEBUG resolveCssRelativeUrl] Error resolving: ${String(error)}`);
|
389
432
|
return null;
|
390
433
|
}
|
391
434
|
}
|
392
435
|
async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
|
436
|
+
console.log(`[DEBUG fetchAsset] Attempting fetch for URL: ${resolvedUrl.href}`);
|
393
437
|
logger?.debug(`Attempting to fetch asset: ${resolvedUrl.href}`);
|
394
438
|
const protocol = resolvedUrl.protocol;
|
395
439
|
try {
|
396
440
|
if (protocol === "http:" || protocol === "https:") {
|
397
|
-
const response = await
|
441
|
+
const response = await axiosNs.default.get(resolvedUrl.href, {
|
398
442
|
responseType: "arraybuffer",
|
399
443
|
timeout
|
400
444
|
});
|
401
|
-
logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data
|
445
|
+
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}`);
|
402
447
|
return Buffer.from(response.data);
|
403
448
|
} else if (protocol === "file:") {
|
404
449
|
let filePath;
|
405
450
|
try {
|
406
|
-
filePath = fileURLToPath(resolvedUrl);
|
451
|
+
filePath = (0, import_url.fileURLToPath)(resolvedUrl);
|
407
452
|
} catch (e) {
|
453
|
+
console.error(`[DEBUG fetchAsset] fileURLToPath FAILED for: ${resolvedUrl.href}`, e);
|
408
454
|
logger?.error(`Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`);
|
409
455
|
return null;
|
410
456
|
}
|
411
|
-
const
|
457
|
+
const normalizedForLog = import_path2.default.normalize(filePath);
|
458
|
+
console.log(`[DEBUG fetchAsset] Attempting readFile with path: "${normalizedForLog}" (Original from URL: "${filePath}")`);
|
459
|
+
const data = await (0, import_promises.readFile)(filePath);
|
460
|
+
console.log(`[DEBUG fetchAsset] readFile call SUCCEEDED for path: "${normalizedForLog}". Data length: ${data?.byteLength}`);
|
412
461
|
logger?.debug(`Read local file ${filePath} (${data.byteLength} bytes)`);
|
413
462
|
return data;
|
414
463
|
} else {
|
464
|
+
console.log(`[DEBUG fetchAsset] Unsupported protocol: ${protocol}`);
|
415
465
|
logger?.warn(`Unsupported protocol "${protocol}" in URL: ${resolvedUrl.href}`);
|
416
466
|
return null;
|
417
467
|
}
|
418
468
|
} catch (error) {
|
419
|
-
|
469
|
+
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)) {
|
420
472
|
const status = error.response?.status ?? "N/A";
|
421
473
|
const statusText = error.response?.statusText ?? "Error";
|
422
474
|
const code = error.code ?? "N/A";
|
423
475
|
const message = error.message;
|
424
476
|
const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}: Status ${status} - ${statusText}. Code: ${code}, Message: ${message}`;
|
425
477
|
logger?.warn(logMessage);
|
426
|
-
}
|
478
|
+
}
|
479
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
427
480
|
let failedPath = resolvedUrl.href;
|
428
481
|
try {
|
429
|
-
failedPath = fileURLToPath(resolvedUrl);
|
482
|
+
failedPath = (0, import_url.fileURLToPath)(resolvedUrl);
|
430
483
|
} catch {
|
431
484
|
}
|
485
|
+
failedPath = import_path2.default.normalize(failedPath);
|
432
486
|
if (error instanceof Error && error.code === "ENOENT") {
|
433
487
|
logger?.warn(`\u26A0\uFE0F File not found (ENOENT) for asset: ${failedPath}.`);
|
434
488
|
} else if (error instanceof Error && error.code === "EACCES") {
|
435
489
|
logger?.warn(`\u26A0\uFE0F Permission denied (EACCES) reading asset: ${failedPath}.`);
|
490
|
+
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
436
491
|
} else if (error instanceof Error) {
|
437
492
|
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
438
493
|
} else {
|
@@ -460,14 +515,13 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
|
|
460
515
|
newlyDiscovered.push({
|
461
516
|
type: assetType,
|
462
517
|
url: resolvedUrl,
|
463
|
-
// The resolved URL string
|
518
|
+
// The resolved absolute URL string
|
464
519
|
content: void 0
|
520
|
+
// Content will be fetched later if needed
|
465
521
|
});
|
466
522
|
logger?.debug(`Discovered nested ${assetType} asset (${ruleType}) in CSS ${cssBaseContextUrl}: ${resolvedUrl}`);
|
467
523
|
}
|
468
524
|
};
|
469
|
-
urlRegex.lastIndex = 0;
|
470
|
-
importRegex.lastIndex = 0;
|
471
525
|
let match;
|
472
526
|
while ((match = urlRegex.exec(cssContent)) !== null) {
|
473
527
|
processFoundUrl(match[2], "url()");
|
@@ -483,23 +537,28 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
483
537
|
const initialAssets = parsed.assets || [];
|
484
538
|
const finalAssetsMap = /* @__PURE__ */ new Map();
|
485
539
|
let assetsToProcess = [];
|
540
|
+
const processedOrQueuedUrls = /* @__PURE__ */ new Set();
|
486
541
|
const htmlBaseContextUrl = determineBaseUrl(inputPathOrUrl || "", logger);
|
487
542
|
if (!htmlBaseContextUrl && initialAssets.some((a) => !/^[a-z]+:/i.test(a.url) && !a.url.startsWith("data:") && !a.url.startsWith("#") && !a.url.startsWith("/"))) {
|
488
543
|
logger?.warn("\u{1F6A8} No valid base path/URL determined for the HTML source! Resolution of relative asset paths from HTML may fail.");
|
489
544
|
} else if (htmlBaseContextUrl) {
|
490
545
|
logger?.debug(`Using HTML base context URL: ${htmlBaseContextUrl}`);
|
491
546
|
}
|
492
|
-
const processedOrQueuedUrls = /* @__PURE__ */ new Set();
|
493
547
|
logger?.debug(`Queueing ${initialAssets.length} initial assets parsed from HTML...`);
|
494
548
|
for (const asset of initialAssets) {
|
495
549
|
const resolvedUrlObj = resolveAssetUrl(asset.url, htmlBaseContextUrl, logger);
|
496
|
-
|
550
|
+
if (!resolvedUrlObj) {
|
551
|
+
logger?.debug(` -> Skipping initial asset with unresolvable/ignorable URL: ${asset.url}`);
|
552
|
+
continue;
|
553
|
+
}
|
554
|
+
const urlToQueue = resolvedUrlObj.href;
|
497
555
|
if (!urlToQueue.startsWith("data:") && !processedOrQueuedUrls.has(urlToQueue)) {
|
498
556
|
processedOrQueuedUrls.add(urlToQueue);
|
499
557
|
const { assetType: guessedType } = guessMimeType(urlToQueue);
|
500
558
|
const initialType = asset.type ?? guessedType;
|
501
559
|
assetsToProcess.push({
|
502
560
|
url: urlToQueue,
|
561
|
+
// Use the resolved URL
|
503
562
|
type: initialType,
|
504
563
|
content: void 0
|
505
564
|
});
|
@@ -507,7 +566,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
507
566
|
} else if (urlToQueue.startsWith("data:")) {
|
508
567
|
logger?.debug(` -> Skipping data URI: ${urlToQueue.substring(0, 50)}...`);
|
509
568
|
} else {
|
510
|
-
logger?.debug(` -> Skipping already queued initial asset: ${urlToQueue}`);
|
569
|
+
logger?.debug(` -> Skipping already processed/queued initial asset: ${urlToQueue}`);
|
511
570
|
}
|
512
571
|
}
|
513
572
|
let iterationCount = 0;
|
@@ -540,7 +599,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
540
599
|
let assetUrlObj = null;
|
541
600
|
if (needsFetching) {
|
542
601
|
try {
|
543
|
-
assetUrlObj = new
|
602
|
+
assetUrlObj = new import_url.URL(asset.url);
|
544
603
|
} catch (urlError) {
|
545
604
|
logger?.warn(`Cannot create URL object for "${asset.url}", skipping fetch. Error: ${urlError instanceof Error ? urlError.message : String(urlError)}`);
|
546
605
|
finalAssetsMap.set(asset.url, { ...asset, content: void 0 });
|
@@ -578,7 +637,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
578
637
|
cssContentForParsing = textContent;
|
579
638
|
}
|
580
639
|
} else {
|
581
|
-
logger?.warn(`Could not decode ${asset.type} ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`);
|
640
|
+
logger?.warn(`Could not decode ${asset.type} asset ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`);
|
582
641
|
cssContentForParsing = void 0;
|
583
642
|
if (embedAssets) {
|
584
643
|
finalContent = `data:${effectiveMime};base64,${assetContentBuffer.toString("base64")}`;
|
@@ -625,6 +684,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
625
684
|
const newlyDiscoveredAssets = extractUrlsFromCSS(
|
626
685
|
cssContentForParsing,
|
627
686
|
cssBaseContextUrl,
|
687
|
+
// Use CSS file's base URL
|
628
688
|
logger
|
629
689
|
);
|
630
690
|
if (newlyDiscoveredAssets.length > 0) {
|
@@ -652,10 +712,15 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
652
712
|
assets: Array.from(finalAssetsMap.values())
|
653
713
|
};
|
654
714
|
}
|
655
|
-
var TEXT_ASSET_TYPES, BINARY_ASSET_TYPES, MAX_ASSET_EXTRACTION_ITERATIONS;
|
715
|
+
var import_promises, fs, import_path2, import_url, axiosNs, TEXT_ASSET_TYPES, BINARY_ASSET_TYPES, MAX_ASSET_EXTRACTION_ITERATIONS;
|
656
716
|
var init_extractor = __esm({
|
657
717
|
"src/core/extractor.ts"() {
|
658
718
|
"use strict";
|
719
|
+
import_promises = require("fs/promises");
|
720
|
+
fs = __toESM(require("fs"), 1);
|
721
|
+
import_path2 = __toESM(require("path"), 1);
|
722
|
+
import_url = require("url");
|
723
|
+
axiosNs = __toESM(require("axios"), 1);
|
659
724
|
init_mime();
|
660
725
|
TEXT_ASSET_TYPES = /* @__PURE__ */ new Set(["css", "js"]);
|
661
726
|
BINARY_ASSET_TYPES = /* @__PURE__ */ new Set(["image", "font", "video", "audio"]);
|
@@ -664,9 +729,6 @@ var init_extractor = __esm({
|
|
664
729
|
});
|
665
730
|
|
666
731
|
// src/core/minifier.ts
|
667
|
-
import { minify as htmlMinify } from "html-minifier-terser";
|
668
|
-
import CleanCSS from "clean-css";
|
669
|
-
import { minify as jsMinify } from "terser";
|
670
732
|
async function minifyAssets(parsed, options = {}, logger) {
|
671
733
|
const { htmlContent, assets } = parsed;
|
672
734
|
const currentHtmlContent = htmlContent ?? "";
|
@@ -692,7 +754,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
692
754
|
try {
|
693
755
|
if (minifyFlags.minifyCss && processedAsset.type === "css") {
|
694
756
|
logger?.debug(`Minifying CSS: ${assetIdentifier}`);
|
695
|
-
const cssMinifier = new
|
757
|
+
const cssMinifier = new import_clean_css.default(CSS_MINIFY_OPTIONS);
|
696
758
|
const result = cssMinifier.minify(processedAsset.content);
|
697
759
|
if (result.errors && result.errors.length > 0) {
|
698
760
|
logger?.warn(`\u26A0\uFE0F CleanCSS failed for ${assetIdentifier}: ${result.errors.join(", ")}`);
|
@@ -710,7 +772,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
710
772
|
}
|
711
773
|
if (minifyFlags.minifyJs && processedAsset.type === "js") {
|
712
774
|
logger?.debug(`Minifying JS: ${assetIdentifier}`);
|
713
|
-
const result = await
|
775
|
+
const result = await (0, import_terser.minify)(processedAsset.content, JS_MINIFY_OPTIONS);
|
714
776
|
if (result.code) {
|
715
777
|
newContent = result.code;
|
716
778
|
logger?.debug(`JS minified successfully: ${assetIdentifier}`);
|
@@ -735,7 +797,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
735
797
|
if (minifyFlags.minifyHtml && finalHtml.length > 0) {
|
736
798
|
logger?.debug("Minifying HTML content...");
|
737
799
|
try {
|
738
|
-
finalHtml = await
|
800
|
+
finalHtml = await (0, import_html_minifier_terser.minify)(finalHtml, {
|
739
801
|
...HTML_MINIFY_OPTIONS,
|
740
802
|
minifyCSS: minifyFlags.minifyCss,
|
741
803
|
minifyJS: minifyFlags.minifyJs
|
@@ -754,10 +816,13 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
754
816
|
// The array of processed asset copies
|
755
817
|
};
|
756
818
|
}
|
757
|
-
var HTML_MINIFY_OPTIONS, CSS_MINIFY_OPTIONS, JS_MINIFY_OPTIONS;
|
819
|
+
var import_html_minifier_terser, import_clean_css, import_terser, HTML_MINIFY_OPTIONS, CSS_MINIFY_OPTIONS, JS_MINIFY_OPTIONS;
|
758
820
|
var init_minifier = __esm({
|
759
821
|
"src/core/minifier.ts"() {
|
760
822
|
"use strict";
|
823
|
+
import_html_minifier_terser = require("html-minifier-terser");
|
824
|
+
import_clean_css = __toESM(require("clean-css"), 1);
|
825
|
+
import_terser = require("terser");
|
761
826
|
HTML_MINIFY_OPTIONS = {
|
762
827
|
collapseWhitespace: true,
|
763
828
|
removeComments: true,
|
@@ -815,7 +880,6 @@ var init_minifier = __esm({
|
|
815
880
|
});
|
816
881
|
|
817
882
|
// src/core/packer.ts
|
818
|
-
import * as cheerio2 from "cheerio";
|
819
883
|
function escapeScriptContent(code) {
|
820
884
|
return code.replace(/<\/(script)/gi, "<\\/$1");
|
821
885
|
}
|
@@ -928,7 +992,7 @@ function packHTML(parsed, logger) {
|
|
928
992
|
return '<!DOCTYPE html><html><head><base href="./"></head><body></body></html>';
|
929
993
|
}
|
930
994
|
logger?.debug("Loading HTML content into Cheerio for packing...");
|
931
|
-
const $ =
|
995
|
+
const $ = cheerio.load(htmlContent);
|
932
996
|
logger?.debug("Ensuring <base> tag exists...");
|
933
997
|
ensureBaseTag($, logger);
|
934
998
|
logger?.debug("Starting asset inlining...");
|
@@ -938,122 +1002,11 @@ function packHTML(parsed, logger) {
|
|
938
1002
|
logger?.debug(`Packing complete. Final size: ${Buffer.byteLength(finalHtml)} bytes.`);
|
939
1003
|
return finalHtml;
|
940
1004
|
}
|
1005
|
+
var cheerio;
|
941
1006
|
var init_packer = __esm({
|
942
1007
|
"src/core/packer.ts"() {
|
943
1008
|
"use strict";
|
944
|
-
|
945
|
-
});
|
946
|
-
|
947
|
-
// src/utils/logger.ts
|
948
|
-
var Logger;
|
949
|
-
var init_logger = __esm({
|
950
|
-
"src/utils/logger.ts"() {
|
951
|
-
"use strict";
|
952
|
-
init_types();
|
953
|
-
Logger = class _Logger {
|
954
|
-
/** The current minimum log level required for a message to be output. */
|
955
|
-
level;
|
956
|
-
/**
|
957
|
-
* Creates a new Logger instance.
|
958
|
-
* Defaults to LogLevel.INFO if no level is provided.
|
959
|
-
*
|
960
|
-
* @param {LogLevel} [level=LogLevel.INFO] - The initial log level for this logger instance.
|
961
|
-
* Must be one of the values from the LogLevel enum.
|
962
|
-
*/
|
963
|
-
constructor(level = 3 /* INFO */) {
|
964
|
-
this.level = level !== void 0 && LogLevel[level] !== void 0 ? level : 3 /* INFO */;
|
965
|
-
}
|
966
|
-
/**
|
967
|
-
* Updates the logger's current level. Messages below this level will be suppressed.
|
968
|
-
*
|
969
|
-
* @param {LogLevel} level - The new log level to set. Must be a LogLevel enum member.
|
970
|
-
*/
|
971
|
-
setLevel(level) {
|
972
|
-
this.level = level;
|
973
|
-
}
|
974
|
-
/**
|
975
|
-
* Logs a debug message if the current log level is DEBUG or higher.
|
976
|
-
*
|
977
|
-
* @param {string} message - The debug message string.
|
978
|
-
*/
|
979
|
-
debug(message) {
|
980
|
-
if (this.level >= 4 /* DEBUG */) {
|
981
|
-
console.debug(`[DEBUG] ${message}`);
|
982
|
-
}
|
983
|
-
}
|
984
|
-
/**
|
985
|
-
* Logs an informational message if the current log level is INFO or higher.
|
986
|
-
*
|
987
|
-
* @param {string} message - The informational message string.
|
988
|
-
*/
|
989
|
-
info(message) {
|
990
|
-
if (this.level >= 3 /* INFO */) {
|
991
|
-
console.info(`[INFO] ${message}`);
|
992
|
-
}
|
993
|
-
}
|
994
|
-
/**
|
995
|
-
* Logs a warning message if the current log level is WARN or higher.
|
996
|
-
*
|
997
|
-
* @param {string} message - The warning message string.
|
998
|
-
*/
|
999
|
-
warn(message) {
|
1000
|
-
if (this.level >= 2 /* WARN */) {
|
1001
|
-
console.warn(`[WARN] ${message}`);
|
1002
|
-
}
|
1003
|
-
}
|
1004
|
-
/**
|
1005
|
-
* Logs an error message if the current log level is ERROR or higher.
|
1006
|
-
*
|
1007
|
-
* @param {string} message - The error message string.
|
1008
|
-
*/
|
1009
|
-
error(message) {
|
1010
|
-
if (this.level >= 1 /* ERROR */) {
|
1011
|
-
console.error(`[ERROR] ${message}`);
|
1012
|
-
}
|
1013
|
-
}
|
1014
|
-
/**
|
1015
|
-
* Static factory method to create a Logger instance based on a simple boolean `verbose` flag.
|
1016
|
-
*
|
1017
|
-
* @static
|
1018
|
-
* @param {{ verbose?: boolean }} [options={}] - An object potentially containing a `verbose` flag.
|
1019
|
-
* @returns {Logger} A new Logger instance set to LogLevel.DEBUG if options.verbose is true,
|
1020
|
-
* otherwise set to LogLevel.INFO.
|
1021
|
-
*/
|
1022
|
-
static fromVerboseFlag(options = {}) {
|
1023
|
-
return new _Logger(options.verbose ? 4 /* DEBUG */ : 3 /* INFO */);
|
1024
|
-
}
|
1025
|
-
/**
|
1026
|
-
* Static factory method to create a Logger instance based on a LogLevel string name.
|
1027
|
-
* Useful for creating a logger from config files or environments variables.
|
1028
|
-
*
|
1029
|
-
* @static
|
1030
|
-
* @param {string | undefined} levelName - The name of the log level (e.g., 'debug', 'info', 'warn', 'error', 'silent'/'none'). Case-insensitive.
|
1031
|
-
* @param {LogLevel} [defaultLevel=LogLevel.INFO] - The level to use if levelName is invalid or undefined.
|
1032
|
-
* @returns {Logger} A new Logger instance set to the corresponding LogLevel.
|
1033
|
-
*/
|
1034
|
-
static fromLevelName(levelName, defaultLevel = 3 /* INFO */) {
|
1035
|
-
if (!levelName) {
|
1036
|
-
return new _Logger(defaultLevel);
|
1037
|
-
}
|
1038
|
-
switch (levelName.toLowerCase()) {
|
1039
|
-
// Return enum members
|
1040
|
-
case "debug":
|
1041
|
-
return new _Logger(4 /* DEBUG */);
|
1042
|
-
case "info":
|
1043
|
-
return new _Logger(3 /* INFO */);
|
1044
|
-
case "warn":
|
1045
|
-
return new _Logger(2 /* WARN */);
|
1046
|
-
case "error":
|
1047
|
-
return new _Logger(1 /* ERROR */);
|
1048
|
-
case "silent":
|
1049
|
-
case "none":
|
1050
|
-
return new _Logger(0 /* NONE */);
|
1051
|
-
default:
|
1052
|
-
console.warn(`[Logger] Invalid log level name "${levelName}". Defaulting to ${LogLevel[defaultLevel]}.`);
|
1053
|
-
return new _Logger(defaultLevel);
|
1054
|
-
}
|
1055
|
-
}
|
1056
|
-
};
|
1009
|
+
cheerio = __toESM(require("cheerio"), 1);
|
1057
1010
|
}
|
1058
1011
|
});
|
1059
1012
|
|
@@ -1093,9 +1046,11 @@ function bundleMultiPageHTML(pages, logger) {
|
|
1093
1046
|
throw new Error(errorMsg);
|
1094
1047
|
}
|
1095
1048
|
logger?.info(`Bundling ${pages.length} pages into a multi-page HTML document.`);
|
1049
|
+
let pageIndex = 0;
|
1096
1050
|
const validPages = pages.filter((page) => {
|
1097
1051
|
const isValid = page && typeof page === "object" && typeof page.url === "string" && typeof page.html === "string";
|
1098
|
-
if (!isValid) logger?.warn(
|
1052
|
+
if (!isValid) logger?.warn(`Skipping invalid page entry at index ${pageIndex}`);
|
1053
|
+
pageIndex++;
|
1099
1054
|
return isValid;
|
1100
1055
|
});
|
1101
1056
|
if (validPages.length === 0) {
|
@@ -1105,70 +1060,137 @@ function bundleMultiPageHTML(pages, logger) {
|
|
1105
1060
|
}
|
1106
1061
|
const slugMap = /* @__PURE__ */ new Map();
|
1107
1062
|
const usedSlugs = /* @__PURE__ */ new Set();
|
1063
|
+
let firstValidSlug = void 0;
|
1064
|
+
let pageCounterForFallback = 1;
|
1108
1065
|
for (const page of validPages) {
|
1109
|
-
|
1066
|
+
let baseSlug = sanitizeSlug(page.url);
|
1067
|
+
const isRootIndex = page.url === "/" || page.url === "index.html" || page.url.endsWith("/index.html");
|
1068
|
+
if (baseSlug === "index" && !isRootIndex) {
|
1069
|
+
logger?.debug(`URL "${page.url}" sanitized to "index", attempting to find alternative slug.`);
|
1070
|
+
const pathParts = page.url.replace(/\/$/, "").split("/").filter((p) => p && p.toLowerCase() !== "index.html" && p.toLowerCase() !== "index");
|
1071
|
+
if (pathParts.length > 0) {
|
1072
|
+
const lastPartSlug = sanitizeSlug(pathParts[pathParts.length - 1]);
|
1073
|
+
if (lastPartSlug && lastPartSlug !== "index") {
|
1074
|
+
baseSlug = lastPartSlug;
|
1075
|
+
logger?.debug(`Using last path part slug "${baseSlug}" instead.`);
|
1076
|
+
} else {
|
1077
|
+
baseSlug = "page";
|
1078
|
+
logger?.debug(`Last path part invalid ("${lastPartSlug}"), using fallback slug "page".`);
|
1079
|
+
}
|
1080
|
+
} else {
|
1081
|
+
baseSlug = "page";
|
1082
|
+
logger?.debug(`No valid path parts found, using fallback slug "page".`);
|
1083
|
+
}
|
1084
|
+
} else if (!baseSlug) {
|
1085
|
+
if (isRootIndex) {
|
1086
|
+
baseSlug = "index";
|
1087
|
+
logger?.debug(`URL "${page.url}" sanitized to empty string, using "index" as it is a root index.`);
|
1088
|
+
} else {
|
1089
|
+
baseSlug = "page";
|
1090
|
+
logger?.debug(`URL "${page.url}" sanitized to empty string, using fallback slug "page".`);
|
1091
|
+
}
|
1092
|
+
}
|
1093
|
+
if (!baseSlug) {
|
1094
|
+
baseSlug = `page-${pageCounterForFallback++}`;
|
1095
|
+
logger?.warn(`Could not determine a valid base slug for "${page.url}", using generated fallback "${baseSlug}".`);
|
1096
|
+
}
|
1110
1097
|
let slug = baseSlug;
|
1111
|
-
let
|
1098
|
+
let collisionCounter = 1;
|
1099
|
+
const originalBaseSlugForLog = baseSlug;
|
1112
1100
|
while (usedSlugs.has(slug)) {
|
1113
|
-
|
1114
|
-
logger?.warn(`Slug collision detected for "${page.url}". Using "${
|
1101
|
+
const newSlug = `${originalBaseSlugForLog}-${collisionCounter++}`;
|
1102
|
+
logger?.warn(`Slug collision detected for "${page.url}" (intended slug: '${originalBaseSlugForLog}'). Using "${newSlug}" instead.`);
|
1103
|
+
slug = newSlug;
|
1115
1104
|
}
|
1116
1105
|
usedSlugs.add(slug);
|
1117
1106
|
slugMap.set(page.url, slug);
|
1107
|
+
if (firstValidSlug === void 0) {
|
1108
|
+
firstValidSlug = slug;
|
1109
|
+
}
|
1118
1110
|
}
|
1119
|
-
const defaultPageSlug =
|
1111
|
+
const defaultPageSlug = usedSlugs.has("index") ? "index" : firstValidSlug || "page";
|
1120
1112
|
let output = `<!DOCTYPE html>
|
1121
1113
|
<html lang="en">
|
1122
1114
|
<head>
|
1123
1115
|
<meta charset="UTF-8">
|
1124
1116
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
1125
1117
|
<title>Multi-Page Bundle</title>
|
1118
|
+
<style>
|
1119
|
+
body { font-family: sans-serif; margin: 0; }
|
1120
|
+
#main-nav { background-color: #f0f0f0; padding: 10px; border-bottom: 1px solid #ccc; }
|
1121
|
+
#main-nav a { margin-right: 15px; text-decoration: none; color: #007bff; }
|
1122
|
+
#main-nav a.active { font-weight: bold; text-decoration: underline; }
|
1123
|
+
#page-container { padding: 20px; }
|
1124
|
+
template { display: none; }
|
1125
|
+
</style>
|
1126
1126
|
</head>
|
1127
1127
|
<body>
|
1128
1128
|
<nav id="main-nav">
|
1129
1129
|
${validPages.map((p) => {
|
1130
1130
|
const slug = slugMap.get(p.url);
|
1131
|
-
const label =
|
1131
|
+
const label = slug;
|
1132
1132
|
return `<a href="#${slug}" data-page="${slug}">${label}</a>`;
|
1133
|
-
}).join("\n")}
|
1133
|
+
}).join("\n ")}
|
1134
1134
|
</nav>
|
1135
1135
|
<div id="page-container"></div>
|
1136
1136
|
${validPages.map((p) => {
|
1137
1137
|
const slug = slugMap.get(p.url);
|
1138
1138
|
return `<template id="page-${slug}">${p.html}</template>`;
|
1139
|
-
}).join("\n")}
|
1139
|
+
}).join("\n ")}
|
1140
1140
|
<script id="router-script">
|
1141
1141
|
document.addEventListener('DOMContentLoaded', function() {
|
1142
|
+
const pageContainer = document.getElementById('page-container');
|
1143
|
+
const navLinks = document.querySelectorAll('#main-nav a');
|
1144
|
+
|
1142
1145
|
function navigateTo(slug) {
|
1143
1146
|
const template = document.getElementById('page-' + slug);
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1147
|
+
if (!template || !pageContainer) {
|
1148
|
+
console.warn('Navigation failed: Template or container not found for slug:', slug);
|
1149
|
+
// Maybe try navigating to default page? Or just clear container?
|
1150
|
+
if (pageContainer) pageContainer.innerHTML = '<p>Page not found.</p>';
|
1151
|
+
return;
|
1152
|
+
}
|
1153
|
+
// Clear previous content and append new content
|
1154
|
+
pageContainer.innerHTML = ''; // Clear reliably
|
1155
|
+
pageContainer.appendChild(template.content.cloneNode(true));
|
1156
|
+
|
1157
|
+
// Update active link styling
|
1158
|
+
navLinks.forEach(link => {
|
1159
|
+
link.classList.toggle('active', link.getAttribute('data-page') === slug);
|
1151
1160
|
});
|
1161
|
+
|
1162
|
+
// Update URL hash without triggering hashchange if already correct
|
1152
1163
|
if (window.location.hash.substring(1) !== slug) {
|
1153
|
-
|
1164
|
+
// Use pushState for cleaner history
|
1165
|
+
history.pushState({ slug: slug }, '', '#' + slug);
|
1154
1166
|
}
|
1155
1167
|
}
|
1156
1168
|
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1169
|
+
// Handle back/forward navigation
|
1170
|
+
window.addEventListener('popstate', (event) => {
|
1171
|
+
let slug = window.location.hash.substring(1);
|
1172
|
+
// If popstate event has state use it, otherwise fallback to hash or default
|
1173
|
+
if (event && event.state && event.state.slug) { // Check event exists
|
1174
|
+
slug = event.state.slug;
|
1175
|
+
}
|
1176
|
+
// Ensure the target page exists before navigating, fallback to default slug
|
1177
|
+
const targetSlug = document.getElementById('page-' + slug) ? slug : '${defaultPageSlug}';
|
1178
|
+
navigateTo(targetSlug);
|
1160
1179
|
});
|
1161
1180
|
|
1162
|
-
|
1181
|
+
// Handle direct link clicks
|
1182
|
+
navLinks.forEach(link => {
|
1163
1183
|
link.addEventListener('click', function(e) {
|
1164
1184
|
e.preventDefault();
|
1165
1185
|
const slug = this.getAttribute('data-page');
|
1166
|
-
navigateTo(slug);
|
1186
|
+
if (slug) navigateTo(slug);
|
1167
1187
|
});
|
1168
1188
|
});
|
1169
1189
|
|
1170
|
-
|
1171
|
-
|
1190
|
+
// Initial page load
|
1191
|
+
const initialHash = window.location.hash.substring(1);
|
1192
|
+
const initialSlug = document.getElementById('page-' + initialHash) ? initialHash : '${defaultPageSlug}';
|
1193
|
+
navigateTo(initialSlug);
|
1172
1194
|
});
|
1173
1195
|
</script>
|
1174
1196
|
</body>
|
@@ -1182,58 +1204,69 @@ var init_bundler = __esm({
|
|
1182
1204
|
init_extractor();
|
1183
1205
|
init_minifier();
|
1184
1206
|
init_packer();
|
1207
|
+
init_types();
|
1185
1208
|
init_slugify();
|
1186
1209
|
}
|
1187
1210
|
});
|
1188
1211
|
|
1189
1212
|
// src/core/web-fetcher.ts
|
1190
|
-
|
1191
|
-
import * as fs2 from "fs/promises";
|
1192
|
-
async function fetchAndPackWebPage(url, logger, timeout = 3e4) {
|
1213
|
+
async function fetchAndPackWebPage(url, logger, timeout = DEFAULT_PAGE_TIMEOUT, userAgent) {
|
1193
1214
|
let browser = null;
|
1194
1215
|
const start = Date.now();
|
1195
|
-
logger?.
|
1216
|
+
logger?.info(`Initiating fetch for single page: ${url}`);
|
1196
1217
|
try {
|
1197
|
-
|
1198
|
-
|
1218
|
+
logger?.debug("Launching browser...");
|
1219
|
+
browser = await puppeteer.launch(PUPPETEER_LAUNCH_OPTIONS);
|
1220
|
+
logger?.debug(`Browser launched successfully (PID: ${browser.process()?.pid}).`);
|
1199
1221
|
const page = await browser.newPage();
|
1200
|
-
logger?.debug(`
|
1222
|
+
logger?.debug(`New page created for ${url}`);
|
1223
|
+
if (userAgent) {
|
1224
|
+
await page.setUserAgent(userAgent);
|
1225
|
+
logger?.debug(`User-Agent set to: "${userAgent}"`);
|
1226
|
+
}
|
1201
1227
|
try {
|
1202
1228
|
logger?.debug(`Navigating to ${url} with timeout ${timeout}ms`);
|
1203
1229
|
await page.goto(url, { waitUntil: "networkidle2", timeout });
|
1204
1230
|
logger?.debug(`Navigation successful for ${url}`);
|
1205
1231
|
const html = await page.content();
|
1206
|
-
logger?.debug(`Content retrieved for ${url}`);
|
1232
|
+
logger?.debug(`Content retrieved for ${url} (${Buffer.byteLength(html, "utf-8")} bytes)`);
|
1207
1233
|
const metadata = {
|
1208
1234
|
input: url,
|
1209
1235
|
outputSize: Buffer.byteLength(html, "utf-8"),
|
1210
1236
|
assetCount: 0,
|
1211
|
-
// Basic fetch doesn't track assets
|
1237
|
+
// Basic fetch doesn't track assets processed by *this* tool
|
1212
1238
|
buildTimeMs: Date.now() - start,
|
1213
1239
|
errors: []
|
1214
1240
|
// No errors if we reached this point
|
1215
1241
|
};
|
1216
1242
|
await page.close();
|
1217
1243
|
logger?.debug(`Page closed for ${url}`);
|
1244
|
+
await browser.close();
|
1218
1245
|
logger?.debug(`Browser closed for ${url}`);
|
1219
1246
|
browser = null;
|
1220
1247
|
return { html, metadata };
|
1221
1248
|
} catch (pageError) {
|
1222
1249
|
logger?.error(`Error during page processing for ${url}: ${pageError.message}`);
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1250
|
+
if (page && !page.isClosed()) {
|
1251
|
+
try {
|
1252
|
+
await page.close();
|
1253
|
+
logger?.debug(`Page closed after error for ${url}`);
|
1254
|
+
} catch (closeErr) {
|
1255
|
+
logger?.error(`Failed to close page after error for ${url}: ${closeErr.message}`);
|
1256
|
+
}
|
1227
1257
|
}
|
1228
1258
|
throw pageError;
|
1229
1259
|
}
|
1230
1260
|
} catch (launchError) {
|
1231
|
-
logger?.error(`Critical error during browser launch or page
|
1261
|
+
logger?.error(`Critical error during browser launch or page setup for ${url}: ${launchError.message}`);
|
1232
1262
|
if (browser) {
|
1233
1263
|
try {
|
1234
1264
|
await browser.close();
|
1265
|
+
logger?.debug("Browser closed after launch/setup error.");
|
1235
1266
|
} catch (closeErr) {
|
1267
|
+
logger?.warn(`Failed to close browser after launch/setup error: ${closeErr.message}`);
|
1236
1268
|
}
|
1269
|
+
browser = null;
|
1237
1270
|
}
|
1238
1271
|
throw launchError;
|
1239
1272
|
} finally {
|
@@ -1246,99 +1279,123 @@ async function fetchAndPackWebPage(url, logger, timeout = 3e4) {
|
|
1246
1279
|
}
|
1247
1280
|
}
|
1248
1281
|
}
|
1249
|
-
async function crawlWebsite(startUrl,
|
1282
|
+
async function crawlWebsite(startUrl, options) {
|
1283
|
+
const {
|
1284
|
+
maxDepth = 1,
|
1285
|
+
timeout = DEFAULT_PAGE_TIMEOUT,
|
1286
|
+
// include = ['**'], // TODO: Implement glob filtering
|
1287
|
+
// exclude = [],
|
1288
|
+
userAgent,
|
1289
|
+
logger
|
1290
|
+
} = options;
|
1250
1291
|
logger?.info(`Starting crawl for ${startUrl} with maxDepth ${maxDepth}`);
|
1251
1292
|
if (maxDepth <= 0) {
|
1252
|
-
logger?.
|
1293
|
+
logger?.warn("maxDepth is 0 or negative, no pages will be crawled.");
|
1253
1294
|
return [];
|
1254
1295
|
}
|
1255
|
-
|
1296
|
+
let browser = null;
|
1256
1297
|
const visited = /* @__PURE__ */ new Set();
|
1257
1298
|
const results = [];
|
1258
1299
|
const queue = [];
|
1259
1300
|
let startOrigin;
|
1260
1301
|
try {
|
1261
|
-
startOrigin = new URL(startUrl).origin;
|
1262
|
-
} catch (e) {
|
1263
|
-
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1264
|
-
await browser.close();
|
1265
|
-
return [];
|
1266
|
-
}
|
1267
|
-
let normalizedStartUrl;
|
1268
|
-
try {
|
1269
|
-
const parsedStartUrl = new URL(startUrl);
|
1270
|
-
parsedStartUrl.hash = "";
|
1271
|
-
normalizedStartUrl = parsedStartUrl.href;
|
1272
|
-
} catch (e) {
|
1273
|
-
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1274
|
-
await browser.close();
|
1275
|
-
return [];
|
1276
|
-
}
|
1277
|
-
visited.add(normalizedStartUrl);
|
1278
|
-
queue.push({ url: normalizedStartUrl, depth: 1 });
|
1279
|
-
logger?.debug(`Queued initial URL: ${normalizedStartUrl} (depth 1)`);
|
1280
|
-
while (queue.length > 0) {
|
1281
|
-
const { url, depth } = queue.shift();
|
1282
|
-
logger?.info(`Processing: ${url} (depth ${depth})`);
|
1283
|
-
let page = null;
|
1284
1302
|
try {
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1292
|
-
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1303
|
+
startOrigin = new URL(startUrl).origin;
|
1304
|
+
} catch (e) {
|
1305
|
+
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1306
|
+
throw new Error(`Invalid start URL: ${startUrl}`);
|
1307
|
+
}
|
1308
|
+
let normalizedStartUrl;
|
1309
|
+
try {
|
1310
|
+
const parsedStartUrl = new URL(startUrl);
|
1311
|
+
parsedStartUrl.hash = "";
|
1312
|
+
normalizedStartUrl = parsedStartUrl.href;
|
1313
|
+
} catch (e) {
|
1314
|
+
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1315
|
+
throw new Error(`Invalid start URL: ${startUrl}`);
|
1316
|
+
}
|
1317
|
+
logger?.debug("Launching browser for crawl...");
|
1318
|
+
browser = await puppeteer.launch(PUPPETEER_LAUNCH_OPTIONS);
|
1319
|
+
logger?.debug(`Browser launched for crawl (PID: ${browser.process()?.pid}).`);
|
1320
|
+
visited.add(normalizedStartUrl);
|
1321
|
+
queue.push({ url: normalizedStartUrl, depth: 1 });
|
1322
|
+
logger?.debug(`Queued initial URL: ${normalizedStartUrl} (depth 1)`);
|
1323
|
+
while (queue.length > 0) {
|
1324
|
+
const { url, depth } = queue.shift();
|
1325
|
+
logger?.info(`Processing: ${url} (depth ${depth})`);
|
1326
|
+
let page = null;
|
1327
|
+
try {
|
1328
|
+
page = await browser.newPage();
|
1329
|
+
if (userAgent) {
|
1330
|
+
await page.setUserAgent(userAgent);
|
1331
|
+
}
|
1332
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout });
|
1333
|
+
const html = await page.content();
|
1334
|
+
results.push({ url, html });
|
1335
|
+
logger?.debug(`Successfully fetched content for ${url}`);
|
1336
|
+
if (depth < maxDepth) {
|
1337
|
+
logger?.debug(`Discovering links on ${url} (depth ${depth}/${maxDepth})`);
|
1338
|
+
const hrefs = await page.evaluate(
|
1339
|
+
() => Array.from(document.querySelectorAll("a[href]"), (a) => a.getAttribute("href"))
|
1340
|
+
);
|
1341
|
+
logger?.debug(`Found ${hrefs.length} potential hrefs on ${url}`);
|
1342
|
+
let linksAdded = 0;
|
1343
|
+
for (const href of hrefs) {
|
1344
|
+
if (!href) continue;
|
1345
|
+
let absoluteUrl;
|
1346
|
+
try {
|
1347
|
+
const resolved = new URL(href, url);
|
1348
|
+
resolved.hash = "";
|
1349
|
+
absoluteUrl = resolved.href;
|
1350
|
+
} catch (e) {
|
1351
|
+
logger?.debug(`Ignoring invalid URL syntax: "${href}" on page ${url}`);
|
1352
|
+
continue;
|
1353
|
+
}
|
1354
|
+
if (absoluteUrl.startsWith(startOrigin) && !visited.has(absoluteUrl)) {
|
1355
|
+
visited.add(absoluteUrl);
|
1356
|
+
queue.push({ url: absoluteUrl, depth: depth + 1 });
|
1357
|
+
linksAdded++;
|
1358
|
+
}
|
1314
1359
|
}
|
1360
|
+
logger?.debug(`Added ${linksAdded} new unique internal links to queue from ${url}`);
|
1361
|
+
} else {
|
1362
|
+
logger?.debug(`Max depth (${maxDepth}) reached, not discovering links on ${url}`);
|
1315
1363
|
}
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
await page.close();
|
1326
|
-
} catch (pageCloseError) {
|
1327
|
-
logger?.error(`Failed to close page for ${url}: ${pageCloseError.message}`);
|
1364
|
+
} catch (err) {
|
1365
|
+
logger?.warn(`\u274C Failed to process ${url}: ${err.message}`);
|
1366
|
+
} finally {
|
1367
|
+
if (page && !page.isClosed()) {
|
1368
|
+
try {
|
1369
|
+
await page.close();
|
1370
|
+
} catch (pageCloseError) {
|
1371
|
+
logger?.error(`Failed to close page for ${url}: ${pageCloseError.message}`);
|
1372
|
+
}
|
1328
1373
|
}
|
1329
1374
|
}
|
1330
1375
|
}
|
1376
|
+
} catch (error) {
|
1377
|
+
logger?.error(`Critical crawl error: ${error instanceof Error ? error.message : error}`);
|
1378
|
+
throw error;
|
1379
|
+
} finally {
|
1380
|
+
if (browser) {
|
1381
|
+
logger?.info(`Crawl finished or errored. Closing browser.`);
|
1382
|
+
await browser.close();
|
1383
|
+
logger?.debug(`Browser closed after crawl.`);
|
1384
|
+
}
|
1331
1385
|
}
|
1332
|
-
logger?.info(`Crawl
|
1333
|
-
await browser.close();
|
1334
|
-
logger?.info(`Found ${results.length} pages.`);
|
1386
|
+
logger?.info(`Crawl found ${results.length} pages.`);
|
1335
1387
|
return results;
|
1336
1388
|
}
|
1337
|
-
async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1) {
|
1338
|
-
const logger = new Logger();
|
1389
|
+
async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1, loggerInstance) {
|
1390
|
+
const logger = loggerInstance || new Logger();
|
1339
1391
|
logger.info(`Starting recursive site bundle for ${startUrl} to ${outputFile} (maxDepth: ${maxDepth})`);
|
1340
1392
|
try {
|
1341
|
-
const
|
1393
|
+
const crawlOptions = {
|
1394
|
+
maxDepth,
|
1395
|
+
logger
|
1396
|
+
/* Add other options like timeout, userAgent if needed */
|
1397
|
+
};
|
1398
|
+
const pages = await crawlWebsite(startUrl, crawlOptions);
|
1342
1399
|
if (pages.length === 0) {
|
1343
1400
|
logger.warn("Crawl completed but found 0 pages. Output file may be empty or reflect an empty bundle.");
|
1344
1401
|
} else {
|
@@ -1361,11 +1418,98 @@ async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1) {
|
|
1361
1418
|
throw error;
|
1362
1419
|
}
|
1363
1420
|
}
|
1421
|
+
var puppeteer, fs2, PUPPETEER_LAUNCH_OPTIONS, DEFAULT_PAGE_TIMEOUT;
|
1364
1422
|
var init_web_fetcher = __esm({
|
1365
1423
|
"src/core/web-fetcher.ts"() {
|
1366
1424
|
"use strict";
|
1425
|
+
puppeteer = __toESM(require("puppeteer"), 1);
|
1426
|
+
fs2 = __toESM(require("fs/promises"), 1);
|
1367
1427
|
init_logger();
|
1368
1428
|
init_bundler();
|
1429
|
+
PUPPETEER_LAUNCH_OPTIONS = {
|
1430
|
+
headless: true,
|
1431
|
+
args: [
|
1432
|
+
"--no-sandbox",
|
1433
|
+
// Often required in containerized environments
|
1434
|
+
"--disable-setuid-sandbox",
|
1435
|
+
"--disable-dev-shm-usage"
|
1436
|
+
// Recommended for Docker/CI
|
1437
|
+
]
|
1438
|
+
};
|
1439
|
+
DEFAULT_PAGE_TIMEOUT = 3e4;
|
1440
|
+
}
|
1441
|
+
});
|
1442
|
+
|
1443
|
+
// src/core/parser.ts
|
1444
|
+
async function parseHTML(entryFilePath, logger) {
|
1445
|
+
logger?.debug(`Parsing HTML file: ${entryFilePath}`);
|
1446
|
+
let htmlContent;
|
1447
|
+
try {
|
1448
|
+
htmlContent = await (0, import_promises2.readFile)(entryFilePath, "utf-8");
|
1449
|
+
logger?.debug(`Successfully read HTML file (${Buffer.byteLength(htmlContent)} bytes).`);
|
1450
|
+
} catch (err) {
|
1451
|
+
logger?.error(`Failed to read HTML file "${entryFilePath}": ${err.message}`);
|
1452
|
+
throw new Error(`Could not read input HTML file: ${entryFilePath}`, { cause: err });
|
1453
|
+
}
|
1454
|
+
const $ = cheerio2.load(htmlContent);
|
1455
|
+
const assets = [];
|
1456
|
+
const addedUrls = /* @__PURE__ */ new Set();
|
1457
|
+
const addAsset = (url, forcedType) => {
|
1458
|
+
if (!url || url.trim() === "" || url.startsWith("data:")) {
|
1459
|
+
return;
|
1460
|
+
}
|
1461
|
+
if (!addedUrls.has(url)) {
|
1462
|
+
addedUrls.add(url);
|
1463
|
+
const mimeInfo = guessMimeType(url);
|
1464
|
+
const type = forcedType ?? mimeInfo.assetType;
|
1465
|
+
assets.push({ type, url });
|
1466
|
+
logger?.debug(`Discovered asset: Type='${type}', URL='${url}'`);
|
1467
|
+
} else {
|
1468
|
+
logger?.debug(`Skipping duplicate asset URL: ${url}`);
|
1469
|
+
}
|
1470
|
+
};
|
1471
|
+
logger?.debug("Extracting assets from HTML tags...");
|
1472
|
+
$('link[rel="stylesheet"][href]').each((_, el) => {
|
1473
|
+
addAsset($(el).attr("href"), "css");
|
1474
|
+
});
|
1475
|
+
$("script[src]").each((_, el) => {
|
1476
|
+
addAsset($(el).attr("src"), "js");
|
1477
|
+
});
|
1478
|
+
$("img[src]").each((_, el) => addAsset($(el).attr("src"), "image"));
|
1479
|
+
$('input[type="image"][src]').each((_, el) => addAsset($(el).attr("src"), "image"));
|
1480
|
+
$("img[srcset], picture source[srcset]").each((_, el) => {
|
1481
|
+
const srcset = $(el).attr("srcset");
|
1482
|
+
srcset?.split(",").forEach((entry) => {
|
1483
|
+
const [url] = entry.trim().split(/\s+/);
|
1484
|
+
addAsset(url, "image");
|
1485
|
+
});
|
1486
|
+
});
|
1487
|
+
$("video[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
1488
|
+
$("video[poster]").each((_, el) => addAsset($(el).attr("poster"), "image"));
|
1489
|
+
$("audio[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
1490
|
+
$("video > source[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
1491
|
+
$("audio > source[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
1492
|
+
$("link[href]").filter((_, el) => {
|
1493
|
+
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
1494
|
+
return ["icon", "shortcut icon", "apple-touch-icon", "manifest"].includes(rel);
|
1495
|
+
}).each((_, el) => {
|
1496
|
+
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
1497
|
+
const isIcon = ["icon", "shortcut icon", "apple-touch-icon"].includes(rel);
|
1498
|
+
addAsset($(el).attr("href"), isIcon ? "image" : void 0);
|
1499
|
+
});
|
1500
|
+
$('link[rel="preload"][as="font"][href]').each((_, el) => {
|
1501
|
+
addAsset($(el).attr("href"), "font");
|
1502
|
+
});
|
1503
|
+
logger?.info(`HTML parsing complete. Discovered ${assets.length} unique asset links.`);
|
1504
|
+
return { htmlContent, assets };
|
1505
|
+
}
|
1506
|
+
var import_promises2, cheerio2;
|
1507
|
+
var init_parser = __esm({
|
1508
|
+
"src/core/parser.ts"() {
|
1509
|
+
"use strict";
|
1510
|
+
import_promises2 = require("fs/promises");
|
1511
|
+
cheerio2 = __toESM(require("cheerio"), 1);
|
1512
|
+
init_mime();
|
1369
1513
|
}
|
1370
1514
|
});
|
1371
1515
|
|
@@ -1455,117 +1599,91 @@ var init_meta = __esm({
|
|
1455
1599
|
});
|
1456
1600
|
|
1457
1601
|
// src/index.ts
|
1602
|
+
async function pack(input, options = {}) {
|
1603
|
+
const logger = options.loggerInstance || new Logger(options.logLevel);
|
1604
|
+
const isHttp = /^https?:\/\//i.test(input);
|
1605
|
+
if (!isHttp && /:\/\//.test(input) && !input.startsWith("file://")) {
|
1606
|
+
const errorMsg = `Unsupported protocol or input type: ${input}`;
|
1607
|
+
logger.error(errorMsg);
|
1608
|
+
throw new Error(errorMsg);
|
1609
|
+
}
|
1610
|
+
const isRemote = /^https?:\/\//i.test(input);
|
1611
|
+
const recursive = options.recursive === true || typeof options.recursive === "number";
|
1612
|
+
if (isRemote && recursive) {
|
1613
|
+
const depth = typeof options.recursive === "number" ? options.recursive : 1;
|
1614
|
+
logger.info(`Starting recursive fetch for ${input} up to depth ${depth}`);
|
1615
|
+
return generateRecursivePortableHTML(input, depth, options, logger);
|
1616
|
+
}
|
1617
|
+
logger.info(`Starting single page processing for: ${input}`);
|
1618
|
+
return generatePortableHTML(input, options, logger);
|
1619
|
+
}
|
1458
1620
|
async function generatePortableHTML(input, options = {}, loggerInstance) {
|
1459
1621
|
const logger = loggerInstance || new Logger(options.logLevel);
|
1460
|
-
logger.info(`Generating portable HTML for: ${input}`);
|
1461
1622
|
const timer = new BuildTimer(input);
|
1462
|
-
|
1463
|
-
|
1464
|
-
logger.info(`Input is a remote URL. Fetching page content directly...`);
|
1623
|
+
if (/^https?:\/\//i.test(input)) {
|
1624
|
+
logger.info(`Workspaceing remote page: ${input}`);
|
1465
1625
|
try {
|
1466
|
-
const result = await
|
1467
|
-
|
1468
|
-
|
1626
|
+
const result = await fetchAndPackWebPage(input, logger);
|
1627
|
+
const metadata = timer.finish(result.html, result.metadata);
|
1628
|
+
logger.info(`Finished fetching and packing remote page: ${input}`);
|
1629
|
+
return { html: result.html, metadata };
|
1469
1630
|
} catch (error) {
|
1470
|
-
logger.error(`
|
1631
|
+
logger.error(`Error fetching remote page ${input}: ${error.message}`);
|
1471
1632
|
throw error;
|
1472
1633
|
}
|
1473
1634
|
}
|
1474
|
-
logger.info(`
|
1475
|
-
const basePath = options.baseUrl || input;
|
1476
|
-
logger.debug(`Using base path for asset resolution: ${basePath}`);
|
1635
|
+
logger.info(`Processing local file: ${input}`);
|
1477
1636
|
try {
|
1637
|
+
const baseUrl = options.baseUrl || input;
|
1478
1638
|
const parsed = await parseHTML(input, logger);
|
1479
|
-
const enriched = await extractAssets(parsed, options.embedAssets ?? true,
|
1639
|
+
const enriched = await extractAssets(parsed, options.embedAssets ?? true, baseUrl, logger);
|
1480
1640
|
const minified = await minifyAssets(enriched, options, logger);
|
1481
1641
|
const finalHtml = packHTML(minified, logger);
|
1482
1642
|
const metadata = timer.finish(finalHtml, {
|
1483
1643
|
assetCount: minified.assets.length
|
1484
|
-
// FIX: Removed incorrect attempt to get errors from logger
|
1485
|
-
// Errors collected by the timer itself (via timer.addError) will be included automatically.
|
1486
1644
|
});
|
1487
|
-
logger.info(`
|
1488
|
-
if (metadata.errors && metadata.errors.length > 0) {
|
1489
|
-
logger.warn(`Completed with ${metadata.errors.length} warning(s) logged in metadata.`);
|
1490
|
-
}
|
1645
|
+
logger.info(`Finished processing local file: ${input}`);
|
1491
1646
|
return { html: finalHtml, metadata };
|
1492
1647
|
} catch (error) {
|
1493
|
-
logger.error(`Error
|
1648
|
+
logger.error(`Error processing local file ${input}: ${error.message}`);
|
1494
1649
|
throw error;
|
1495
1650
|
}
|
1496
1651
|
}
|
1497
1652
|
async function generateRecursivePortableHTML(url, depth = 1, options = {}, loggerInstance) {
|
1498
1653
|
const logger = loggerInstance || new Logger(options.logLevel);
|
1499
|
-
logger.info(`Generating recursive portable HTML for: ${url}, Max Depth: ${depth}`);
|
1500
1654
|
const timer = new BuildTimer(url);
|
1501
1655
|
if (!/^https?:\/\//i.test(url)) {
|
1502
|
-
const
|
1503
|
-
logger.error(
|
1504
|
-
throw new Error(
|
1656
|
+
const errorMsg = `Invalid URL for recursive bundling. Must start with http:// or https://. Received: ${url}`;
|
1657
|
+
logger.error(errorMsg);
|
1658
|
+
throw new Error(errorMsg);
|
1505
1659
|
}
|
1506
|
-
|
1660
|
+
logger.info(`Starting recursive bundle for ${url} up to depth ${depth}`);
|
1507
1661
|
try {
|
1508
|
-
const { html, pages } = await recursivelyBundleSite(url,
|
1509
|
-
logger.info(`Recursive crawl complete. Discovered and bundled ${pages} pages.`);
|
1662
|
+
const { html, pages } = await recursivelyBundleSite(url, "output.html", depth, logger);
|
1510
1663
|
timer.setPageCount(pages);
|
1511
1664
|
const metadata = timer.finish(html, {
|
1512
1665
|
assetCount: 0,
|
1513
|
-
// NOTE: Asset count across multiple pages is not currently aggregated.
|
1514
1666
|
pagesBundled: pages
|
1515
|
-
// TODO: Potentially collect errors from the core function if it returns them
|
1516
1667
|
});
|
1517
|
-
logger.info(`
|
1518
|
-
if (metadata.errors && metadata.errors.length > 0) {
|
1519
|
-
logger.warn(`Completed with ${metadata.errors.length} warning(s) logged in metadata.`);
|
1520
|
-
}
|
1668
|
+
logger.info(`Finished recursive bundle for ${url}. Bundled ${pages} pages.`);
|
1521
1669
|
return { html, metadata };
|
1522
1670
|
} catch (error) {
|
1523
|
-
logger.error(`Error during recursive
|
1524
|
-
if (error.cause instanceof Error) {
|
1525
|
-
logger.error(`Cause: ${error.cause.message}`);
|
1526
|
-
}
|
1527
|
-
throw error;
|
1528
|
-
}
|
1529
|
-
}
|
1530
|
-
async function fetchAndPackWebPage2(url, options = {}, loggerInstance) {
|
1531
|
-
const logger = loggerInstance || new Logger(options.logLevel);
|
1532
|
-
logger.info(`Workspaceing single remote page: ${url}`);
|
1533
|
-
const timer = new BuildTimer(url);
|
1534
|
-
if (!/^https?:\/\//i.test(url)) {
|
1535
|
-
const errMsg = `Invalid input URL for fetchAndPackWebPage: ${url}. Must start with http(s)://`;
|
1536
|
-
logger.error(errMsg);
|
1537
|
-
throw new Error(errMsg);
|
1538
|
-
}
|
1539
|
-
try {
|
1540
|
-
const result = await fetchAndPackWebPage(url, logger);
|
1541
|
-
const metadata = timer.finish(result.html, {
|
1542
|
-
// Use assetCount and errors from core metadata if available
|
1543
|
-
assetCount: result.metadata?.assetCount ?? 0,
|
1544
|
-
errors: result.metadata?.errors ?? []
|
1545
|
-
// Ensure errors array exists
|
1546
|
-
});
|
1547
|
-
logger.info(`Single page fetch complete. Input: ${url}, Size: ${metadata.outputSize} bytes, Assets: ${metadata.assetCount}, Time: ${metadata.buildTimeMs}ms`);
|
1548
|
-
if (metadata.errors && metadata.errors.length > 0) {
|
1549
|
-
logger.warn(`Completed with ${metadata.errors.length} warning(s) logged in metadata.`);
|
1550
|
-
}
|
1551
|
-
return { html: result.html, metadata };
|
1552
|
-
} catch (error) {
|
1553
|
-
logger.error(`Error during single page fetch for ${url}: ${error.message}`);
|
1671
|
+
logger.error(`Error during recursive bundle for ${url}: ${error.message}`);
|
1554
1672
|
throw error;
|
1555
1673
|
}
|
1556
1674
|
}
|
1557
1675
|
var init_src = __esm({
|
1558
1676
|
"src/index.ts"() {
|
1559
1677
|
"use strict";
|
1678
|
+
init_web_fetcher();
|
1560
1679
|
init_parser();
|
1561
1680
|
init_extractor();
|
1562
1681
|
init_minifier();
|
1563
1682
|
init_packer();
|
1564
|
-
init_web_fetcher();
|
1565
1683
|
init_bundler();
|
1684
|
+
init_logger();
|
1566
1685
|
init_meta();
|
1567
1686
|
init_logger();
|
1568
|
-
init_types();
|
1569
1687
|
}
|
1570
1688
|
});
|
1571
1689
|
|
@@ -1575,20 +1693,15 @@ __export(cli_exports, {
|
|
1575
1693
|
main: () => main,
|
1576
1694
|
runCli: () => runCli
|
1577
1695
|
});
|
1578
|
-
import fs3 from "fs";
|
1579
|
-
import path3 from "path";
|
1580
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
1581
1696
|
function getPackageJson() {
|
1582
1697
|
try {
|
1583
|
-
const
|
1584
|
-
const
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
}
|
1589
|
-
} catch (_) {
|
1698
|
+
const searchPath = typeof __dirname !== "undefined" ? import_path3.default.join(__dirname, "..", "..") : process.cwd();
|
1699
|
+
const pkgJsonPath = require.resolve("portapack/package.json", { paths: [searchPath] });
|
1700
|
+
return require(pkgJsonPath);
|
1701
|
+
} catch (err) {
|
1702
|
+
console.error("Warning: Could not dynamically load package.json for version.", err);
|
1703
|
+
return { version: "0.0.0-unknown" };
|
1590
1704
|
}
|
1591
|
-
return { version: "0.1.0" };
|
1592
1705
|
}
|
1593
1706
|
async function runCli(argv = process.argv) {
|
1594
1707
|
let stdout = "";
|
@@ -1597,6 +1710,11 @@ async function runCli(argv = process.argv) {
|
|
1597
1710
|
const originalLog = console.log;
|
1598
1711
|
const originalErr = console.error;
|
1599
1712
|
const originalWarn = console.warn;
|
1713
|
+
const restoreConsole = () => {
|
1714
|
+
console.log = originalLog;
|
1715
|
+
console.error = originalErr;
|
1716
|
+
console.warn = originalWarn;
|
1717
|
+
};
|
1600
1718
|
console.log = (...args) => {
|
1601
1719
|
stdout += args.join(" ") + "\n";
|
1602
1720
|
};
|
@@ -1606,40 +1724,38 @@ async function runCli(argv = process.argv) {
|
|
1606
1724
|
console.warn = (...args) => {
|
1607
1725
|
stderr += args.join(" ") + "\n";
|
1608
1726
|
};
|
1609
|
-
let
|
1727
|
+
let cliOptions;
|
1610
1728
|
try {
|
1611
|
-
|
1612
|
-
const version = getPackageJson().version || "0.
|
1613
|
-
if (
|
1729
|
+
cliOptions = parseOptions(argv);
|
1730
|
+
const version = getPackageJson().version || "0.0.0";
|
1731
|
+
if (cliOptions.verbose) {
|
1614
1732
|
console.log(`\u{1F4E6} PortaPack v${version}`);
|
1615
1733
|
}
|
1616
|
-
if (!
|
1734
|
+
if (!cliOptions.input) {
|
1617
1735
|
console.error("\u274C Missing input file or URL");
|
1618
|
-
|
1619
|
-
console.error = originalErr;
|
1620
|
-
console.warn = originalWarn;
|
1736
|
+
restoreConsole();
|
1621
1737
|
return { stdout, stderr, exitCode: 1 };
|
1622
1738
|
}
|
1623
|
-
const
|
1624
|
-
|
1625
|
-
|
1739
|
+
const inputBasename = import_path3.default.basename(cliOptions.input);
|
1740
|
+
const outputDefaultBase = inputBasename.includes(".") ? inputBasename.substring(0, inputBasename.lastIndexOf(".")) : inputBasename;
|
1741
|
+
const outputPath = cliOptions.output ?? `${outputDefaultBase || "output"}.packed.html`;
|
1742
|
+
if (cliOptions.verbose) {
|
1743
|
+
console.log(`\u{1F4E5} Input: ${cliOptions.input}`);
|
1626
1744
|
console.log(`\u{1F4E4} Output: ${outputPath}`);
|
1627
|
-
console.log(`
|
1628
|
-
console.log(`
|
1629
|
-
console.log(`
|
1630
|
-
console.log(`
|
1631
|
-
console.log(`
|
1632
|
-
console.log(`
|
1745
|
+
console.log(` Recursive: ${cliOptions.recursive ?? false}`);
|
1746
|
+
console.log(` Embed Assets: ${cliOptions.embedAssets}`);
|
1747
|
+
console.log(` Minify HTML: ${cliOptions.minifyHtml}`);
|
1748
|
+
console.log(` Minify CSS: ${cliOptions.minifyCss}`);
|
1749
|
+
console.log(` Minify JS: ${cliOptions.minifyJs}`);
|
1750
|
+
console.log(` Log Level: ${cliOptions.logLevel}`);
|
1633
1751
|
}
|
1634
|
-
if (
|
1752
|
+
if (cliOptions.dryRun) {
|
1635
1753
|
console.log("\u{1F4A1} Dry run mode \u2014 no output will be written");
|
1636
|
-
|
1637
|
-
console.error = originalErr;
|
1638
|
-
console.warn = originalWarn;
|
1754
|
+
restoreConsole();
|
1639
1755
|
return { stdout, stderr, exitCode: 0 };
|
1640
1756
|
}
|
1641
|
-
const result =
|
1642
|
-
|
1757
|
+
const result = await pack(cliOptions.input, cliOptions);
|
1758
|
+
import_fs.default.writeFileSync(outputPath, result.html, "utf-8");
|
1643
1759
|
const meta = result.metadata;
|
1644
1760
|
console.log(`\u2705 Packed: ${meta.input} \u2192 ${outputPath}`);
|
1645
1761
|
console.log(`\u{1F4E6} Size: ${(meta.outputSize / 1024).toFixed(2)} KB`);
|
@@ -1648,7 +1764,7 @@ async function runCli(argv = process.argv) {
|
|
1648
1764
|
if (meta.pagesBundled && meta.pagesBundled > 0) {
|
1649
1765
|
console.log(`\u{1F9E9} Pages: ${meta.pagesBundled}`);
|
1650
1766
|
}
|
1651
|
-
if (meta.errors
|
1767
|
+
if (meta.errors?.length) {
|
1652
1768
|
console.warn(`
|
1653
1769
|
\u26A0\uFE0F ${meta.errors.length} warning(s):`);
|
1654
1770
|
for (const err of meta.errors) {
|
@@ -1658,37 +1774,49 @@ async function runCli(argv = process.argv) {
|
|
1658
1774
|
} catch (err) {
|
1659
1775
|
console.error(`
|
1660
1776
|
\u{1F4A5} Error: ${err?.message || "Unknown failure"}`);
|
1661
|
-
if (err?.stack &&
|
1777
|
+
if (err?.stack && cliOptions?.verbose) {
|
1662
1778
|
console.error(err.stack);
|
1663
1779
|
}
|
1664
1780
|
exitCode = 1;
|
1665
1781
|
} finally {
|
1666
|
-
|
1667
|
-
console.error = originalErr;
|
1668
|
-
console.warn = originalWarn;
|
1782
|
+
restoreConsole();
|
1669
1783
|
}
|
1670
1784
|
return { stdout, stderr, exitCode };
|
1671
1785
|
}
|
1672
|
-
var main;
|
1786
|
+
var import_fs, import_path3, main;
|
1673
1787
|
var init_cli = __esm({
|
1674
1788
|
"src/cli/cli.ts"() {
|
1675
1789
|
"use strict";
|
1790
|
+
import_fs = __toESM(require("fs"), 1);
|
1791
|
+
import_path3 = __toESM(require("path"), 1);
|
1676
1792
|
init_options();
|
1677
1793
|
init_src();
|
1678
|
-
init_types();
|
1679
1794
|
main = runCli;
|
1680
1795
|
}
|
1681
1796
|
});
|
1682
1797
|
|
1683
1798
|
// src/cli/cli-entry.ts
|
1799
|
+
var cli_entry_exports = {};
|
1800
|
+
__export(cli_entry_exports, {
|
1801
|
+
startCLI: () => startCLI
|
1802
|
+
});
|
1803
|
+
module.exports = __toCommonJS(cli_entry_exports);
|
1684
1804
|
var startCLI = async () => {
|
1685
1805
|
const { main: main2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
1686
1806
|
return await main2(process.argv);
|
1687
1807
|
};
|
1688
|
-
if (
|
1689
|
-
startCLI().then(({ exitCode }) =>
|
1808
|
+
if (require.main === module) {
|
1809
|
+
startCLI().then(({ stdout, stderr, exitCode }) => {
|
1810
|
+
if (stdout) process.stdout.write(stdout);
|
1811
|
+
if (stderr) process.stderr.write(stderr);
|
1812
|
+
process.exit(Number(exitCode));
|
1813
|
+
}).catch((err) => {
|
1814
|
+
console.error("\u{1F4A5} Unhandled CLI error:", err);
|
1815
|
+
process.exit(1);
|
1816
|
+
});
|
1690
1817
|
}
|
1691
|
-
export
|
1818
|
+
// Annotate the CommonJS export names for ESM import in node:
|
1819
|
+
0 && (module.exports = {
|
1692
1820
|
startCLI
|
1693
|
-
};
|
1694
|
-
//# sourceMappingURL=cli-entry.
|
1821
|
+
});
|
1822
|
+
//# sourceMappingURL=cli-entry.cjs.map
|