portapack 0.2.1 → 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 +20 -0
- package/README.md +81 -219
- package/dist/cli/{cli-entry.js → cli-entry.cjs} +620 -513
- package/dist/cli/cli-entry.cjs.map +1 -0
- package/dist/index.d.ts +51 -56
- package/dist/index.js +517 -458
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +0 -1
- package/docs/cli.md +108 -45
- package/docs/configuration.md +101 -116
- package/docs/getting-started.md +74 -44
- 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 +364 -277
- 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 +608 -1064
- 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 +310 -265
- package/tests/unit/index.test.ts +206 -300
- package/tests/unit/utils/logger.test.ts +32 -28
- package/tsconfig.jest.json +8 -7
- package/tsup.config.ts +34 -29
- package/dist/cli/cli-entry.js.map +0 -1
- package/docs/demo.md +0 -46
- 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");
|
@@ -275,7 +333,7 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
275
333
|
}
|
276
334
|
try {
|
277
335
|
if (/^https?:\/\//i.test(inputPathOrUrl)) {
|
278
|
-
const url = new
|
336
|
+
const url = new import_url.URL(inputPathOrUrl);
|
279
337
|
url.pathname = url.pathname.substring(0, url.pathname.lastIndexOf("/") + 1);
|
280
338
|
url.search = "";
|
281
339
|
url.hash = "";
|
@@ -286,44 +344,35 @@ function determineBaseUrl(inputPathOrUrl, logger) {
|
|
286
344
|
logger?.warn(`Input "${inputPathOrUrl}" looks like a URL but uses an unsupported protocol. Cannot determine base URL.`);
|
287
345
|
return void 0;
|
288
346
|
} else {
|
289
|
-
let
|
347
|
+
let resourcePath;
|
348
|
+
let isInputLikelyDirectory = false;
|
290
349
|
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
|
-
}
|
350
|
+
resourcePath = (0, import_url.fileURLToPath)(inputPathOrUrl);
|
351
|
+
isInputLikelyDirectory = inputPathOrUrl.endsWith("/");
|
297
352
|
} 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.`);
|
353
|
+
resourcePath = import_path2.default.resolve(inputPathOrUrl);
|
354
|
+
try {
|
355
|
+
isInputLikelyDirectory = fs.statSync(resourcePath).isDirectory();
|
356
|
+
} catch {
|
357
|
+
isInputLikelyDirectory = false;
|
308
358
|
}
|
309
|
-
isDirectory = false;
|
310
359
|
}
|
311
|
-
const
|
312
|
-
let normalizedPathForURL =
|
360
|
+
const baseDirPath = isInputLikelyDirectory ? resourcePath : import_path2.default.dirname(resourcePath);
|
361
|
+
let normalizedPathForURL = baseDirPath.replace(/\\/g, "/");
|
313
362
|
if (/^[A-Z]:\//i.test(normalizedPathForURL) && !normalizedPathForURL.startsWith("/")) {
|
314
363
|
normalizedPathForURL = "/" + normalizedPathForURL;
|
315
364
|
}
|
316
|
-
|
317
|
-
|
318
|
-
if (!fileUrlString.endsWith("/")) {
|
319
|
-
fileUrlString += "/";
|
365
|
+
if (!normalizedPathForURL.endsWith("/")) {
|
366
|
+
normalizedPathForURL += "/";
|
320
367
|
}
|
321
|
-
|
368
|
+
const fileUrl = new import_url.URL("file://" + normalizedPathForURL);
|
369
|
+
const fileUrlString = fileUrl.href;
|
370
|
+
logger?.debug(`Determined base URL: ${fileUrlString} (from: ${inputPathOrUrl}, resolved base dir: ${baseDirPath})`);
|
322
371
|
return fileUrlString;
|
323
372
|
}
|
324
373
|
} catch (error) {
|
325
374
|
const message = error instanceof Error ? error.message : String(error);
|
326
|
-
logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error ? ` - Stack: ${error.stack}` : ""}`);
|
375
|
+
logger?.error(`\u{1F480} Failed to determine base URL for "${inputPathOrUrl}": ${message}${error instanceof Error && error.stack ? ` - Stack: ${error.stack}` : ""}`);
|
327
376
|
return void 0;
|
328
377
|
}
|
329
378
|
}
|
@@ -335,7 +384,7 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
335
384
|
let resolvableUrl = trimmedUrl;
|
336
385
|
if (resolvableUrl.startsWith("//") && baseContextUrl) {
|
337
386
|
try {
|
338
|
-
const base = new
|
387
|
+
const base = new import_url.URL(baseContextUrl);
|
339
388
|
resolvableUrl = base.protocol + resolvableUrl;
|
340
389
|
} catch (e) {
|
341
390
|
logger?.warn(`Could not extract protocol from base "${baseContextUrl}" for protocol-relative URL "${trimmedUrl}". Skipping.`);
|
@@ -343,7 +392,11 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
343
392
|
}
|
344
393
|
}
|
345
394
|
try {
|
346
|
-
const resolved = new
|
395
|
+
const resolved = new import_url.URL(resolvableUrl, baseContextUrl);
|
396
|
+
if (!["http:", "https:", "file:"].includes(resolved.protocol)) {
|
397
|
+
logger?.debug(`Skipping asset with unsupported protocol: ${resolved.href}`);
|
398
|
+
return null;
|
399
|
+
}
|
347
400
|
return resolved;
|
348
401
|
} catch (error) {
|
349
402
|
const message = error instanceof Error ? error.message : String(error);
|
@@ -356,35 +409,15 @@ function resolveAssetUrl(assetUrl, baseContextUrl, logger) {
|
|
356
409
|
}
|
357
410
|
}
|
358
411
|
function resolveCssRelativeUrl(relativeUrl, cssBaseContextUrl, logger) {
|
359
|
-
if (!relativeUrl || relativeUrl.startsWith("data:")) {
|
412
|
+
if (!relativeUrl || relativeUrl.startsWith("data:") || relativeUrl.startsWith("#")) {
|
360
413
|
return null;
|
361
414
|
}
|
362
415
|
try {
|
363
|
-
|
364
|
-
|
365
|
-
let cssDir;
|
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
|
-
}
|
416
|
+
const resolvedUrl = new import_url.URL(relativeUrl, cssBaseContextUrl);
|
417
|
+
return resolvedUrl.href;
|
385
418
|
} catch (error) {
|
386
419
|
logger?.warn(
|
387
|
-
`Failed to resolve CSS URL: "${relativeUrl}"
|
420
|
+
`Failed to resolve CSS URL: "${relativeUrl}" relative to "${cssBaseContextUrl}": ${String(error)}`
|
388
421
|
);
|
389
422
|
return null;
|
390
423
|
}
|
@@ -394,21 +427,24 @@ async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
|
|
394
427
|
const protocol = resolvedUrl.protocol;
|
395
428
|
try {
|
396
429
|
if (protocol === "http:" || protocol === "https:") {
|
397
|
-
const response = await
|
430
|
+
const response = await axiosNs.default.get(resolvedUrl.href, {
|
398
431
|
responseType: "arraybuffer",
|
432
|
+
// Fetch as binary data
|
399
433
|
timeout
|
434
|
+
// Apply network timeout
|
400
435
|
});
|
401
|
-
logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data
|
436
|
+
logger?.debug(`Workspaceed remote asset ${resolvedUrl.href} (Status: ${response.status}, Type: ${response.headers["content-type"] || "N/A"}, Size: ${response.data?.byteLength ?? 0} bytes)`);
|
402
437
|
return Buffer.from(response.data);
|
403
438
|
} else if (protocol === "file:") {
|
404
439
|
let filePath;
|
405
440
|
try {
|
406
|
-
filePath = fileURLToPath(resolvedUrl);
|
441
|
+
filePath = (0, import_url.fileURLToPath)(resolvedUrl);
|
407
442
|
} catch (e) {
|
408
443
|
logger?.error(`Could not convert file URL to path: ${resolvedUrl.href}. Error: ${e.message}`);
|
409
444
|
return null;
|
410
445
|
}
|
411
|
-
const
|
446
|
+
const normalizedForLog = import_path2.default.normalize(filePath);
|
447
|
+
const data = await (0, import_promises.readFile)(filePath);
|
412
448
|
logger?.debug(`Read local file ${filePath} (${data.byteLength} bytes)`);
|
413
449
|
return data;
|
414
450
|
} else {
|
@@ -416,27 +452,26 @@ async function fetchAsset(resolvedUrl, logger, timeout = 1e4) {
|
|
416
452
|
return null;
|
417
453
|
}
|
418
454
|
} catch (error) {
|
419
|
-
|
420
|
-
|
421
|
-
const
|
422
|
-
const
|
423
|
-
const
|
424
|
-
const logMessage = `\u26A0\uFE0F Failed to fetch remote asset ${resolvedUrl.href}:
|
455
|
+
const failedId = protocol === "file:" ? import_path2.default.normalize((0, import_url.fileURLToPath)(resolvedUrl)) : resolvedUrl.href;
|
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})`;
|
425
461
|
logger?.warn(logMessage);
|
426
|
-
} else if (protocol === "file:") {
|
462
|
+
} else if (protocol === "file:" && error instanceof Error) {
|
427
463
|
let failedPath = resolvedUrl.href;
|
428
464
|
try {
|
429
|
-
failedPath = fileURLToPath(resolvedUrl);
|
465
|
+
failedPath = (0, import_url.fileURLToPath)(resolvedUrl);
|
430
466
|
} catch {
|
431
467
|
}
|
432
|
-
|
468
|
+
failedPath = import_path2.default.normalize(failedPath);
|
469
|
+
if (error.code === "ENOENT") {
|
433
470
|
logger?.warn(`\u26A0\uFE0F File not found (ENOENT) for asset: ${failedPath}.`);
|
434
|
-
} else if (error
|
471
|
+
} else if (error.code === "EACCES") {
|
435
472
|
logger?.warn(`\u26A0\uFE0F Permission denied (EACCES) reading asset: ${failedPath}.`);
|
436
|
-
} else if (error instanceof Error) {
|
437
|
-
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
438
473
|
} else {
|
439
|
-
logger?.warn(`\u26A0\uFE0F
|
474
|
+
logger?.warn(`\u26A0\uFE0F Failed to read local asset ${failedPath}: ${error.message}`);
|
440
475
|
}
|
441
476
|
} else if (error instanceof Error) {
|
442
477
|
logger?.warn(`\u26A0\uFE0F An unexpected error occurred processing asset ${resolvedUrl.href}: ${error.message}`);
|
@@ -452,7 +487,7 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
|
|
452
487
|
const urlRegex = /url\(\s*(['"]?)(.*?)\1\s*\)/gi;
|
453
488
|
const importRegex = /@import\s+(?:url\(\s*(['"]?)(.*?)\1\s*\)|(['"])(.*?)\3)\s*;/gi;
|
454
489
|
const processFoundUrl = (rawUrl, ruleType) => {
|
455
|
-
if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:")) return;
|
490
|
+
if (!rawUrl || rawUrl.trim() === "" || rawUrl.startsWith("data:") || rawUrl.startsWith("#")) return;
|
456
491
|
const resolvedUrl = resolveCssRelativeUrl(rawUrl, cssBaseContextUrl, logger);
|
457
492
|
if (resolvedUrl && !processedInThisParse.has(resolvedUrl)) {
|
458
493
|
processedInThisParse.add(resolvedUrl);
|
@@ -460,14 +495,13 @@ function extractUrlsFromCSS(cssContent, cssBaseContextUrl, logger) {
|
|
460
495
|
newlyDiscovered.push({
|
461
496
|
type: assetType,
|
462
497
|
url: resolvedUrl,
|
463
|
-
//
|
498
|
+
// Store the resolved absolute URL string
|
464
499
|
content: void 0
|
500
|
+
// Content will be fetched later if needed
|
465
501
|
});
|
466
502
|
logger?.debug(`Discovered nested ${assetType} asset (${ruleType}) in CSS ${cssBaseContextUrl}: ${resolvedUrl}`);
|
467
503
|
}
|
468
504
|
};
|
469
|
-
urlRegex.lastIndex = 0;
|
470
|
-
importRegex.lastIndex = 0;
|
471
505
|
let match;
|
472
506
|
while ((match = urlRegex.exec(cssContent)) !== null) {
|
473
507
|
processFoundUrl(match[2], "url()");
|
@@ -483,31 +517,35 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
483
517
|
const initialAssets = parsed.assets || [];
|
484
518
|
const finalAssetsMap = /* @__PURE__ */ new Map();
|
485
519
|
let assetsToProcess = [];
|
520
|
+
const processedOrQueuedUrls = /* @__PURE__ */ new Set();
|
486
521
|
const htmlBaseContextUrl = determineBaseUrl(inputPathOrUrl || "", logger);
|
487
522
|
if (!htmlBaseContextUrl && initialAssets.some((a) => !/^[a-z]+:/i.test(a.url) && !a.url.startsWith("data:") && !a.url.startsWith("#") && !a.url.startsWith("/"))) {
|
488
523
|
logger?.warn("\u{1F6A8} No valid base path/URL determined for the HTML source! Resolution of relative asset paths from HTML may fail.");
|
489
524
|
} else if (htmlBaseContextUrl) {
|
490
525
|
logger?.debug(`Using HTML base context URL: ${htmlBaseContextUrl}`);
|
491
526
|
}
|
492
|
-
const processedOrQueuedUrls = /* @__PURE__ */ new Set();
|
493
527
|
logger?.debug(`Queueing ${initialAssets.length} initial assets parsed from HTML...`);
|
494
528
|
for (const asset of initialAssets) {
|
495
529
|
const resolvedUrlObj = resolveAssetUrl(asset.url, htmlBaseContextUrl, logger);
|
496
|
-
|
497
|
-
|
530
|
+
if (!resolvedUrlObj) {
|
531
|
+
logger?.debug(` -> Skipping initial asset with unresolvable/ignorable URL: ${asset.url}`);
|
532
|
+
continue;
|
533
|
+
}
|
534
|
+
const urlToQueue = resolvedUrlObj.href;
|
535
|
+
if (!processedOrQueuedUrls.has(urlToQueue)) {
|
498
536
|
processedOrQueuedUrls.add(urlToQueue);
|
499
537
|
const { assetType: guessedType } = guessMimeType(urlToQueue);
|
500
538
|
const initialType = asset.type ?? guessedType;
|
501
539
|
assetsToProcess.push({
|
502
540
|
url: urlToQueue,
|
541
|
+
// Use the resolved URL
|
503
542
|
type: initialType,
|
504
543
|
content: void 0
|
544
|
+
// Content is initially undefined
|
505
545
|
});
|
506
546
|
logger?.debug(` -> Queued initial asset: ${urlToQueue} (Original raw: ${asset.url})`);
|
507
|
-
} else if (urlToQueue.startsWith("data:")) {
|
508
|
-
logger?.debug(` -> Skipping data URI: ${urlToQueue.substring(0, 50)}...`);
|
509
547
|
} else {
|
510
|
-
logger?.debug(` -> Skipping already queued initial asset: ${urlToQueue}`);
|
548
|
+
logger?.debug(` -> Skipping already processed/queued initial asset: ${urlToQueue}`);
|
511
549
|
}
|
512
550
|
}
|
513
551
|
let iterationCount = 0;
|
@@ -540,7 +578,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
540
578
|
let assetUrlObj = null;
|
541
579
|
if (needsFetching) {
|
542
580
|
try {
|
543
|
-
assetUrlObj = new
|
581
|
+
assetUrlObj = new import_url.URL(asset.url);
|
544
582
|
} catch (urlError) {
|
545
583
|
logger?.warn(`Cannot create URL object for "${asset.url}", skipping fetch. Error: ${urlError instanceof Error ? urlError.message : String(urlError)}`);
|
546
584
|
finalAssetsMap.set(asset.url, { ...asset, content: void 0 });
|
@@ -578,7 +616,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
578
616
|
cssContentForParsing = textContent;
|
579
617
|
}
|
580
618
|
} else {
|
581
|
-
logger?.warn(`Could not decode ${asset.type} ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`);
|
619
|
+
logger?.warn(`Could not decode ${asset.type} asset ${asset.url} as valid UTF-8 text.${embedAssets ? " Falling back to base64 data URI." : ""}`);
|
582
620
|
cssContentForParsing = void 0;
|
583
621
|
if (embedAssets) {
|
584
622
|
finalContent = `data:${effectiveMime};base64,${assetContentBuffer.toString("base64")}`;
|
@@ -625,6 +663,7 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
625
663
|
const newlyDiscoveredAssets = extractUrlsFromCSS(
|
626
664
|
cssContentForParsing,
|
627
665
|
cssBaseContextUrl,
|
666
|
+
// Use the CSS file's own URL as the base
|
628
667
|
logger
|
629
668
|
);
|
630
669
|
if (newlyDiscoveredAssets.length > 0) {
|
@@ -645,17 +684,22 @@ async function extractAssets(parsed, embedAssets = true, inputPathOrUrl, logger)
|
|
645
684
|
}
|
646
685
|
}
|
647
686
|
}
|
648
|
-
const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ?
|
687
|
+
const finalIterationCount = iterationCount > MAX_ASSET_EXTRACTION_ITERATIONS ? `${MAX_ASSET_EXTRACTION_ITERATIONS}+ (limit hit)` : iterationCount;
|
649
688
|
logger?.info(`\u2705 Asset extraction COMPLETE! Found ${finalAssetsMap.size} unique assets in ${finalIterationCount} iterations.`);
|
650
689
|
return {
|
651
690
|
htmlContent: parsed.htmlContent,
|
652
691
|
assets: Array.from(finalAssetsMap.values())
|
653
692
|
};
|
654
693
|
}
|
655
|
-
var TEXT_ASSET_TYPES, BINARY_ASSET_TYPES, MAX_ASSET_EXTRACTION_ITERATIONS;
|
694
|
+
var import_promises, fs, import_path2, import_url, axiosNs, TEXT_ASSET_TYPES, BINARY_ASSET_TYPES, MAX_ASSET_EXTRACTION_ITERATIONS;
|
656
695
|
var init_extractor = __esm({
|
657
696
|
"src/core/extractor.ts"() {
|
658
697
|
"use strict";
|
698
|
+
import_promises = require("fs/promises");
|
699
|
+
fs = __toESM(require("fs"), 1);
|
700
|
+
import_path2 = __toESM(require("path"), 1);
|
701
|
+
import_url = require("url");
|
702
|
+
axiosNs = __toESM(require("axios"), 1);
|
659
703
|
init_mime();
|
660
704
|
TEXT_ASSET_TYPES = /* @__PURE__ */ new Set(["css", "js"]);
|
661
705
|
BINARY_ASSET_TYPES = /* @__PURE__ */ new Set(["image", "font", "video", "audio"]);
|
@@ -664,9 +708,6 @@ var init_extractor = __esm({
|
|
664
708
|
});
|
665
709
|
|
666
710
|
// 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
711
|
async function minifyAssets(parsed, options = {}, logger) {
|
671
712
|
const { htmlContent, assets } = parsed;
|
672
713
|
const currentHtmlContent = htmlContent ?? "";
|
@@ -692,7 +733,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
692
733
|
try {
|
693
734
|
if (minifyFlags.minifyCss && processedAsset.type === "css") {
|
694
735
|
logger?.debug(`Minifying CSS: ${assetIdentifier}`);
|
695
|
-
const cssMinifier = new
|
736
|
+
const cssMinifier = new import_clean_css.default(CSS_MINIFY_OPTIONS);
|
696
737
|
const result = cssMinifier.minify(processedAsset.content);
|
697
738
|
if (result.errors && result.errors.length > 0) {
|
698
739
|
logger?.warn(`\u26A0\uFE0F CleanCSS failed for ${assetIdentifier}: ${result.errors.join(", ")}`);
|
@@ -710,7 +751,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
710
751
|
}
|
711
752
|
if (minifyFlags.minifyJs && processedAsset.type === "js") {
|
712
753
|
logger?.debug(`Minifying JS: ${assetIdentifier}`);
|
713
|
-
const result = await
|
754
|
+
const result = await (0, import_terser.minify)(processedAsset.content, JS_MINIFY_OPTIONS);
|
714
755
|
if (result.code) {
|
715
756
|
newContent = result.code;
|
716
757
|
logger?.debug(`JS minified successfully: ${assetIdentifier}`);
|
@@ -735,7 +776,7 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
735
776
|
if (minifyFlags.minifyHtml && finalHtml.length > 0) {
|
736
777
|
logger?.debug("Minifying HTML content...");
|
737
778
|
try {
|
738
|
-
finalHtml = await
|
779
|
+
finalHtml = await (0, import_html_minifier_terser.minify)(finalHtml, {
|
739
780
|
...HTML_MINIFY_OPTIONS,
|
740
781
|
minifyCSS: minifyFlags.minifyCss,
|
741
782
|
minifyJS: minifyFlags.minifyJs
|
@@ -754,10 +795,13 @@ async function minifyAssets(parsed, options = {}, logger) {
|
|
754
795
|
// The array of processed asset copies
|
755
796
|
};
|
756
797
|
}
|
757
|
-
var HTML_MINIFY_OPTIONS, CSS_MINIFY_OPTIONS, JS_MINIFY_OPTIONS;
|
798
|
+
var import_html_minifier_terser, import_clean_css, import_terser, HTML_MINIFY_OPTIONS, CSS_MINIFY_OPTIONS, JS_MINIFY_OPTIONS;
|
758
799
|
var init_minifier = __esm({
|
759
800
|
"src/core/minifier.ts"() {
|
760
801
|
"use strict";
|
802
|
+
import_html_minifier_terser = require("html-minifier-terser");
|
803
|
+
import_clean_css = __toESM(require("clean-css"), 1);
|
804
|
+
import_terser = require("terser");
|
761
805
|
HTML_MINIFY_OPTIONS = {
|
762
806
|
collapseWhitespace: true,
|
763
807
|
removeComments: true,
|
@@ -815,7 +859,6 @@ var init_minifier = __esm({
|
|
815
859
|
});
|
816
860
|
|
817
861
|
// src/core/packer.ts
|
818
|
-
import * as cheerio2 from "cheerio";
|
819
862
|
function escapeScriptContent(code) {
|
820
863
|
return code.replace(/<\/(script)/gi, "<\\/$1");
|
821
864
|
}
|
@@ -928,7 +971,7 @@ function packHTML(parsed, logger) {
|
|
928
971
|
return '<!DOCTYPE html><html><head><base href="./"></head><body></body></html>';
|
929
972
|
}
|
930
973
|
logger?.debug("Loading HTML content into Cheerio for packing...");
|
931
|
-
const $ =
|
974
|
+
const $ = cheerio.load(htmlContent);
|
932
975
|
logger?.debug("Ensuring <base> tag exists...");
|
933
976
|
ensureBaseTag($, logger);
|
934
977
|
logger?.debug("Starting asset inlining...");
|
@@ -938,122 +981,11 @@ function packHTML(parsed, logger) {
|
|
938
981
|
logger?.debug(`Packing complete. Final size: ${Buffer.byteLength(finalHtml)} bytes.`);
|
939
982
|
return finalHtml;
|
940
983
|
}
|
984
|
+
var cheerio;
|
941
985
|
var init_packer = __esm({
|
942
986
|
"src/core/packer.ts"() {
|
943
987
|
"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
|
-
};
|
988
|
+
cheerio = __toESM(require("cheerio"), 1);
|
1057
989
|
}
|
1058
990
|
});
|
1059
991
|
|
@@ -1093,9 +1025,11 @@ function bundleMultiPageHTML(pages, logger) {
|
|
1093
1025
|
throw new Error(errorMsg);
|
1094
1026
|
}
|
1095
1027
|
logger?.info(`Bundling ${pages.length} pages into a multi-page HTML document.`);
|
1028
|
+
let pageIndex = 0;
|
1096
1029
|
const validPages = pages.filter((page) => {
|
1097
1030
|
const isValid = page && typeof page === "object" && typeof page.url === "string" && typeof page.html === "string";
|
1098
|
-
if (!isValid) logger?.warn(
|
1031
|
+
if (!isValid) logger?.warn(`Skipping invalid page entry at index ${pageIndex}`);
|
1032
|
+
pageIndex++;
|
1099
1033
|
return isValid;
|
1100
1034
|
});
|
1101
1035
|
if (validPages.length === 0) {
|
@@ -1105,70 +1039,137 @@ function bundleMultiPageHTML(pages, logger) {
|
|
1105
1039
|
}
|
1106
1040
|
const slugMap = /* @__PURE__ */ new Map();
|
1107
1041
|
const usedSlugs = /* @__PURE__ */ new Set();
|
1042
|
+
let firstValidSlug = void 0;
|
1043
|
+
let pageCounterForFallback = 1;
|
1108
1044
|
for (const page of validPages) {
|
1109
|
-
|
1045
|
+
let baseSlug = sanitizeSlug(page.url);
|
1046
|
+
const isRootIndex = page.url === "/" || page.url === "index.html" || page.url.endsWith("/index.html");
|
1047
|
+
if (baseSlug === "index" && !isRootIndex) {
|
1048
|
+
logger?.debug(`URL "${page.url}" sanitized to "index", attempting to find alternative slug.`);
|
1049
|
+
const pathParts = page.url.replace(/\/$/, "").split("/").filter((p) => p && p.toLowerCase() !== "index.html" && p.toLowerCase() !== "index");
|
1050
|
+
if (pathParts.length > 0) {
|
1051
|
+
const lastPartSlug = sanitizeSlug(pathParts[pathParts.length - 1]);
|
1052
|
+
if (lastPartSlug && lastPartSlug !== "index") {
|
1053
|
+
baseSlug = lastPartSlug;
|
1054
|
+
logger?.debug(`Using last path part slug "${baseSlug}" instead.`);
|
1055
|
+
} else {
|
1056
|
+
baseSlug = "page";
|
1057
|
+
logger?.debug(`Last path part invalid ("${lastPartSlug}"), using fallback slug "page".`);
|
1058
|
+
}
|
1059
|
+
} else {
|
1060
|
+
baseSlug = "page";
|
1061
|
+
logger?.debug(`No valid path parts found, using fallback slug "page".`);
|
1062
|
+
}
|
1063
|
+
} else if (!baseSlug) {
|
1064
|
+
if (isRootIndex) {
|
1065
|
+
baseSlug = "index";
|
1066
|
+
logger?.debug(`URL "${page.url}" sanitized to empty string, using "index" as it is a root index.`);
|
1067
|
+
} else {
|
1068
|
+
baseSlug = "page";
|
1069
|
+
logger?.debug(`URL "${page.url}" sanitized to empty string, using fallback slug "page".`);
|
1070
|
+
}
|
1071
|
+
}
|
1072
|
+
if (!baseSlug) {
|
1073
|
+
baseSlug = `page-${pageCounterForFallback++}`;
|
1074
|
+
logger?.warn(`Could not determine a valid base slug for "${page.url}", using generated fallback "${baseSlug}".`);
|
1075
|
+
}
|
1110
1076
|
let slug = baseSlug;
|
1111
|
-
let
|
1077
|
+
let collisionCounter = 1;
|
1078
|
+
const originalBaseSlugForLog = baseSlug;
|
1112
1079
|
while (usedSlugs.has(slug)) {
|
1113
|
-
|
1114
|
-
logger?.warn(`Slug collision detected for "${page.url}". Using "${
|
1080
|
+
const newSlug = `${originalBaseSlugForLog}-${collisionCounter++}`;
|
1081
|
+
logger?.warn(`Slug collision detected for "${page.url}" (intended slug: '${originalBaseSlugForLog}'). Using "${newSlug}" instead.`);
|
1082
|
+
slug = newSlug;
|
1115
1083
|
}
|
1116
1084
|
usedSlugs.add(slug);
|
1117
1085
|
slugMap.set(page.url, slug);
|
1086
|
+
if (firstValidSlug === void 0) {
|
1087
|
+
firstValidSlug = slug;
|
1088
|
+
}
|
1118
1089
|
}
|
1119
|
-
const defaultPageSlug =
|
1090
|
+
const defaultPageSlug = usedSlugs.has("index") ? "index" : firstValidSlug || "page";
|
1120
1091
|
let output = `<!DOCTYPE html>
|
1121
1092
|
<html lang="en">
|
1122
1093
|
<head>
|
1123
1094
|
<meta charset="UTF-8">
|
1124
1095
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
1125
1096
|
<title>Multi-Page Bundle</title>
|
1097
|
+
<style>
|
1098
|
+
body { font-family: sans-serif; margin: 0; }
|
1099
|
+
#main-nav { background-color: #f0f0f0; padding: 10px; border-bottom: 1px solid #ccc; }
|
1100
|
+
#main-nav a { margin-right: 15px; text-decoration: none; color: #007bff; }
|
1101
|
+
#main-nav a.active { font-weight: bold; text-decoration: underline; }
|
1102
|
+
#page-container { padding: 20px; }
|
1103
|
+
template { display: none; }
|
1104
|
+
</style>
|
1126
1105
|
</head>
|
1127
1106
|
<body>
|
1128
1107
|
<nav id="main-nav">
|
1129
1108
|
${validPages.map((p) => {
|
1130
1109
|
const slug = slugMap.get(p.url);
|
1131
|
-
const label =
|
1110
|
+
const label = slug;
|
1132
1111
|
return `<a href="#${slug}" data-page="${slug}">${label}</a>`;
|
1133
|
-
}).join("\n")}
|
1112
|
+
}).join("\n ")}
|
1134
1113
|
</nav>
|
1135
1114
|
<div id="page-container"></div>
|
1136
1115
|
${validPages.map((p) => {
|
1137
1116
|
const slug = slugMap.get(p.url);
|
1138
1117
|
return `<template id="page-${slug}">${p.html}</template>`;
|
1139
|
-
}).join("\n")}
|
1118
|
+
}).join("\n ")}
|
1140
1119
|
<script id="router-script">
|
1141
1120
|
document.addEventListener('DOMContentLoaded', function() {
|
1121
|
+
const pageContainer = document.getElementById('page-container');
|
1122
|
+
const navLinks = document.querySelectorAll('#main-nav a');
|
1123
|
+
|
1142
1124
|
function navigateTo(slug) {
|
1143
1125
|
const template = document.getElementById('page-' + slug);
|
1144
|
-
|
1145
|
-
|
1146
|
-
|
1147
|
-
|
1148
|
-
|
1149
|
-
|
1150
|
-
|
1126
|
+
if (!template || !pageContainer) {
|
1127
|
+
console.warn('Navigation failed: Template or container not found for slug:', slug);
|
1128
|
+
// Maybe try navigating to default page? Or just clear container?
|
1129
|
+
if (pageContainer) pageContainer.innerHTML = '<p>Page not found.</p>';
|
1130
|
+
return;
|
1131
|
+
}
|
1132
|
+
// Clear previous content and append new content
|
1133
|
+
pageContainer.innerHTML = ''; // Clear reliably
|
1134
|
+
pageContainer.appendChild(template.content.cloneNode(true));
|
1135
|
+
|
1136
|
+
// Update active link styling
|
1137
|
+
navLinks.forEach(link => {
|
1138
|
+
link.classList.toggle('active', link.getAttribute('data-page') === slug);
|
1151
1139
|
});
|
1140
|
+
|
1141
|
+
// Update URL hash without triggering hashchange if already correct
|
1152
1142
|
if (window.location.hash.substring(1) !== slug) {
|
1153
|
-
|
1143
|
+
// Use pushState for cleaner history
|
1144
|
+
history.pushState({ slug: slug }, '', '#' + slug);
|
1154
1145
|
}
|
1155
1146
|
}
|
1156
1147
|
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1148
|
+
// Handle back/forward navigation
|
1149
|
+
window.addEventListener('popstate', (event) => {
|
1150
|
+
let slug = window.location.hash.substring(1);
|
1151
|
+
// If popstate event has state use it, otherwise fallback to hash or default
|
1152
|
+
if (event && event.state && event.state.slug) { // Check event exists
|
1153
|
+
slug = event.state.slug;
|
1154
|
+
}
|
1155
|
+
// Ensure the target page exists before navigating, fallback to default slug
|
1156
|
+
const targetSlug = document.getElementById('page-' + slug) ? slug : '${defaultPageSlug}';
|
1157
|
+
navigateTo(targetSlug);
|
1160
1158
|
});
|
1161
1159
|
|
1162
|
-
|
1160
|
+
// Handle direct link clicks
|
1161
|
+
navLinks.forEach(link => {
|
1163
1162
|
link.addEventListener('click', function(e) {
|
1164
1163
|
e.preventDefault();
|
1165
1164
|
const slug = this.getAttribute('data-page');
|
1166
|
-
navigateTo(slug);
|
1165
|
+
if (slug) navigateTo(slug);
|
1167
1166
|
});
|
1168
1167
|
});
|
1169
1168
|
|
1170
|
-
|
1171
|
-
|
1169
|
+
// Initial page load
|
1170
|
+
const initialHash = window.location.hash.substring(1);
|
1171
|
+
const initialSlug = document.getElementById('page-' + initialHash) ? initialHash : '${defaultPageSlug}';
|
1172
|
+
navigateTo(initialSlug);
|
1172
1173
|
});
|
1173
1174
|
</script>
|
1174
1175
|
</body>
|
@@ -1182,58 +1183,69 @@ var init_bundler = __esm({
|
|
1182
1183
|
init_extractor();
|
1183
1184
|
init_minifier();
|
1184
1185
|
init_packer();
|
1186
|
+
init_types();
|
1185
1187
|
init_slugify();
|
1186
1188
|
}
|
1187
1189
|
});
|
1188
1190
|
|
1189
1191
|
// src/core/web-fetcher.ts
|
1190
|
-
|
1191
|
-
import * as fs2 from "fs/promises";
|
1192
|
-
async function fetchAndPackWebPage(url, logger, timeout = 3e4) {
|
1192
|
+
async function fetchAndPackWebPage(url, logger, timeout = DEFAULT_PAGE_TIMEOUT, userAgent) {
|
1193
1193
|
let browser = null;
|
1194
1194
|
const start = Date.now();
|
1195
|
-
logger?.
|
1195
|
+
logger?.info(`Initiating fetch for single page: ${url}`);
|
1196
1196
|
try {
|
1197
|
-
|
1198
|
-
|
1197
|
+
logger?.debug("Launching browser...");
|
1198
|
+
browser = await puppeteer.launch(PUPPETEER_LAUNCH_OPTIONS);
|
1199
|
+
logger?.debug(`Browser launched successfully (PID: ${browser.process()?.pid}).`);
|
1199
1200
|
const page = await browser.newPage();
|
1200
|
-
logger?.debug(`
|
1201
|
+
logger?.debug(`New page created for ${url}`);
|
1202
|
+
if (userAgent) {
|
1203
|
+
await page.setUserAgent(userAgent);
|
1204
|
+
logger?.debug(`User-Agent set to: "${userAgent}"`);
|
1205
|
+
}
|
1201
1206
|
try {
|
1202
1207
|
logger?.debug(`Navigating to ${url} with timeout ${timeout}ms`);
|
1203
1208
|
await page.goto(url, { waitUntil: "networkidle2", timeout });
|
1204
1209
|
logger?.debug(`Navigation successful for ${url}`);
|
1205
1210
|
const html = await page.content();
|
1206
|
-
logger?.debug(`Content retrieved for ${url}`);
|
1211
|
+
logger?.debug(`Content retrieved for ${url} (${Buffer.byteLength(html, "utf-8")} bytes)`);
|
1207
1212
|
const metadata = {
|
1208
1213
|
input: url,
|
1209
1214
|
outputSize: Buffer.byteLength(html, "utf-8"),
|
1210
1215
|
assetCount: 0,
|
1211
|
-
// Basic fetch doesn't track assets
|
1216
|
+
// Basic fetch doesn't track assets processed by *this* tool
|
1212
1217
|
buildTimeMs: Date.now() - start,
|
1213
1218
|
errors: []
|
1214
1219
|
// No errors if we reached this point
|
1215
1220
|
};
|
1216
1221
|
await page.close();
|
1217
1222
|
logger?.debug(`Page closed for ${url}`);
|
1223
|
+
await browser.close();
|
1218
1224
|
logger?.debug(`Browser closed for ${url}`);
|
1219
1225
|
browser = null;
|
1220
1226
|
return { html, metadata };
|
1221
1227
|
} catch (pageError) {
|
1222
1228
|
logger?.error(`Error during page processing for ${url}: ${pageError.message}`);
|
1223
|
-
|
1224
|
-
|
1225
|
-
|
1226
|
-
|
1229
|
+
if (page && !page.isClosed()) {
|
1230
|
+
try {
|
1231
|
+
await page.close();
|
1232
|
+
logger?.debug(`Page closed after error for ${url}`);
|
1233
|
+
} catch (closeErr) {
|
1234
|
+
logger?.error(`Failed to close page after error for ${url}: ${closeErr.message}`);
|
1235
|
+
}
|
1227
1236
|
}
|
1228
1237
|
throw pageError;
|
1229
1238
|
}
|
1230
1239
|
} catch (launchError) {
|
1231
|
-
logger?.error(`Critical error during browser launch or page
|
1240
|
+
logger?.error(`Critical error during browser launch or page setup for ${url}: ${launchError.message}`);
|
1232
1241
|
if (browser) {
|
1233
1242
|
try {
|
1234
1243
|
await browser.close();
|
1244
|
+
logger?.debug("Browser closed after launch/setup error.");
|
1235
1245
|
} catch (closeErr) {
|
1246
|
+
logger?.warn(`Failed to close browser after launch/setup error: ${closeErr.message}`);
|
1236
1247
|
}
|
1248
|
+
browser = null;
|
1237
1249
|
}
|
1238
1250
|
throw launchError;
|
1239
1251
|
} finally {
|
@@ -1246,99 +1258,123 @@ async function fetchAndPackWebPage(url, logger, timeout = 3e4) {
|
|
1246
1258
|
}
|
1247
1259
|
}
|
1248
1260
|
}
|
1249
|
-
async function crawlWebsite(startUrl,
|
1261
|
+
async function crawlWebsite(startUrl, options) {
|
1262
|
+
const {
|
1263
|
+
maxDepth = 1,
|
1264
|
+
timeout = DEFAULT_PAGE_TIMEOUT,
|
1265
|
+
// include = ['**'], // TODO: Implement glob filtering
|
1266
|
+
// exclude = [],
|
1267
|
+
userAgent,
|
1268
|
+
logger
|
1269
|
+
} = options;
|
1250
1270
|
logger?.info(`Starting crawl for ${startUrl} with maxDepth ${maxDepth}`);
|
1251
1271
|
if (maxDepth <= 0) {
|
1252
|
-
logger?.
|
1272
|
+
logger?.warn("maxDepth is 0 or negative, no pages will be crawled.");
|
1253
1273
|
return [];
|
1254
1274
|
}
|
1255
|
-
|
1275
|
+
let browser = null;
|
1256
1276
|
const visited = /* @__PURE__ */ new Set();
|
1257
1277
|
const results = [];
|
1258
1278
|
const queue = [];
|
1259
1279
|
let startOrigin;
|
1260
1280
|
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
1281
|
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
|
-
|
1282
|
+
startOrigin = new URL(startUrl).origin;
|
1283
|
+
} catch (e) {
|
1284
|
+
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1285
|
+
throw new Error(`Invalid start URL: ${startUrl}`);
|
1286
|
+
}
|
1287
|
+
let normalizedStartUrl;
|
1288
|
+
try {
|
1289
|
+
const parsedStartUrl = new URL(startUrl);
|
1290
|
+
parsedStartUrl.hash = "";
|
1291
|
+
normalizedStartUrl = parsedStartUrl.href;
|
1292
|
+
} catch (e) {
|
1293
|
+
logger?.error(`Invalid start URL: ${startUrl}. ${e.message}`);
|
1294
|
+
throw new Error(`Invalid start URL: ${startUrl}`);
|
1295
|
+
}
|
1296
|
+
logger?.debug("Launching browser for crawl...");
|
1297
|
+
browser = await puppeteer.launch(PUPPETEER_LAUNCH_OPTIONS);
|
1298
|
+
logger?.debug(`Browser launched for crawl (PID: ${browser.process()?.pid}).`);
|
1299
|
+
visited.add(normalizedStartUrl);
|
1300
|
+
queue.push({ url: normalizedStartUrl, depth: 1 });
|
1301
|
+
logger?.debug(`Queued initial URL: ${normalizedStartUrl} (depth 1)`);
|
1302
|
+
while (queue.length > 0) {
|
1303
|
+
const { url, depth } = queue.shift();
|
1304
|
+
logger?.info(`Processing: ${url} (depth ${depth})`);
|
1305
|
+
let page = null;
|
1306
|
+
try {
|
1307
|
+
page = await browser.newPage();
|
1308
|
+
if (userAgent) {
|
1309
|
+
await page.setUserAgent(userAgent);
|
1310
|
+
}
|
1311
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout });
|
1312
|
+
const html = await page.content();
|
1313
|
+
results.push({ url, html });
|
1314
|
+
logger?.debug(`Successfully fetched content for ${url}`);
|
1315
|
+
if (depth < maxDepth) {
|
1316
|
+
logger?.debug(`Discovering links on ${url} (depth ${depth}/${maxDepth})`);
|
1317
|
+
const hrefs = await page.evaluate(
|
1318
|
+
() => Array.from(document.querySelectorAll("a[href]"), (a) => a.getAttribute("href"))
|
1319
|
+
);
|
1320
|
+
logger?.debug(`Found ${hrefs.length} potential hrefs on ${url}`);
|
1321
|
+
let linksAdded = 0;
|
1322
|
+
for (const href of hrefs) {
|
1323
|
+
if (!href) continue;
|
1324
|
+
let absoluteUrl;
|
1325
|
+
try {
|
1326
|
+
const resolved = new URL(href, url);
|
1327
|
+
resolved.hash = "";
|
1328
|
+
absoluteUrl = resolved.href;
|
1329
|
+
} catch (e) {
|
1330
|
+
logger?.debug(`Ignoring invalid URL syntax: "${href}" on page ${url}`);
|
1331
|
+
continue;
|
1332
|
+
}
|
1333
|
+
if (absoluteUrl.startsWith(startOrigin) && !visited.has(absoluteUrl)) {
|
1334
|
+
visited.add(absoluteUrl);
|
1335
|
+
queue.push({ url: absoluteUrl, depth: depth + 1 });
|
1336
|
+
linksAdded++;
|
1337
|
+
}
|
1314
1338
|
}
|
1339
|
+
logger?.debug(`Added ${linksAdded} new unique internal links to queue from ${url}`);
|
1340
|
+
} else {
|
1341
|
+
logger?.debug(`Max depth (${maxDepth}) reached, not discovering links on ${url}`);
|
1315
1342
|
}
|
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}`);
|
1343
|
+
} catch (err) {
|
1344
|
+
logger?.warn(`\u274C Failed to process ${url}: ${err.message}`);
|
1345
|
+
} finally {
|
1346
|
+
if (page && !page.isClosed()) {
|
1347
|
+
try {
|
1348
|
+
await page.close();
|
1349
|
+
} catch (pageCloseError) {
|
1350
|
+
logger?.error(`Failed to close page for ${url}: ${pageCloseError.message}`);
|
1351
|
+
}
|
1328
1352
|
}
|
1329
1353
|
}
|
1330
1354
|
}
|
1355
|
+
} catch (error) {
|
1356
|
+
logger?.error(`Critical crawl error: ${error instanceof Error ? error.message : error}`);
|
1357
|
+
throw error;
|
1358
|
+
} finally {
|
1359
|
+
if (browser) {
|
1360
|
+
logger?.info(`Crawl finished or errored. Closing browser.`);
|
1361
|
+
await browser.close();
|
1362
|
+
logger?.debug(`Browser closed after crawl.`);
|
1363
|
+
}
|
1331
1364
|
}
|
1332
|
-
logger?.info(`Crawl
|
1333
|
-
await browser.close();
|
1334
|
-
logger?.info(`Found ${results.length} pages.`);
|
1365
|
+
logger?.info(`Crawl found ${results.length} pages.`);
|
1335
1366
|
return results;
|
1336
1367
|
}
|
1337
|
-
async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1) {
|
1338
|
-
const logger = new Logger();
|
1368
|
+
async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1, loggerInstance) {
|
1369
|
+
const logger = loggerInstance || new Logger();
|
1339
1370
|
logger.info(`Starting recursive site bundle for ${startUrl} to ${outputFile} (maxDepth: ${maxDepth})`);
|
1340
1371
|
try {
|
1341
|
-
const
|
1372
|
+
const crawlOptions = {
|
1373
|
+
maxDepth,
|
1374
|
+
logger
|
1375
|
+
/* Add other options like timeout, userAgent if needed */
|
1376
|
+
};
|
1377
|
+
const pages = await crawlWebsite(startUrl, crawlOptions);
|
1342
1378
|
if (pages.length === 0) {
|
1343
1379
|
logger.warn("Crawl completed but found 0 pages. Output file may be empty or reflect an empty bundle.");
|
1344
1380
|
} else {
|
@@ -1361,11 +1397,98 @@ async function recursivelyBundleSite(startUrl, outputFile, maxDepth = 1) {
|
|
1361
1397
|
throw error;
|
1362
1398
|
}
|
1363
1399
|
}
|
1400
|
+
var puppeteer, fs2, PUPPETEER_LAUNCH_OPTIONS, DEFAULT_PAGE_TIMEOUT;
|
1364
1401
|
var init_web_fetcher = __esm({
|
1365
1402
|
"src/core/web-fetcher.ts"() {
|
1366
1403
|
"use strict";
|
1404
|
+
puppeteer = __toESM(require("puppeteer"), 1);
|
1405
|
+
fs2 = __toESM(require("fs/promises"), 1);
|
1367
1406
|
init_logger();
|
1368
1407
|
init_bundler();
|
1408
|
+
PUPPETEER_LAUNCH_OPTIONS = {
|
1409
|
+
headless: true,
|
1410
|
+
args: [
|
1411
|
+
"--no-sandbox",
|
1412
|
+
// Often required in containerized environments
|
1413
|
+
"--disable-setuid-sandbox",
|
1414
|
+
"--disable-dev-shm-usage"
|
1415
|
+
// Recommended for Docker/CI
|
1416
|
+
]
|
1417
|
+
};
|
1418
|
+
DEFAULT_PAGE_TIMEOUT = 3e4;
|
1419
|
+
}
|
1420
|
+
});
|
1421
|
+
|
1422
|
+
// src/core/parser.ts
|
1423
|
+
async function parseHTML(entryFilePath, logger) {
|
1424
|
+
logger?.debug(`Parsing HTML file: ${entryFilePath}`);
|
1425
|
+
let htmlContent;
|
1426
|
+
try {
|
1427
|
+
htmlContent = await (0, import_promises2.readFile)(entryFilePath, "utf-8");
|
1428
|
+
logger?.debug(`Successfully read HTML file (${Buffer.byteLength(htmlContent)} bytes).`);
|
1429
|
+
} catch (err) {
|
1430
|
+
logger?.error(`Failed to read HTML file "${entryFilePath}": ${err.message}`);
|
1431
|
+
throw new Error(`Could not read input HTML file: ${entryFilePath}`, { cause: err });
|
1432
|
+
}
|
1433
|
+
const $ = cheerio2.load(htmlContent);
|
1434
|
+
const assets = [];
|
1435
|
+
const addedUrls = /* @__PURE__ */ new Set();
|
1436
|
+
const addAsset = (url, forcedType) => {
|
1437
|
+
if (!url || url.trim() === "" || url.startsWith("data:")) {
|
1438
|
+
return;
|
1439
|
+
}
|
1440
|
+
if (!addedUrls.has(url)) {
|
1441
|
+
addedUrls.add(url);
|
1442
|
+
const mimeInfo = guessMimeType(url);
|
1443
|
+
const type = forcedType ?? mimeInfo.assetType;
|
1444
|
+
assets.push({ type, url });
|
1445
|
+
logger?.debug(`Discovered asset: Type='${type}', URL='${url}'`);
|
1446
|
+
} else {
|
1447
|
+
logger?.debug(`Skipping duplicate asset URL: ${url}`);
|
1448
|
+
}
|
1449
|
+
};
|
1450
|
+
logger?.debug("Extracting assets from HTML tags...");
|
1451
|
+
$('link[rel="stylesheet"][href]').each((_, el) => {
|
1452
|
+
addAsset($(el).attr("href"), "css");
|
1453
|
+
});
|
1454
|
+
$("script[src]").each((_, el) => {
|
1455
|
+
addAsset($(el).attr("src"), "js");
|
1456
|
+
});
|
1457
|
+
$("img[src]").each((_, el) => addAsset($(el).attr("src"), "image"));
|
1458
|
+
$('input[type="image"][src]').each((_, el) => addAsset($(el).attr("src"), "image"));
|
1459
|
+
$("img[srcset], picture source[srcset]").each((_, el) => {
|
1460
|
+
const srcset = $(el).attr("srcset");
|
1461
|
+
srcset?.split(",").forEach((entry) => {
|
1462
|
+
const [url] = entry.trim().split(/\s+/);
|
1463
|
+
addAsset(url, "image");
|
1464
|
+
});
|
1465
|
+
});
|
1466
|
+
$("video[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
1467
|
+
$("video[poster]").each((_, el) => addAsset($(el).attr("poster"), "image"));
|
1468
|
+
$("audio[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
1469
|
+
$("video > source[src]").each((_, el) => addAsset($(el).attr("src"), "video"));
|
1470
|
+
$("audio > source[src]").each((_, el) => addAsset($(el).attr("src"), "audio"));
|
1471
|
+
$("link[href]").filter((_, el) => {
|
1472
|
+
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
1473
|
+
return ["icon", "shortcut icon", "apple-touch-icon", "manifest"].includes(rel);
|
1474
|
+
}).each((_, el) => {
|
1475
|
+
const rel = $(el).attr("rel")?.toLowerCase() ?? "";
|
1476
|
+
const isIcon = ["icon", "shortcut icon", "apple-touch-icon"].includes(rel);
|
1477
|
+
addAsset($(el).attr("href"), isIcon ? "image" : void 0);
|
1478
|
+
});
|
1479
|
+
$('link[rel="preload"][as="font"][href]').each((_, el) => {
|
1480
|
+
addAsset($(el).attr("href"), "font");
|
1481
|
+
});
|
1482
|
+
logger?.info(`HTML parsing complete. Discovered ${assets.length} unique asset links.`);
|
1483
|
+
return { htmlContent, assets };
|
1484
|
+
}
|
1485
|
+
var import_promises2, cheerio2;
|
1486
|
+
var init_parser = __esm({
|
1487
|
+
"src/core/parser.ts"() {
|
1488
|
+
"use strict";
|
1489
|
+
import_promises2 = require("fs/promises");
|
1490
|
+
cheerio2 = __toESM(require("cheerio"), 1);
|
1491
|
+
init_mime();
|
1369
1492
|
}
|
1370
1493
|
});
|
1371
1494
|
|
@@ -1455,117 +1578,91 @@ var init_meta = __esm({
|
|
1455
1578
|
});
|
1456
1579
|
|
1457
1580
|
// src/index.ts
|
1581
|
+
async function pack(input, options = {}) {
|
1582
|
+
const logger = options.loggerInstance || new Logger(options.logLevel);
|
1583
|
+
const isHttp = /^https?:\/\//i.test(input);
|
1584
|
+
if (!isHttp && /:\/\//.test(input) && !input.startsWith("file://")) {
|
1585
|
+
const errorMsg = `Unsupported protocol or input type: ${input}`;
|
1586
|
+
logger.error(errorMsg);
|
1587
|
+
throw new Error(errorMsg);
|
1588
|
+
}
|
1589
|
+
const isRemote = /^https?:\/\//i.test(input);
|
1590
|
+
const recursive = options.recursive === true || typeof options.recursive === "number";
|
1591
|
+
if (isRemote && recursive) {
|
1592
|
+
const depth = typeof options.recursive === "number" ? options.recursive : 1;
|
1593
|
+
logger.info(`Starting recursive fetch for ${input} up to depth ${depth}`);
|
1594
|
+
return generateRecursivePortableHTML(input, depth, options, logger);
|
1595
|
+
}
|
1596
|
+
logger.info(`Starting single page processing for: ${input}`);
|
1597
|
+
return generatePortableHTML(input, options, logger);
|
1598
|
+
}
|
1458
1599
|
async function generatePortableHTML(input, options = {}, loggerInstance) {
|
1459
1600
|
const logger = loggerInstance || new Logger(options.logLevel);
|
1460
|
-
logger.info(`Generating portable HTML for: ${input}`);
|
1461
1601
|
const timer = new BuildTimer(input);
|
1462
|
-
|
1463
|
-
|
1464
|
-
logger.info(`Input is a remote URL. Fetching page content directly...`);
|
1602
|
+
if (/^https?:\/\//i.test(input)) {
|
1603
|
+
logger.info(`Workspaceing remote page: ${input}`);
|
1465
1604
|
try {
|
1466
|
-
const result = await
|
1467
|
-
|
1468
|
-
|
1605
|
+
const result = await fetchAndPackWebPage(input, logger);
|
1606
|
+
const metadata = timer.finish(result.html, result.metadata);
|
1607
|
+
logger.info(`Finished fetching and packing remote page: ${input}`);
|
1608
|
+
return { html: result.html, metadata };
|
1469
1609
|
} catch (error) {
|
1470
|
-
logger.error(`
|
1610
|
+
logger.error(`Error fetching remote page ${input}: ${error.message}`);
|
1471
1611
|
throw error;
|
1472
1612
|
}
|
1473
1613
|
}
|
1474
|
-
logger.info(`
|
1475
|
-
const basePath = options.baseUrl || input;
|
1476
|
-
logger.debug(`Using base path for asset resolution: ${basePath}`);
|
1614
|
+
logger.info(`Processing local file: ${input}`);
|
1477
1615
|
try {
|
1616
|
+
const baseUrl = options.baseUrl || input;
|
1478
1617
|
const parsed = await parseHTML(input, logger);
|
1479
|
-
const enriched = await extractAssets(parsed, options.embedAssets ?? true,
|
1618
|
+
const enriched = await extractAssets(parsed, options.embedAssets ?? true, baseUrl, logger);
|
1480
1619
|
const minified = await minifyAssets(enriched, options, logger);
|
1481
1620
|
const finalHtml = packHTML(minified, logger);
|
1482
1621
|
const metadata = timer.finish(finalHtml, {
|
1483
1622
|
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
1623
|
});
|
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
|
-
}
|
1624
|
+
logger.info(`Finished processing local file: ${input}`);
|
1491
1625
|
return { html: finalHtml, metadata };
|
1492
1626
|
} catch (error) {
|
1493
|
-
logger.error(`Error
|
1627
|
+
logger.error(`Error processing local file ${input}: ${error.message}`);
|
1494
1628
|
throw error;
|
1495
1629
|
}
|
1496
1630
|
}
|
1497
1631
|
async function generateRecursivePortableHTML(url, depth = 1, options = {}, loggerInstance) {
|
1498
1632
|
const logger = loggerInstance || new Logger(options.logLevel);
|
1499
|
-
logger.info(`Generating recursive portable HTML for: ${url}, Max Depth: ${depth}`);
|
1500
1633
|
const timer = new BuildTimer(url);
|
1501
1634
|
if (!/^https?:\/\//i.test(url)) {
|
1502
|
-
const
|
1503
|
-
logger.error(
|
1504
|
-
throw new Error(
|
1635
|
+
const errorMsg = `Invalid URL for recursive bundling. Must start with http:// or https://. Received: ${url}`;
|
1636
|
+
logger.error(errorMsg);
|
1637
|
+
throw new Error(errorMsg);
|
1505
1638
|
}
|
1506
|
-
|
1639
|
+
logger.info(`Starting recursive bundle for ${url} up to depth ${depth}`);
|
1507
1640
|
try {
|
1508
|
-
const { html, pages } = await recursivelyBundleSite(url,
|
1509
|
-
logger.info(`Recursive crawl complete. Discovered and bundled ${pages} pages.`);
|
1641
|
+
const { html, pages } = await recursivelyBundleSite(url, "output.html", depth, logger);
|
1510
1642
|
timer.setPageCount(pages);
|
1511
1643
|
const metadata = timer.finish(html, {
|
1512
1644
|
assetCount: 0,
|
1513
|
-
// NOTE: Asset count across multiple pages is not currently aggregated.
|
1514
1645
|
pagesBundled: pages
|
1515
|
-
// TODO: Potentially collect errors from the core function if it returns them
|
1516
1646
|
});
|
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
|
-
}
|
1647
|
+
logger.info(`Finished recursive bundle for ${url}. Bundled ${pages} pages.`);
|
1521
1648
|
return { html, metadata };
|
1522
1649
|
} 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}`);
|
1650
|
+
logger.error(`Error during recursive bundle for ${url}: ${error.message}`);
|
1554
1651
|
throw error;
|
1555
1652
|
}
|
1556
1653
|
}
|
1557
1654
|
var init_src = __esm({
|
1558
1655
|
"src/index.ts"() {
|
1559
1656
|
"use strict";
|
1657
|
+
init_web_fetcher();
|
1560
1658
|
init_parser();
|
1561
1659
|
init_extractor();
|
1562
1660
|
init_minifier();
|
1563
1661
|
init_packer();
|
1564
|
-
init_web_fetcher();
|
1565
1662
|
init_bundler();
|
1663
|
+
init_logger();
|
1566
1664
|
init_meta();
|
1567
1665
|
init_logger();
|
1568
|
-
init_types();
|
1569
1666
|
}
|
1570
1667
|
});
|
1571
1668
|
|
@@ -1575,20 +1672,15 @@ __export(cli_exports, {
|
|
1575
1672
|
main: () => main,
|
1576
1673
|
runCli: () => runCli
|
1577
1674
|
});
|
1578
|
-
import fs3 from "fs";
|
1579
|
-
import path3 from "path";
|
1580
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
1581
1675
|
function getPackageJson() {
|
1582
1676
|
try {
|
1583
|
-
const
|
1584
|
-
const
|
1585
|
-
|
1586
|
-
|
1587
|
-
|
1588
|
-
}
|
1589
|
-
} catch (_) {
|
1677
|
+
const searchPath = typeof __dirname !== "undefined" ? import_path3.default.join(__dirname, "..", "..") : process.cwd();
|
1678
|
+
const pkgJsonPath = require.resolve("portapack/package.json", { paths: [searchPath] });
|
1679
|
+
return require(pkgJsonPath);
|
1680
|
+
} catch (err) {
|
1681
|
+
console.error("Warning: Could not dynamically load package.json for version.", err);
|
1682
|
+
return { version: "0.0.0-unknown" };
|
1590
1683
|
}
|
1591
|
-
return { version: "0.1.0" };
|
1592
1684
|
}
|
1593
1685
|
async function runCli(argv = process.argv) {
|
1594
1686
|
let stdout = "";
|
@@ -1597,6 +1689,11 @@ async function runCli(argv = process.argv) {
|
|
1597
1689
|
const originalLog = console.log;
|
1598
1690
|
const originalErr = console.error;
|
1599
1691
|
const originalWarn = console.warn;
|
1692
|
+
const restoreConsole = () => {
|
1693
|
+
console.log = originalLog;
|
1694
|
+
console.error = originalErr;
|
1695
|
+
console.warn = originalWarn;
|
1696
|
+
};
|
1600
1697
|
console.log = (...args) => {
|
1601
1698
|
stdout += args.join(" ") + "\n";
|
1602
1699
|
};
|
@@ -1606,40 +1703,38 @@ async function runCli(argv = process.argv) {
|
|
1606
1703
|
console.warn = (...args) => {
|
1607
1704
|
stderr += args.join(" ") + "\n";
|
1608
1705
|
};
|
1609
|
-
let
|
1706
|
+
let cliOptions;
|
1610
1707
|
try {
|
1611
|
-
|
1612
|
-
const version = getPackageJson().version || "0.
|
1613
|
-
if (
|
1708
|
+
cliOptions = parseOptions(argv);
|
1709
|
+
const version = getPackageJson().version || "0.0.0";
|
1710
|
+
if (cliOptions.verbose) {
|
1614
1711
|
console.log(`\u{1F4E6} PortaPack v${version}`);
|
1615
1712
|
}
|
1616
|
-
if (!
|
1713
|
+
if (!cliOptions.input) {
|
1617
1714
|
console.error("\u274C Missing input file or URL");
|
1618
|
-
|
1619
|
-
console.error = originalErr;
|
1620
|
-
console.warn = originalWarn;
|
1715
|
+
restoreConsole();
|
1621
1716
|
return { stdout, stderr, exitCode: 1 };
|
1622
1717
|
}
|
1623
|
-
const
|
1624
|
-
|
1625
|
-
|
1718
|
+
const inputBasename = import_path3.default.basename(cliOptions.input);
|
1719
|
+
const outputDefaultBase = inputBasename.includes(".") ? inputBasename.substring(0, inputBasename.lastIndexOf(".")) : inputBasename;
|
1720
|
+
const outputPath = cliOptions.output ?? `${outputDefaultBase || "output"}.packed.html`;
|
1721
|
+
if (cliOptions.verbose) {
|
1722
|
+
console.log(`\u{1F4E5} Input: ${cliOptions.input}`);
|
1626
1723
|
console.log(`\u{1F4E4} Output: ${outputPath}`);
|
1627
|
-
console.log(`
|
1628
|
-
console.log(`
|
1629
|
-
console.log(`
|
1630
|
-
console.log(`
|
1631
|
-
console.log(`
|
1632
|
-
console.log(`
|
1724
|
+
console.log(` Recursive: ${cliOptions.recursive ?? false}`);
|
1725
|
+
console.log(` Embed Assets: ${cliOptions.embedAssets}`);
|
1726
|
+
console.log(` Minify HTML: ${cliOptions.minifyHtml}`);
|
1727
|
+
console.log(` Minify CSS: ${cliOptions.minifyCss}`);
|
1728
|
+
console.log(` Minify JS: ${cliOptions.minifyJs}`);
|
1729
|
+
console.log(` Log Level: ${cliOptions.logLevel}`);
|
1633
1730
|
}
|
1634
|
-
if (
|
1731
|
+
if (cliOptions.dryRun) {
|
1635
1732
|
console.log("\u{1F4A1} Dry run mode \u2014 no output will be written");
|
1636
|
-
|
1637
|
-
console.error = originalErr;
|
1638
|
-
console.warn = originalWarn;
|
1733
|
+
restoreConsole();
|
1639
1734
|
return { stdout, stderr, exitCode: 0 };
|
1640
1735
|
}
|
1641
|
-
const result =
|
1642
|
-
|
1736
|
+
const result = await pack(cliOptions.input, cliOptions);
|
1737
|
+
import_fs.default.writeFileSync(outputPath, result.html, "utf-8");
|
1643
1738
|
const meta = result.metadata;
|
1644
1739
|
console.log(`\u2705 Packed: ${meta.input} \u2192 ${outputPath}`);
|
1645
1740
|
console.log(`\u{1F4E6} Size: ${(meta.outputSize / 1024).toFixed(2)} KB`);
|
@@ -1648,7 +1743,7 @@ async function runCli(argv = process.argv) {
|
|
1648
1743
|
if (meta.pagesBundled && meta.pagesBundled > 0) {
|
1649
1744
|
console.log(`\u{1F9E9} Pages: ${meta.pagesBundled}`);
|
1650
1745
|
}
|
1651
|
-
if (meta.errors
|
1746
|
+
if (meta.errors?.length) {
|
1652
1747
|
console.warn(`
|
1653
1748
|
\u26A0\uFE0F ${meta.errors.length} warning(s):`);
|
1654
1749
|
for (const err of meta.errors) {
|
@@ -1658,37 +1753,49 @@ async function runCli(argv = process.argv) {
|
|
1658
1753
|
} catch (err) {
|
1659
1754
|
console.error(`
|
1660
1755
|
\u{1F4A5} Error: ${err?.message || "Unknown failure"}`);
|
1661
|
-
if (err?.stack &&
|
1756
|
+
if (err?.stack && cliOptions?.verbose) {
|
1662
1757
|
console.error(err.stack);
|
1663
1758
|
}
|
1664
1759
|
exitCode = 1;
|
1665
1760
|
} finally {
|
1666
|
-
|
1667
|
-
console.error = originalErr;
|
1668
|
-
console.warn = originalWarn;
|
1761
|
+
restoreConsole();
|
1669
1762
|
}
|
1670
1763
|
return { stdout, stderr, exitCode };
|
1671
1764
|
}
|
1672
|
-
var main;
|
1765
|
+
var import_fs, import_path3, main;
|
1673
1766
|
var init_cli = __esm({
|
1674
1767
|
"src/cli/cli.ts"() {
|
1675
1768
|
"use strict";
|
1769
|
+
import_fs = __toESM(require("fs"), 1);
|
1770
|
+
import_path3 = __toESM(require("path"), 1);
|
1676
1771
|
init_options();
|
1677
1772
|
init_src();
|
1678
|
-
init_types();
|
1679
1773
|
main = runCli;
|
1680
1774
|
}
|
1681
1775
|
});
|
1682
1776
|
|
1683
1777
|
// src/cli/cli-entry.ts
|
1778
|
+
var cli_entry_exports = {};
|
1779
|
+
__export(cli_entry_exports, {
|
1780
|
+
startCLI: () => startCLI
|
1781
|
+
});
|
1782
|
+
module.exports = __toCommonJS(cli_entry_exports);
|
1684
1783
|
var startCLI = async () => {
|
1685
1784
|
const { main: main2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
1686
1785
|
return await main2(process.argv);
|
1687
1786
|
};
|
1688
|
-
if (
|
1689
|
-
startCLI().then(({ exitCode }) =>
|
1787
|
+
if (require.main === module) {
|
1788
|
+
startCLI().then(({ stdout, stderr, exitCode }) => {
|
1789
|
+
if (stdout) process.stdout.write(stdout);
|
1790
|
+
if (stderr) process.stderr.write(stderr);
|
1791
|
+
process.exit(Number(exitCode));
|
1792
|
+
}).catch((err) => {
|
1793
|
+
console.error("\u{1F4A5} Unhandled CLI error:", err);
|
1794
|
+
process.exit(1);
|
1795
|
+
});
|
1690
1796
|
}
|
1691
|
-
export
|
1797
|
+
// Annotate the CommonJS export names for ESM import in node:
|
1798
|
+
0 && (module.exports = {
|
1692
1799
|
startCLI
|
1693
|
-
};
|
1694
|
-
//# sourceMappingURL=cli-entry.
|
1800
|
+
});
|
1801
|
+
//# sourceMappingURL=cli-entry.cjs.map
|