portapack 0.2.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/.eslintrc.json +9 -0
- package/.github/workflows/ci.yml +73 -0
- package/.github/workflows/deploy-pages.yml +56 -0
- package/.prettierrc +9 -0
- package/.releaserc.js +29 -0
- package/CHANGELOG.md +21 -0
- package/README.md +288 -0
- package/commitlint.config.js +36 -0
- package/dist/cli/cli-entry.js +1694 -0
- package/dist/cli/cli-entry.js.map +1 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +1405 -0
- package/dist/index.js.map +1 -0
- package/docs/.vitepress/config.ts +89 -0
- package/docs/.vitepress/sidebar-generator.ts +73 -0
- package/docs/cli.md +117 -0
- package/docs/code-of-conduct.md +65 -0
- package/docs/configuration.md +151 -0
- package/docs/contributing.md +107 -0
- package/docs/demo.md +46 -0
- package/docs/deployment.md +132 -0
- package/docs/development.md +168 -0
- package/docs/getting-started.md +106 -0
- package/docs/index.md +40 -0
- package/docs/portapack-transparent.png +0 -0
- package/docs/portapack.jpg +0 -0
- package/docs/troubleshooting.md +107 -0
- package/examples/main.ts +118 -0
- package/examples/sample-project/index.html +12 -0
- package/examples/sample-project/logo.png +1 -0
- package/examples/sample-project/script.js +1 -0
- package/examples/sample-project/styles.css +1 -0
- package/jest.config.ts +124 -0
- package/jest.setup.cjs +211 -0
- package/nodemon.json +11 -0
- package/output.html +1 -0
- package/package.json +161 -0
- package/site-packed.html +1 -0
- package/src/cli/cli-entry.ts +28 -0
- package/src/cli/cli.ts +139 -0
- package/src/cli/options.ts +151 -0
- package/src/core/bundler.ts +201 -0
- package/src/core/extractor.ts +618 -0
- package/src/core/minifier.ts +233 -0
- package/src/core/packer.ts +191 -0
- package/src/core/parser.ts +115 -0
- package/src/core/web-fetcher.ts +292 -0
- package/src/index.ts +262 -0
- package/src/types.ts +163 -0
- package/src/utils/font.ts +41 -0
- package/src/utils/logger.ts +139 -0
- package/src/utils/meta.ts +100 -0
- package/src/utils/mime.ts +90 -0
- package/src/utils/slugify.ts +70 -0
- package/test-output.html +0 -0
- package/tests/__fixtures__/sample-project/index.html +5 -0
- package/tests/unit/cli/cli-entry.test.ts +104 -0
- package/tests/unit/cli/cli.test.ts +230 -0
- package/tests/unit/cli/options.test.ts +316 -0
- package/tests/unit/core/bundler.test.ts +287 -0
- package/tests/unit/core/extractor.test.ts +1129 -0
- package/tests/unit/core/minifier.test.ts +414 -0
- package/tests/unit/core/packer.test.ts +193 -0
- package/tests/unit/core/parser.test.ts +540 -0
- package/tests/unit/core/web-fetcher.test.ts +374 -0
- package/tests/unit/index.test.ts +339 -0
- package/tests/unit/utils/font.test.ts +81 -0
- package/tests/unit/utils/logger.test.ts +275 -0
- package/tests/unit/utils/meta.test.ts +70 -0
- package/tests/unit/utils/mime.test.ts +96 -0
- package/tests/unit/utils/slugify.test.ts +71 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.jest.json +17 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +71 -0
- package/typedoc.json +28 -0
package/jest.setup.cjs
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
/**
|
2
|
+
* @file jest.setup.cjs
|
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
|
11
|
+
const path = require('path');
|
12
|
+
const crypto = require('crypto');
|
13
|
+
const os = require('os');
|
14
|
+
|
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
|
+
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
|
+
const resolve = (...args) => path.resolve(__dirname, ...args);
|
23
|
+
|
24
|
+
/**
|
25
|
+
* Utility function to ensure a directory exists.
|
26
|
+
* Creates the directory recursively if it doesn't exist.
|
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
|
+
*/
|
32
|
+
const ensureDir = async (dir) => {
|
33
|
+
try {
|
34
|
+
await fs.mkdir(dir, { recursive: true });
|
35
|
+
} catch (e) {
|
36
|
+
// If the error is simply that the directory already exists, ignore it.
|
37
|
+
if (e.code !== 'EEXIST') {
|
38
|
+
console.error(`Failed to ensure directory ${dir}:`, e); // Log unexpected errors
|
39
|
+
throw e; // Re-throw other errors
|
40
|
+
}
|
41
|
+
}
|
42
|
+
};
|
43
|
+
|
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
|
+
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
|
+
*/
|
52
|
+
const baseDirs = {
|
53
|
+
root: resolve(), // Project root (where jest.config / setup file likely is)
|
54
|
+
output: path.join(tempDir, 'test-output'), // General output for test artifacts
|
55
|
+
fixtures: path.join(tempDir, '__fixtures__'), // Base for fixture data (if needed)
|
56
|
+
fixturesOutput: path.join(tempDir, '__fixtures__/output'), // Output specifically related to fixtures
|
57
|
+
sampleProject: path.join(tempDir, 'sample-project') // Directory for the mock HTML project
|
58
|
+
};
|
59
|
+
|
60
|
+
/**
|
61
|
+
* Jest's global beforeAll hook. Runs once before any tests in a suite file execute.
|
62
|
+
* Sets up the necessary directory structure and global variables/mocks for tests.
|
63
|
+
*/
|
64
|
+
beforeAll(async () => {
|
65
|
+
console.log(`Setting up test environment in: ${tempDir}`); // Log setup start
|
66
|
+
|
67
|
+
// Create essential output/fixture directories early.
|
68
|
+
// Using Promise.all here is likely safe as they are independent paths.
|
69
|
+
await Promise.all([
|
70
|
+
ensureDir(baseDirs.output),
|
71
|
+
ensureDir(baseDirs.fixturesOutput)
|
72
|
+
]);
|
73
|
+
|
74
|
+
// Define paths specific to this test run using the TEST_RUN_ID for further isolation.
|
75
|
+
const runDirs = {
|
76
|
+
uniqueOutput: path.join(baseDirs.output, TEST_RUN_ID),
|
77
|
+
uniqueFixturesOutput: path.join(baseDirs.fixturesOutput, TEST_RUN_ID)
|
78
|
+
};
|
79
|
+
|
80
|
+
// Create the run-specific directories.
|
81
|
+
await Promise.all([
|
82
|
+
fs.mkdir(runDirs.uniqueOutput, { recursive: true }),
|
83
|
+
fs.mkdir(runDirs.uniqueFixturesOutput, { recursive: true })
|
84
|
+
]);
|
85
|
+
|
86
|
+
// --- Set up global variables accessible within tests ---
|
87
|
+
// This provides a consistent way for tests to access temporary paths.
|
88
|
+
global.__TEST_DIRECTORIES__ = {
|
89
|
+
...baseDirs,
|
90
|
+
...runDirs
|
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);
|
100
|
+
|
101
|
+
// --- Create specific mock files needed by certain tests ---
|
102
|
+
// Example: Creating virtual font files for font processing tests.
|
103
|
+
await ensureDir(path.dirname(path.join(tempDir, 'font.woff2'))); // Ensure parent dir exists
|
104
|
+
await ensureDir(path.dirname(path.join(tempDir, 'font.ttf')));
|
105
|
+
// No need to ensureDir for 'missing.ttf' as we only need the path, not the file
|
106
|
+
|
107
|
+
// Write mock content to the font files.
|
108
|
+
await fs.writeFile(path.join(tempDir, 'font.woff2'), 'mock woff2 data');
|
109
|
+
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
|
+
global.__MOCK_FILE_PATH__ = tempDir;
|
113
|
+
|
114
|
+
// --- Create the stub/mock sample HTML project ---
|
115
|
+
// **FIX APPLIED HERE:** Ensure directory exists *before* writing files into it
|
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
|
120
|
+
await ensureDir(baseDirs.sampleProject);
|
121
|
+
|
122
|
+
// 2. THEN write all the files into it using Promise.all for concurrency (safe now).
|
123
|
+
try {
|
124
|
+
console.log(`Writing sample project files to ${baseDirs.sampleProject}...`);
|
125
|
+
await Promise.all([
|
126
|
+
fs.writeFile(
|
127
|
+
path.join(baseDirs.sampleProject, 'index.html'),
|
128
|
+
`<!DOCTYPE html><html><head><link rel="stylesheet" href="styles.css"></head><body><img src="logo.png"/><script src="script.js"></script></body></html>`
|
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
|
133
|
+
]);
|
134
|
+
console.log(`Successfully wrote sample project files.`);
|
135
|
+
} catch (writeError) {
|
136
|
+
console.error(`Failed to write sample project files to ${baseDirs.sampleProject}:`, writeError);
|
137
|
+
// Depending on setup needs, you might want to stop the tests here.
|
138
|
+
throw writeError; // Re-throw to potentially fail the setup.
|
139
|
+
}
|
140
|
+
// --- End Fix ---
|
141
|
+
|
142
|
+
// --- Mock console methods ---
|
143
|
+
// This helps keep test output clean and allows assertions on console messages.
|
144
|
+
const originalLog = console.log;
|
145
|
+
const originalWarn = console.warn;
|
146
|
+
const originalError = console.error;
|
147
|
+
|
148
|
+
// Replace global console methods with Jest mocks.
|
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
|
183
|
+
});
|
184
|
+
|
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
|
+
afterAll(async () => {
|
190
|
+
console.log(`Cleaning up test environment in: ${tempDir}`); // Log cleanup start
|
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
|
+
|
197
|
+
try {
|
198
|
+
// Recursively remove the entire temporary directory for this test run.
|
199
|
+
// Using force: true helps ignore errors if files are missing (e.g., cleanup already ran partially).
|
200
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
201
|
+
console.log(`Successfully cleaned up test directory: ${tempDir}`);
|
202
|
+
} 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
|
+
if (e.code !== 'ENOENT') {
|
206
|
+
console.warn(`Could not fully clean test directory ${tempDir}:`, e.message);
|
207
|
+
} else {
|
208
|
+
console.log(`Test directory already removed: ${tempDir}`);
|
209
|
+
}
|
210
|
+
}
|
211
|
+
});
|
package/nodemon.json
ADDED
package/output.html
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<html>Warning</html>
|
package/package.json
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
{
|
2
|
+
"name": "portapack",
|
3
|
+
"version": "0.2.1",
|
4
|
+
"description": "š¦ A tool to bundle and minify HTML and all its dependencies into a single portable file.",
|
5
|
+
"main": "dist/index.js",
|
6
|
+
"module": "dist/index.js",
|
7
|
+
"types": "dist/index.d.ts",
|
8
|
+
"type": "module",
|
9
|
+
"bin": {
|
10
|
+
"portapack": "./dist/cli/cli-entry.js"
|
11
|
+
},
|
12
|
+
"scripts": {
|
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
|
+
"dev:build": "tsup --watch",
|
15
|
+
"dev:docs": "concurrently \"npm run docs:api:watch\" \"npm run docs:dev\"",
|
16
|
+
"dev:test": "cross-env FORCE_COLOR=1 jest --watch --clearCache --passWithNoTests --watchPathIgnorePatterns=\"<rootDir>/tests/__fixtures__/output\"",
|
17
|
+
"dev:test:debug": "cross-env NODE_OPTIONS=--experimental-vm-modules FORCE_COLOR=1 jest --watch --runTestsByPath",
|
18
|
+
"build": "npm run build:code && npm run docs:api && npm run docs:build",
|
19
|
+
"build:code": "tsup",
|
20
|
+
"example": "npx tsx examples/main.ts",
|
21
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
22
|
+
"test:ci": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage --ci",
|
23
|
+
"test:clear": "jest --clearCache",
|
24
|
+
"coverage": "jest --coverage && open coverage/lcov-report/index.html",
|
25
|
+
"docs:coverage": "npm run test && mkdir -p docs/test-coverage && cp -r coverage/lcov-report/* docs/test-coverage/",
|
26
|
+
"prepare": "npm run build",
|
27
|
+
"publish:npm": "npm publish",
|
28
|
+
"lint": "eslint . --ext .ts,.js",
|
29
|
+
"format": "prettier --write \"**/*.{ts,js,json,md}\"",
|
30
|
+
"format:check": "prettier --check \"**/*.{ts,js,json,md}\"",
|
31
|
+
"commit": "git-cz",
|
32
|
+
"semantic-release": "semantic-release",
|
33
|
+
"coveralls": "jest --coverage && coveralls < coverage/lcov.info",
|
34
|
+
"docs:dev": "vitepress dev docs",
|
35
|
+
"docs:api": "typedoc --options typedoc.json",
|
36
|
+
"docs:api:watch": "nodemon --watch src --ext ts,tsx --exec \"npm run docs:api\"",
|
37
|
+
"docs:build": "vitepress build docs",
|
38
|
+
"docs:preview": "vitepress preview docs",
|
39
|
+
"docs:deploy": "npm run docs:api && vitepress build docs && gh-pages -d docs/.vitepress/dist",
|
40
|
+
"docs:serve": "npm run docs:api && vitepress build docs && vitepress serve docs"
|
41
|
+
},
|
42
|
+
"keywords": [
|
43
|
+
"html",
|
44
|
+
"bundler",
|
45
|
+
"portable",
|
46
|
+
"minify",
|
47
|
+
"packer",
|
48
|
+
"assets",
|
49
|
+
"cli",
|
50
|
+
"offline",
|
51
|
+
"webapp",
|
52
|
+
"static-site",
|
53
|
+
"bundle",
|
54
|
+
"recursive"
|
55
|
+
],
|
56
|
+
"author": "Manic.agency",
|
57
|
+
"license": "MIT",
|
58
|
+
"repository": {
|
59
|
+
"type": "git",
|
60
|
+
"url": "https://github.com/manicinc/portapack.git"
|
61
|
+
},
|
62
|
+
"bugs": {
|
63
|
+
"url": "https://github.com/manicinc/portapack/issues"
|
64
|
+
},
|
65
|
+
"homepage": "https://github.com/manicinc/portapack#readme",
|
66
|
+
"dependencies": {
|
67
|
+
"axios": "^1.8.4",
|
68
|
+
"chalk": "^5.4.1",
|
69
|
+
"cheerio": "^1.0.0",
|
70
|
+
"clean-css": "^5.3.3",
|
71
|
+
"commander": "^13.0.0",
|
72
|
+
"html-minifier-terser": "^7.2.0",
|
73
|
+
"puppeteer": "^24.6.0",
|
74
|
+
"terser": "^5.39.0"
|
75
|
+
},
|
76
|
+
"devDependencies": {
|
77
|
+
"@commitlint/cli": "^19.0.0",
|
78
|
+
"@commitlint/config-conventional": "^19.0.0",
|
79
|
+
"@semantic-release/changelog": "^6.0.3",
|
80
|
+
"@semantic-release/git": "^10.0.1",
|
81
|
+
"@types/clean-css": "^4.2.11",
|
82
|
+
"@types/html-minifier-terser": "^7.0.2",
|
83
|
+
"@types/jest": "^29.5.14",
|
84
|
+
"@types/node": "^22.13.14",
|
85
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
86
|
+
"@typescript-eslint/parser": "^7.0.0",
|
87
|
+
"commitizen": "^4.3.0",
|
88
|
+
"concurrently": "^9.1.2",
|
89
|
+
"coveralls": "^3.1.1",
|
90
|
+
"cross-env": "^7.0.3",
|
91
|
+
"cz-conventional-changelog": "^3.3.0",
|
92
|
+
"eslint": "^8.57.0",
|
93
|
+
"eslint-config-prettier": "^9.0.0",
|
94
|
+
"eslint-plugin-jest": "^27.9.0",
|
95
|
+
"execa": "^9.5.2",
|
96
|
+
"gh-pages": "^6.3.0",
|
97
|
+
"glob": "^11.0.1",
|
98
|
+
"husky": "^9.0.0",
|
99
|
+
"jest": "^29.7.0",
|
100
|
+
"jest-environment-node": "^29.7.0",
|
101
|
+
"jest-watch-typeahead": "^2.2.2",
|
102
|
+
"lint-staged": "^15.2.0",
|
103
|
+
"nodemon": "^3.1.9",
|
104
|
+
"prettier": "^3.2.5",
|
105
|
+
"semantic-release": "^23.0.0",
|
106
|
+
"ts-jest": "^29.3.1",
|
107
|
+
"ts-node": "^10.9.2",
|
108
|
+
"tsup": "^8.4.0",
|
109
|
+
"typedoc": "^0.28.1",
|
110
|
+
"typedoc-plugin-markdown": "^4.6.0",
|
111
|
+
"typescript": "^5.8.2",
|
112
|
+
"vitepress": "^1.6.3"
|
113
|
+
},
|
114
|
+
"config": {
|
115
|
+
"commitizen": {
|
116
|
+
"path": "./node_modules/cz-conventional-changelog"
|
117
|
+
}
|
118
|
+
},
|
119
|
+
"husky": {
|
120
|
+
"hooks": {
|
121
|
+
"pre-commit": "lint-staged",
|
122
|
+
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
123
|
+
}
|
124
|
+
},
|
125
|
+
"lint-staged": {
|
126
|
+
"*.{ts,js}": [
|
127
|
+
"eslint --fix",
|
128
|
+
"prettier --write"
|
129
|
+
],
|
130
|
+
"*.{json,md}": [
|
131
|
+
"prettier --write"
|
132
|
+
]
|
133
|
+
},
|
134
|
+
"commitlint": {
|
135
|
+
"extends": [
|
136
|
+
"@commitlint/config-conventional"
|
137
|
+
]
|
138
|
+
},
|
139
|
+
"release": {
|
140
|
+
"branches": [
|
141
|
+
"master"
|
142
|
+
],
|
143
|
+
"plugins": [
|
144
|
+
"@semantic-release/commit-analyzer",
|
145
|
+
"@semantic-release/release-notes-generator",
|
146
|
+
"@semantic-release/changelog",
|
147
|
+
"@semantic-release/npm",
|
148
|
+
"@semantic-release/github",
|
149
|
+
[
|
150
|
+
"@semantic-release/git",
|
151
|
+
{
|
152
|
+
"assets": [
|
153
|
+
"CHANGELOG.md",
|
154
|
+
"package.json"
|
155
|
+
],
|
156
|
+
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
|
157
|
+
}
|
158
|
+
]
|
159
|
+
]
|
160
|
+
}
|
161
|
+
}
|
package/site-packed.html
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
<html>Generated Recursive Number</html>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
/**
|
2
|
+
* @file cli-entry.ts
|
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'`
|
9
|
+
*/
|
10
|
+
|
11
|
+
import type { CLIResult } from '../types';
|
12
|
+
|
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
|
+
const startCLI = async (): Promise<CLIResult> => {
|
19
|
+
const { main } = await import('./cli.js');
|
20
|
+
return await main(process.argv);
|
21
|
+
};
|
22
|
+
|
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
|
26
|
+
}
|
27
|
+
|
28
|
+
export { startCLI };
|
package/src/cli/cli.ts
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
/**
|
2
|
+
* @file cli.ts
|
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.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import fs from 'fs'; // Use default import if mocking default below
|
9
|
+
import path from 'path';
|
10
|
+
import { fileURLToPath } from 'url';
|
11
|
+
|
12
|
+
import { parseOptions } from './options.js';
|
13
|
+
import { generatePortableHTML, generateRecursivePortableHTML } from '../index';
|
14
|
+
import type { CLIResult } from '../types';
|
15
|
+
|
16
|
+
import { LogLevel } from '../types';
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Dynamically loads package.json metadata.
|
20
|
+
*/
|
21
|
+
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
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Entry function for running the CLI.
|
39
|
+
*/
|
40
|
+
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;
|
128
|
+
}
|
129
|
+
|
130
|
+
return { stdout, stderr, exitCode };
|
131
|
+
}
|
132
|
+
|
133
|
+
// Optional: Define main export if this file is intended to be run directly
|
134
|
+
export const main = runCli;
|
135
|
+
|
136
|
+
// Example direct execution (usually handled by bin entry in package.json)
|
137
|
+
// if (require.main === module) {
|
138
|
+
// runCli();
|
139
|
+
// }
|