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.
@@ -1,28 +1,28 @@
1
1
  /**
2
2
  * @file cli-entry.ts
3
3
  * @description
4
- * Node.js entry point for PortaPack CLI (compatible with ESM).
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
- // If executed directly from the command line, run and exit.
24
- if (import.meta.url === `file://${process.argv[1]}`) {
25
- startCLI().then(({ exitCode }) => process.exit(Number(exitCode))); // Cast exitCode to Number
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 CLI args, executing the HTML bundler,
5
- * writing output to disk, logging metadata, and returning structured results.
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'; // Use default import if mocking default below
8
+ import fs from 'fs';
9
9
  import path from 'path';
10
- import { fileURLToPath } from 'url';
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.js';
13
- import { generatePortableHTML, generateRecursivePortableHTML } from '../index';
14
- import type { CLIResult } from '../types';
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 metadata.
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
- try {
23
- const __filename = fileURLToPath(import.meta.url);
24
- const __dirname = path.dirname(__filename);
25
- const pkgPath = path.resolve(__dirname, '../../package.json');
26
-
27
- // Use fs directly, assuming mock works or it's okay in non-test env
28
- if (fs.existsSync(pkgPath)) {
29
- return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
30
- }
31
- } catch (_) {
32
- // Ignore and fallback
33
- }
34
- return { version: '0.1.0' }; // Default fallback version
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
- * Entry function for running the CLI.
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
- let stdout = '';
42
- let stderr = '';
43
- let exitCode = 0;
44
-
45
- // Capture console output for result object
46
- const originalLog = console.log;
47
- const originalErr = console.error;
48
- const originalWarn = console.warn;
49
- console.log = (...args) => { stdout += args.join(' ') + '\n'; };
50
- console.error = (...args) => { stderr += args.join(' ') + '\n'; };
51
- console.warn = (...args) => { stderr += args.join(' ') + '\n'; }; // Capture warnings in stderr too
52
-
53
- let opts: ReturnType<typeof parseOptions> | undefined;
54
- try {
55
- opts = parseOptions(argv);
56
- const version = getPackageJson().version || '0.1.0';
57
-
58
- if (opts.verbose) {
59
- console.log(`šŸ“¦ PortaPack v${version}`);
60
- }
61
-
62
- if (!opts.input) {
63
- console.error('āŒ Missing input file or URL');
64
- // Restore console before returning
65
- console.log = originalLog; console.error = originalErr; console.warn = originalWarn;
66
- return { stdout, stderr, exitCode: 1 };
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
- return { stdout, stderr, exitCode };
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
- // Optional: Define main export if this file is intended to be run directly
134
- export const main = runCli;
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
- // Example direct execution (usually handled by bin entry in package.json)
137
- // if (require.main === module) {
138
- // runCli();
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;