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
package/jest.config.ts
CHANGED
@@ -5,7 +5,8 @@ const config: Config = {
|
|
5
5
|
/**
|
6
6
|
* Use ts-jest with ESM support.
|
7
7
|
*/
|
8
|
-
preset: 'ts-jest/presets/default-esm',
|
8
|
+
// preset: 'ts-jest/presets/default-esm',
|
9
|
+
preset: 'ts-jest',
|
9
10
|
|
10
11
|
/**
|
11
12
|
* Node test environment
|
@@ -20,16 +21,25 @@ const config: Config = {
|
|
20
21
|
/**
|
21
22
|
* Tell ts-jest to use ESM transformation
|
22
23
|
*/
|
24
|
+
// transform: {
|
25
|
+
// '^.+\\.tsx?$': [
|
26
|
+
// 'ts-jest',
|
27
|
+
// {
|
28
|
+
// useESM: true,
|
29
|
+
// tsconfig: './tsconfig.jest.json'
|
30
|
+
// }
|
31
|
+
// ]
|
32
|
+
// },
|
33
|
+
|
23
34
|
transform: {
|
24
35
|
'^.+\\.tsx?$': [
|
25
36
|
'ts-jest',
|
26
37
|
{
|
27
|
-
useESM:
|
28
|
-
tsconfig: './tsconfig.jest.json'
|
38
|
+
// useESM: false, // Explicitly false or remove this line entirely
|
39
|
+
tsconfig: './tsconfig.jest.json' // Ensure this tsconfig targets CommonJS
|
29
40
|
}
|
30
41
|
]
|
31
42
|
},
|
32
|
-
|
33
43
|
/**
|
34
44
|
* Treat `.ts` files as ESM modules.
|
35
45
|
*/
|
@@ -68,10 +78,10 @@ const config: Config = {
|
|
68
78
|
*/
|
69
79
|
coverageThreshold: {
|
70
80
|
global: {
|
71
|
-
branches:
|
72
|
-
functions:
|
73
|
-
lines:
|
74
|
-
statements:
|
81
|
+
branches: 70,
|
82
|
+
functions: 70,
|
83
|
+
lines: 70,
|
84
|
+
statements: 70,
|
75
85
|
},
|
76
86
|
},
|
77
87
|
|
package/jest.setup.cjs
CHANGED
@@ -1,211 +1,131 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
* @description Jest global setup script executed before test suites run (per file).
|
4
|
-
* Responsible for creating temporary directories for test outputs and fixtures,
|
5
|
-
* setting up mock data (like the sample project), and cleaning up afterwards.
|
6
|
-
* Uses CommonJS syntax as Jest setup files often run in a CJS context.
|
7
|
-
*/
|
8
|
-
|
9
|
-
// Node.js core modules required for setup
|
10
|
-
const fs = require('fs/promises'); // Using promises API for async operations
|
1
|
+
// jest.setup.cjs
|
2
|
+
const fs = require('fs/promises');
|
11
3
|
const path = require('path');
|
12
4
|
const crypto = require('crypto');
|
13
5
|
const os = require('os');
|
6
|
+
// const jest = require('@jest/globals'); // REMOVE THIS LINE
|
14
7
|
|
15
|
-
// Generate a unique ID for each test execution session.
|
16
|
-
// This helps isolate test runs if running concurrently or prevents conflicts
|
17
|
-
// if cleanup fails on a previous run.
|
18
8
|
const TEST_RUN_ID = crypto.randomBytes(4).toString('hex');
|
19
|
-
|
20
|
-
// Helper function for resolving paths relative to this setup file's directory
|
21
|
-
// (usually the project root or a specific test config directory)
|
22
9
|
const resolve = (...args) => path.resolve(__dirname, ...args);
|
23
10
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
* Ignores 'EEXIST' error if the directory is already present.
|
28
|
-
* @param {string} dir - The absolute path of the directory to ensure.
|
29
|
-
* @returns {Promise<void>}
|
30
|
-
* @throws {Error} Throws errors other than 'EEXIST' during directory creation.
|
31
|
-
*/
|
11
|
+
// Use the *original* console.log temporarily for setup debugging
|
12
|
+
const originalSetupLog = console.log;
|
13
|
+
|
32
14
|
const ensureDir = async (dir) => {
|
33
15
|
try {
|
34
16
|
await fs.mkdir(dir, { recursive: true });
|
35
17
|
} catch (e) {
|
36
|
-
// If the error is simply that the directory already exists, ignore it.
|
37
18
|
if (e.code !== 'EEXIST') {
|
38
|
-
|
39
|
-
throw e;
|
19
|
+
originalSetupLog(`[SETUP-ERROR] Failed to ensure directory ${dir}:`, e);
|
20
|
+
throw e;
|
40
21
|
}
|
41
22
|
}
|
42
23
|
};
|
43
24
|
|
44
|
-
// Define the base temporary directory using the OS's temp location and the unique run ID.
|
45
|
-
// Using os.tmpdir() is generally safer regarding permissions and OS conventions.
|
46
25
|
const tempDir = path.join(os.tmpdir(), 'portapack-tests', TEST_RUN_ID);
|
47
|
-
|
48
|
-
|
49
|
-
* Object containing base paths for various test-related directories.
|
50
|
-
* These are constructed relative to the main temporary directory.
|
51
|
-
*/
|
26
|
+
// *** Make sure baseDirs is defined ***
|
27
|
+
// (Assuming it was defined correctly before the placeholder comment)
|
52
28
|
const baseDirs = {
|
53
|
-
root: resolve(), // Project root
|
54
|
-
output: path.join(tempDir, 'test-output'), // General output
|
55
|
-
fixtures: path.join(tempDir, '__fixtures__'), // Base for
|
56
|
-
fixturesOutput: path.join(tempDir, '__fixtures__/output'), //
|
57
|
-
sampleProject: path.join(tempDir, 'sample-project') //
|
29
|
+
root: resolve(), // Project root
|
30
|
+
output: path.join(tempDir, 'test-output'), // General output
|
31
|
+
fixtures: path.join(tempDir, '__fixtures__'), // Base for fixtures
|
32
|
+
fixturesOutput: path.join(tempDir, '__fixtures__/output'), // Fixture output
|
33
|
+
sampleProject: path.join(tempDir, 'sample-project') // Mock project dir
|
58
34
|
};
|
35
|
+
// **********************************
|
59
36
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
*/
|
37
|
+
|
38
|
+
// --- Use Jest's GLOBAL beforeAll/afterAll/jest ---
|
39
|
+
// These are available globally in setup files
|
64
40
|
beforeAll(async () => {
|
65
|
-
|
41
|
+
originalSetupLog(`[SETUP] Starting setup in: ${tempDir}`);
|
66
42
|
|
67
|
-
|
68
|
-
//
|
43
|
+
originalSetupLog('[SETUP] Ensuring baseDirs...');
|
44
|
+
// Make sure baseDirs is defined before using it
|
45
|
+
if (!baseDirs || !baseDirs.output || !baseDirs.fixturesOutput) {
|
46
|
+
throw new Error("baseDirs is not properly defined!");
|
47
|
+
}
|
69
48
|
await Promise.all([
|
70
49
|
ensureDir(baseDirs.output),
|
71
50
|
ensureDir(baseDirs.fixturesOutput)
|
72
51
|
]);
|
52
|
+
originalSetupLog('[SETUP] BaseDirs ensured.');
|
73
53
|
|
74
|
-
|
54
|
+
originalSetupLog('[SETUP] Creating runDirs...');
|
75
55
|
const runDirs = {
|
76
56
|
uniqueOutput: path.join(baseDirs.output, TEST_RUN_ID),
|
77
57
|
uniqueFixturesOutput: path.join(baseDirs.fixturesOutput, TEST_RUN_ID)
|
78
58
|
};
|
79
|
-
|
80
|
-
// Create the run-specific directories.
|
81
59
|
await Promise.all([
|
82
60
|
fs.mkdir(runDirs.uniqueOutput, { recursive: true }),
|
83
61
|
fs.mkdir(runDirs.uniqueFixturesOutput, { recursive: true })
|
84
62
|
]);
|
63
|
+
originalSetupLog('[SETUP] RunDirs created.');
|
85
64
|
|
86
|
-
|
87
|
-
|
88
|
-
global.
|
89
|
-
|
90
|
-
|
91
|
-
};
|
92
|
-
|
93
|
-
// Helper function for tests to get a path within the unique test output directory.
|
94
|
-
global.getTestFilePath = (relPath) =>
|
95
|
-
path.join(global.__TEST_DIRECTORIES__.uniqueOutput, relPath);
|
96
|
-
|
97
|
-
// Helper function for tests to get a path within the unique fixture output directory.
|
98
|
-
global.getTestFixturePath = (relPath) =>
|
99
|
-
path.join(global.__TEST_DIRECTORIES__.uniqueFixturesOutput, relPath);
|
65
|
+
originalSetupLog('[SETUP] Setting globals...');
|
66
|
+
global.__TEST_DIRECTORIES__ = { ...baseDirs, ...runDirs };
|
67
|
+
global.getTestFilePath = (relPath) => path.join(global.__TEST_DIRECTORIES__.uniqueOutput, relPath);
|
68
|
+
global.getTestFixturePath = (relPath) => path.join(global.__TEST_DIRECTORIES__.uniqueFixturesOutput, relPath);
|
69
|
+
originalSetupLog('[SETUP] Globals set.');
|
100
70
|
|
101
|
-
|
102
|
-
|
103
|
-
await ensureDir(path.dirname(path.join(tempDir, 'font.woff2')));
|
71
|
+
originalSetupLog('[SETUP] Ensuring mock font dirs...');
|
72
|
+
if (!tempDir) throw new Error("tempDir is not defined before font dir creation!");
|
73
|
+
await ensureDir(path.dirname(path.join(tempDir, 'font.woff2')));
|
104
74
|
await ensureDir(path.dirname(path.join(tempDir, 'font.ttf')));
|
105
|
-
|
75
|
+
originalSetupLog('[SETUP] Mock font dirs ensured.');
|
106
76
|
|
107
|
-
|
77
|
+
originalSetupLog('[SETUP] Writing mock font files...');
|
108
78
|
await fs.writeFile(path.join(tempDir, 'font.woff2'), 'mock woff2 data');
|
109
79
|
await fs.writeFile(path.join(tempDir, 'font.ttf'), 'mock ttf data');
|
110
|
-
|
111
|
-
// Set up a global variable pointing to the temp directory for potential use in mocks.
|
112
80
|
global.__MOCK_FILE_PATH__ = tempDir;
|
81
|
+
originalSetupLog('[SETUP] Mock font files written.');
|
113
82
|
|
114
|
-
|
115
|
-
|
116
|
-
// to prevent ENOENT race conditions.
|
117
|
-
|
118
|
-
console.log(`Ensuring sample project directory exists: ${baseDirs.sampleProject}`);
|
119
|
-
// 1. Ensure the sample project directory exists FIRST
|
83
|
+
originalSetupLog('[SETUP] Ensuring sample project dir...');
|
84
|
+
if (!baseDirs || !baseDirs.sampleProject) throw new Error("baseDirs.sampleProject is not defined!");
|
120
85
|
await ensureDir(baseDirs.sampleProject);
|
86
|
+
originalSetupLog('[SETUP] Sample project dir ensured.');
|
121
87
|
|
122
|
-
|
88
|
+
originalSetupLog('[SETUP] Writing sample project files...');
|
123
89
|
try {
|
124
|
-
console.log(`Writing sample project files to ${baseDirs.sampleProject}...`);
|
125
90
|
await Promise.all([
|
126
|
-
fs.writeFile(
|
127
|
-
|
128
|
-
|
129
|
-
),
|
130
|
-
fs.writeFile(path.join(baseDirs.sampleProject, 'styles.css'), 'body { margin: 0; }'),
|
131
|
-
fs.writeFile(path.join(baseDirs.sampleProject, 'script.js'), `console.log('hello');`),
|
132
|
-
fs.writeFile(path.join(baseDirs.sampleProject, 'logo.png'), 'fake image data') // Using string for simplicity
|
91
|
+
fs.writeFile(path.join(baseDirs.sampleProject, 'index.html'), ``),
|
92
|
+
fs.writeFile(path.join(baseDirs.sampleProject, 'styles.css'), '/* Mock CSS */'),
|
93
|
+
fs.writeFile(path.join(baseDirs.sampleProject, 'script.js'), '// Mock JS'),
|
94
|
+
fs.writeFile(path.join(baseDirs.sampleProject, 'logo.png'), 'fake image data')
|
133
95
|
]);
|
134
|
-
|
96
|
+
originalSetupLog('[SETUP] Sample project files written.');
|
135
97
|
} catch (writeError) {
|
136
|
-
|
137
|
-
|
138
|
-
throw writeError; // Re-throw to potentially fail the setup.
|
98
|
+
originalSetupLog('[SETUP-ERROR] Failed to write sample project files:', writeError);
|
99
|
+
throw writeError;
|
139
100
|
}
|
140
|
-
// --- End Fix ---
|
141
101
|
|
142
|
-
|
143
|
-
// This helps keep test output clean and allows assertions on console messages.
|
102
|
+
originalSetupLog('[SETUP] Mocking console...');
|
144
103
|
const originalLog = console.log;
|
145
104
|
const originalWarn = console.warn;
|
146
105
|
const originalError = console.error;
|
106
|
+
// Use the GLOBAL `jest` object to access `fn`
|
107
|
+
global.console.log = jest.fn((...args) => { if (process.env.DEBUG) originalLog(...args); });
|
108
|
+
global.console.warn = jest.fn((...args) => { /* ... filtering logic ... */ if (process.env.DEBUG) originalWarn(...args); });
|
109
|
+
global.console.error = jest.fn((...args) => { if (process.env.DEBUG) originalError(...args); });
|
110
|
+
originalSetupLog('[SETUP] Console mocked.');
|
147
111
|
|
148
|
-
|
149
|
-
global.console.log = jest.fn((...args) => {
|
150
|
-
// Only log to actual console if DEBUG environment variable is set.
|
151
|
-
if (process.env.DEBUG) {
|
152
|
-
originalLog(...args);
|
153
|
-
}
|
154
|
-
});
|
155
|
-
|
156
|
-
global.console.warn = jest.fn((...args) => {
|
157
|
-
// Optionally filter specific warnings you expect and don't want cluttering output.
|
158
|
-
const msg = args.join(' ');
|
159
|
-
if (
|
160
|
-
msg.includes('Could not fetch asset') ||
|
161
|
-
msg.includes('Error minifying')
|
162
|
-
// Add other expected warning patterns here if needed
|
163
|
-
) {
|
164
|
-
return; // Suppress specific known warnings
|
165
|
-
}
|
166
|
-
// Log other warnings, potentially only in debug mode.
|
167
|
-
if (process.env.DEBUG) {
|
168
|
-
originalWarn(...args);
|
169
|
-
}
|
170
|
-
});
|
171
|
-
|
172
|
-
global.console.error = jest.fn((...args) => {
|
173
|
-
// Always show errors, especially in debug mode.
|
174
|
-
// Could add filtering if there are known, non-critical errors to ignore.
|
175
|
-
if (process.env.DEBUG) {
|
176
|
-
originalError(...args);
|
177
|
-
}
|
178
|
-
// Potentially log even without DEBUG for visibility during tests
|
179
|
-
// originalError(...args);
|
180
|
-
});
|
181
|
-
|
182
|
-
console.log("Test setup complete."); // Log setup finish
|
112
|
+
originalSetupLog("[SETUP] Test setup complete.");
|
183
113
|
});
|
184
114
|
|
185
|
-
/**
|
186
|
-
* Jest's global afterAll hook. Runs once after all tests in a suite file have completed.
|
187
|
-
* Cleans up the temporary directory created for the test run.
|
188
|
-
*/
|
189
115
|
afterAll(async () => {
|
190
|
-
|
191
|
-
const dirs = global.__TEST_DIRECTORIES__; // Retrieve paths from global scope
|
192
|
-
if (!dirs) {
|
193
|
-
console.warn("Global test directories not found during cleanup.");
|
194
|
-
return;
|
195
|
-
}
|
196
|
-
|
116
|
+
originalSetupLog(`[SETUP] Cleaning up test environment in: ${tempDir}`);
|
197
117
|
try {
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
118
|
+
if (tempDir) {
|
119
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
120
|
+
originalSetupLog(`[SETUP] Successfully cleaned up: ${tempDir}`);
|
121
|
+
} else {
|
122
|
+
originalSetupLog(`[SETUP-WARN] tempDir variable was not defined during cleanup.`);
|
123
|
+
}
|
202
124
|
} catch (e) {
|
203
|
-
// Log errors during cleanup but don't fail the tests typically.
|
204
|
-
// ENOENT means the directory was already gone, which is fine.
|
205
125
|
if (e.code !== 'ENOENT') {
|
206
|
-
|
126
|
+
originalSetupLog(`[SETUP-WARN] Could not fully clean test directory ${tempDir}:`, e.message);
|
207
127
|
} else {
|
208
|
-
|
128
|
+
originalSetupLog(`[SETUP] Test directory already removed: ${tempDir}`);
|
209
129
|
}
|
210
130
|
}
|
211
131
|
});
|
package/package.json
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
{
|
2
2
|
"name": "portapack",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.3.1",
|
4
4
|
"description": "📦 A tool to bundle and minify HTML and all its dependencies into a single portable file.",
|
5
5
|
"main": "dist/index.js",
|
6
6
|
"module": "dist/index.js",
|
7
7
|
"types": "dist/index.d.ts",
|
8
8
|
"type": "module",
|
9
9
|
"bin": {
|
10
|
-
"portapack": "./dist/cli/cli-entry.
|
10
|
+
"portapack": "./dist/cli/cli-entry.cjs"
|
11
11
|
},
|
12
12
|
"scripts": {
|
13
13
|
"dev": "concurrently --success=all -n BUILD,DOCS,TEST -c green,blue,magenta \"npm run dev:build\" \"npm run dev:docs\" \"npm run dev:test\"",
|
14
14
|
"dev:build": "tsup --watch",
|
15
15
|
"dev:docs": "concurrently \"npm run docs:api:watch\" \"npm run docs:dev\"",
|
16
16
|
"dev:test": "cross-env FORCE_COLOR=1 jest --watch --clearCache --passWithNoTests --watchPathIgnorePatterns=\"<rootDir>/tests/__fixtures__/output\"",
|
17
|
-
"dev:test:debug": "cross-env
|
17
|
+
"dev:test:debug": "cross-env FORCE_COLOR=1 jest --watch --runTestsByPath",
|
18
18
|
"build": "npm run build:code && npm run docs:api && npm run docs:build",
|
19
19
|
"build:code": "tsup",
|
20
20
|
"example": "npx tsx examples/main.ts",
|
21
|
-
"test": "cross-env
|
22
|
-
"test:ci": "cross-env
|
21
|
+
"test": "cross-env jest --coverage",
|
22
|
+
"test:ci": "cross-env jest --coverage --ci",
|
23
23
|
"test:clear": "jest --clearCache",
|
24
24
|
"coverage": "jest --coverage && open coverage/lcov-report/index.html",
|
25
25
|
"docs:coverage": "npm run test && mkdir -p docs/test-coverage && cp -r coverage/lcov-report/* docs/test-coverage/",
|
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 };
|