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
package/src/cli/cli-entry.ts
CHANGED
@@ -1,28 +1,28 @@
|
|
1
1
|
/**
|
2
2
|
* @file cli-entry.ts
|
3
3
|
* @description
|
4
|
-
* Node.js
|
5
|
-
*
|
6
|
-
* Supports:
|
7
|
-
* - Direct execution: `node cli-entry.js`
|
8
|
-
* - Programmatic import for testing: `import { startCLI } from './cli-entry'`
|
4
|
+
* Safe Node.js CLI entrypoint for PortaPack, compatible with both ESM and CommonJS output.
|
9
5
|
*/
|
10
6
|
|
11
7
|
import type { CLIResult } from '../types';
|
12
8
|
|
13
|
-
/**
|
14
|
-
* Starts the CLI by importing and invoking the main CLI logic.
|
15
|
-
*
|
16
|
-
* @returns {Promise<CLIResult>} - Exit code and any captured output
|
17
|
-
*/
|
18
9
|
const startCLI = async (): Promise<CLIResult> => {
|
19
|
-
const { main } = await import('./cli.js');
|
10
|
+
const { main } = await import('./cli.js'); // This stays ESM-friendly
|
20
11
|
return await main(process.argv);
|
21
12
|
};
|
22
13
|
|
23
|
-
//
|
24
|
-
if (
|
25
|
-
startCLI()
|
14
|
+
// Safe: if this file is the entry point, run the CLI
|
15
|
+
if (require.main === module) {
|
16
|
+
startCLI()
|
17
|
+
.then(({ stdout, stderr, exitCode }) => {
|
18
|
+
if (stdout) process.stdout.write(stdout);
|
19
|
+
if (stderr) process.stderr.write(stderr);
|
20
|
+
process.exit(Number(exitCode));
|
21
|
+
})
|
22
|
+
.catch((err) => {
|
23
|
+
console.error('š„ Unhandled CLI error:', err);
|
24
|
+
process.exit(1);
|
25
|
+
});
|
26
26
|
}
|
27
27
|
|
28
|
-
export { startCLI };
|
28
|
+
export { startCLI };
|
package/src/cli/cli.ts
CHANGED
@@ -1,139 +1,150 @@
|
|
1
1
|
/**
|
2
2
|
* @file cli.ts
|
3
3
|
* @description
|
4
|
-
* Main CLI runner for PortaPack. Handles parsing
|
5
|
-
*
|
4
|
+
* Main CLI runner for PortaPack. Handles argument parsing, calls the bundler via `pack()`,
|
5
|
+
* writes output to disk (unless dry-run), logs build stats, and captures structured output.
|
6
6
|
*/
|
7
7
|
|
8
|
-
import fs from 'fs';
|
8
|
+
import fs from 'fs';
|
9
9
|
import path from 'path';
|
10
|
-
|
10
|
+
// Use standard require for core modules in CJS context if needed
|
11
|
+
// const path = require('path');
|
12
|
+
// const fs = require('fs');
|
11
13
|
|
12
|
-
import { parseOptions } from './options
|
13
|
-
import {
|
14
|
-
|
15
|
-
|
16
|
-
import { LogLevel } from '../types';
|
14
|
+
import { parseOptions } from './options';
|
15
|
+
import { pack } from '../index';
|
16
|
+
// Import CLIOptions correctly
|
17
|
+
import type { CLIResult, BundleOptions, BundleMetadata, CLIOptions } from '../types';
|
17
18
|
|
18
19
|
/**
|
19
|
-
* Dynamically loads package.json
|
20
|
+
* Dynamically loads version info from package.json using CommonJS compatible method.
|
21
|
+
*
|
22
|
+
* @returns {Record<string, any>} Parsed package.json or fallback
|
20
23
|
*/
|
21
24
|
function getPackageJson(): Record<string, any> {
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
25
|
+
try {
|
26
|
+
// FIX: Use require.resolve which works in CommonJS to find the package path
|
27
|
+
// It resolves relative to the location of this file or the node_modules structure
|
28
|
+
// Assumes 'portapack' is the package name defined in package.json
|
29
|
+
// We need the package.json itself, so resolve 'portapack/package.json'
|
30
|
+
// Use __dirname if available in CJS context, otherwise try relative from cwd as fallback
|
31
|
+
const searchPath = typeof __dirname !== 'undefined' ? path.join(__dirname, '..', '..') : process.cwd();
|
32
|
+
const pkgJsonPath = require.resolve('portapack/package.json', { paths: [searchPath] });
|
33
|
+
return require(pkgJsonPath); // Use require directly to load JSON
|
34
|
+
} catch (err) {
|
35
|
+
console.error("Warning: Could not dynamically load package.json for version.", err); // Log error for debugging
|
36
|
+
return { version: '0.0.0-unknown' };
|
37
|
+
}
|
35
38
|
}
|
36
39
|
|
37
40
|
/**
|
38
|
-
*
|
41
|
+
* Entrypoint for CLI execution. Parses args, runs bundler, logs output and errors.
|
42
|
+
*
|
43
|
+
* @param {string[]} [argv=process.argv] - Command-line arguments (default: system args)
|
44
|
+
* @returns {Promise<CLIResult>} - Structured result containing output, error, and exit code
|
39
45
|
*/
|
40
46
|
export async function runCli(argv: string[] = process.argv): Promise<CLIResult> {
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
// Determine output path using nullish coalescing
|
70
|
-
const outputPath = opts.output ?? `${path.basename(opts.input).split('.')[0] || 'output'}.packed.html`;
|
71
|
-
|
72
|
-
if (opts.verbose) {
|
73
|
-
console.log(`š„ Input: ${opts.input}`);
|
74
|
-
console.log(`š¤ Output: ${outputPath}`);
|
75
|
-
// Log other effective options if verbose
|
76
|
-
console.log(` Recursive: ${opts.recursive ?? false}`);
|
77
|
-
console.log(` Embed Assets: ${opts.embedAssets}`);
|
78
|
-
console.log(` Minify HTML: ${opts.minifyHtml}`);
|
79
|
-
console.log(` Minify CSS: ${opts.minifyCss}`);
|
80
|
-
console.log(` Minify JS: ${opts.minifyJs}`);
|
81
|
-
console.log(` Log Level: ${LogLevel[opts.logLevel ?? LogLevel.INFO]}`);
|
82
|
-
}
|
83
|
-
|
84
|
-
if (opts.dryRun) {
|
85
|
-
console.log('š” Dry run mode ā no output will be written');
|
86
|
-
// Restore console before returning
|
87
|
-
console.log = originalLog; console.error = originalErr; console.warn = originalWarn;
|
88
|
-
return { stdout, stderr, exitCode: 0 };
|
89
|
-
}
|
90
|
-
|
91
|
-
// --- FIX: Pass 'opts' object to generate functions ---
|
92
|
-
const result = opts.recursive
|
93
|
-
// Convert boolean recursive flag to depth 1 if needed, otherwise use number
|
94
|
-
? await generateRecursivePortableHTML(opts.input, typeof opts.recursive === 'boolean' ? 1 : opts.recursive, opts)
|
95
|
-
: await generatePortableHTML(opts.input, opts);
|
96
|
-
// ----------------------------------------------------
|
97
|
-
|
98
|
-
// Use fs directly - ensure mock is working in tests
|
99
|
-
fs.writeFileSync(outputPath, result.html, 'utf-8');
|
100
|
-
|
101
|
-
const meta = result.metadata;
|
102
|
-
console.log(`ā
Packed: ${meta.input} ā ${outputPath}`);
|
103
|
-
console.log(`š¦ Size: ${(meta.outputSize / 1024).toFixed(2)} KB`);
|
104
|
-
console.log(`ā±ļø Time: ${meta.buildTimeMs} ms`); // Use alternative emoji
|
105
|
-
console.log(`š¼ļø Assets: ${meta.assetCount}`); // Add asset count log
|
106
|
-
|
107
|
-
if (meta.pagesBundled && meta.pagesBundled > 0) { // Check > 0 for clarity
|
108
|
-
console.log(`š§© Pages: ${meta.pagesBundled}`);
|
109
|
-
}
|
110
|
-
|
111
|
-
if (meta.errors && meta.errors.length > 0) {
|
112
|
-
console.warn(`\nā ļø ${meta.errors.length} warning(s):`); // Add newline for separation
|
113
|
-
for (const err of meta.errors) {
|
114
|
-
console.warn(` - ${err}`);
|
115
|
-
}
|
116
|
-
}
|
117
|
-
} catch (err: any) {
|
118
|
-
console.error(`\nš„ Error: ${err?.message || 'Unknown failure'}`); // Add newline
|
119
|
-
if (err?.stack && opts?.verbose) { // Show stack only if verbose
|
120
|
-
console.error(err.stack);
|
121
|
-
}
|
122
|
-
exitCode = 1;
|
123
|
-
} finally {
|
124
|
-
// Restore original console methods
|
125
|
-
console.log = originalLog;
|
126
|
-
console.error = originalErr;
|
127
|
-
console.warn = originalWarn;
|
47
|
+
let stdout = '';
|
48
|
+
let stderr = '';
|
49
|
+
let exitCode = 0;
|
50
|
+
|
51
|
+
// Capture console output
|
52
|
+
const originalLog = console.log;
|
53
|
+
const originalErr = console.error;
|
54
|
+
const originalWarn = console.warn;
|
55
|
+
|
56
|
+
const restoreConsole = () => {
|
57
|
+
console.log = originalLog;
|
58
|
+
console.error = originalErr;
|
59
|
+
console.warn = originalWarn;
|
60
|
+
};
|
61
|
+
|
62
|
+
console.log = (...args) => { stdout += args.join(' ') + '\n'; };
|
63
|
+
console.error = (...args) => { stderr += args.join(' ') + '\n'; };
|
64
|
+
console.warn = (...args) => { stderr += args.join(' ') + '\n'; };
|
65
|
+
|
66
|
+
// FIX: Use the correct type CLIOptions which includes 'input'
|
67
|
+
let cliOptions: CLIOptions | undefined;
|
68
|
+
try {
|
69
|
+
// Get the fully parsed options object which includes 'input'
|
70
|
+
cliOptions = parseOptions(argv);
|
71
|
+
const version = getPackageJson().version || '0.0.0';
|
72
|
+
|
73
|
+
if (cliOptions.verbose) {
|
74
|
+
console.log(`š¦ PortaPack v${version}`);
|
128
75
|
}
|
129
76
|
|
130
|
-
|
131
|
-
|
77
|
+
// Check for the input property on the correct object
|
78
|
+
if (!cliOptions.input) {
|
79
|
+
console.error('ā Missing input file or URL');
|
80
|
+
restoreConsole();
|
81
|
+
return { stdout, stderr, exitCode: 1 };
|
82
|
+
}
|
83
|
+
|
84
|
+
// Use path.basename and handle potential extension removal carefully
|
85
|
+
const inputBasename = path.basename(cliOptions.input);
|
86
|
+
const outputDefaultBase = inputBasename.includes('.') ? inputBasename.substring(0, inputBasename.lastIndexOf('.')) : inputBasename;
|
87
|
+
// Use the parsed output option or generate default
|
88
|
+
const outputPath = cliOptions.output ?? `${outputDefaultBase || 'output'}.packed.html`;
|
89
|
+
|
90
|
+
if (cliOptions.verbose) {
|
91
|
+
console.log(`š„ Input: ${cliOptions.input}`); // Access input correctly
|
92
|
+
console.log(`š¤ Output: ${outputPath}`);
|
93
|
+
// Display other resolved options
|
94
|
+
console.log(` Recursive: ${cliOptions.recursive ?? false}`);
|
95
|
+
console.log(` Embed Assets: ${cliOptions.embedAssets}`);
|
96
|
+
console.log(` Minify HTML: ${cliOptions.minifyHtml}`);
|
97
|
+
console.log(` Minify CSS: ${cliOptions.minifyCss}`);
|
98
|
+
console.log(` Minify JS: ${cliOptions.minifyJs}`);
|
99
|
+
console.log(` Log Level: ${cliOptions.logLevel}`);
|
100
|
+
}
|
101
|
+
|
102
|
+
if (cliOptions.dryRun) {
|
103
|
+
console.log('š” Dry run mode ā no output will be written');
|
104
|
+
restoreConsole();
|
105
|
+
return { stdout, stderr, exitCode: 0 };
|
106
|
+
}
|
107
|
+
|
108
|
+
// FIX: Call pack with input as the first argument, and the rest of the options as the second.
|
109
|
+
// The cliOptions object should be compatible with PackOptions expected by pack.
|
110
|
+
const result = await pack(cliOptions.input, cliOptions);
|
111
|
+
|
112
|
+
// Use standard fs sync version as used before
|
113
|
+
fs.writeFileSync(outputPath, result.html, 'utf-8');
|
132
114
|
|
133
|
-
|
134
|
-
|
115
|
+
const meta = result.metadata;
|
116
|
+
// Log results to captured stdout
|
117
|
+
console.log(`ā
Packed: ${meta.input} ā ${outputPath}`); // meta.input should be correct from pack's result
|
118
|
+
console.log(`š¦ Size: ${(meta.outputSize / 1024).toFixed(2)} KB`);
|
119
|
+
console.log(`ā±ļø Time: ${meta.buildTimeMs} ms`);
|
120
|
+
console.log(`š¼ļø Assets: ${meta.assetCount}`);
|
135
121
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
122
|
+
if (meta.pagesBundled && meta.pagesBundled > 0) {
|
123
|
+
console.log(`š§© Pages: ${meta.pagesBundled}`);
|
124
|
+
}
|
125
|
+
|
126
|
+
if (meta.errors?.length) {
|
127
|
+
console.warn(`\nā ļø ${meta.errors.length} warning(s):`);
|
128
|
+
for (const err of meta.errors) {
|
129
|
+
console.warn(` - ${err}`);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
} catch (err: any) {
|
134
|
+
console.error(`\nš„ Error: ${err?.message || 'Unknown failure'}`);
|
135
|
+
// Check verbose flag on the correct variable
|
136
|
+
if (err?.stack && cliOptions?.verbose) {
|
137
|
+
console.error(err.stack);
|
138
|
+
}
|
139
|
+
exitCode = 1;
|
140
|
+
} finally {
|
141
|
+
restoreConsole();
|
142
|
+
}
|
143
|
+
|
144
|
+
return { stdout, stderr, exitCode };
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* Default exportable main runner for CLI invocation.
|
149
|
+
*/
|
150
|
+
export const main = runCli;
|