portapack 0.3.0 → 0.3.2
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 +67 -8
- package/.github/workflows/ci.yml +5 -4
- package/.releaserc.js +25 -27
- package/CHANGELOG.md +12 -19
- package/LICENSE.md +21 -0
- package/README.md +34 -36
- package/commitlint.config.js +30 -34
- package/dist/cli/cli-entry.cjs +199 -135
- package/dist/cli/cli-entry.cjs.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +194 -134
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +36 -34
- package/docs/.vitepress/sidebar-generator.ts +89 -38
- package/docs/cli.md +29 -82
- package/docs/code-of-conduct.md +7 -1
- package/docs/configuration.md +103 -117
- package/docs/contributing.md +6 -2
- package/docs/deployment.md +10 -5
- package/docs/development.md +8 -5
- package/docs/getting-started.md +76 -45
- package/docs/index.md +1 -1
- package/docs/public/android-chrome-192x192.png +0 -0
- package/docs/public/android-chrome-512x512.png +0 -0
- package/docs/public/apple-touch-icon.png +0 -0
- package/docs/public/favicon-16x16.png +0 -0
- package/docs/public/favicon-32x32.png +0 -0
- package/docs/public/favicon.ico +0 -0
- package/docs/site.webmanifest +1 -0
- package/docs/troubleshooting.md +12 -1
- package/examples/main.ts +7 -10
- package/examples/sample-project/script.js +1 -1
- package/jest.config.ts +8 -13
- package/nodemon.json +5 -10
- package/package.json +2 -5
- package/src/cli/cli-entry.ts +2 -2
- package/src/cli/cli.ts +21 -16
- package/src/cli/options.ts +127 -113
- package/src/core/bundler.ts +254 -221
- package/src/core/extractor.ts +639 -520
- package/src/core/minifier.ts +173 -162
- package/src/core/packer.ts +141 -137
- package/src/core/parser.ts +74 -73
- package/src/core/web-fetcher.ts +270 -258
- package/src/index.ts +18 -17
- package/src/types.ts +9 -11
- package/src/utils/font.ts +12 -6
- package/src/utils/logger.ts +110 -105
- package/src/utils/meta.ts +75 -76
- package/src/utils/mime.ts +50 -50
- package/src/utils/slugify.ts +33 -34
- package/tests/unit/cli/cli-entry.test.ts +72 -70
- package/tests/unit/cli/cli.test.ts +314 -278
- package/tests/unit/cli/options.test.ts +294 -301
- package/tests/unit/core/bundler.test.ts +426 -329
- package/tests/unit/core/extractor.test.ts +828 -380
- package/tests/unit/core/minifier.test.ts +374 -274
- package/tests/unit/core/packer.test.ts +298 -264
- package/tests/unit/core/parser.test.ts +538 -150
- package/tests/unit/core/web-fetcher.test.ts +389 -359
- package/tests/unit/index.test.ts +238 -197
- package/tests/unit/utils/font.test.ts +26 -21
- package/tests/unit/utils/logger.test.ts +267 -260
- package/tests/unit/utils/meta.test.ts +29 -28
- package/tests/unit/utils/mime.test.ts +73 -74
- package/tests/unit/utils/slugify.test.ts +14 -12
- package/tsconfig.build.json +9 -10
- package/tsconfig.jest.json +2 -1
- package/tsconfig.json +2 -2
- package/tsup.config.ts +8 -8
- package/typedoc.json +5 -9
- package/docs/demo.md +0 -46
- /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
- /package/docs/{portapack.jpg → public/portapack.jpg} +0 -0
@@ -8,89 +8,88 @@ import { describe, it, expect } from '@jest/globals';
|
|
8
8
|
import type { Asset } from '../../../src/types'; // Import Asset type
|
9
9
|
|
10
10
|
describe('🧪 MIME Utilities', () => {
|
11
|
+
describe('guessMimeType()', () => {
|
12
|
+
const defaultResult = { mime: 'application/octet-stream', assetType: 'other' as Asset['type'] };
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
// Test cases: [input, expectedMime, expectedAssetType]
|
15
|
+
const testCases: [string, string, Asset['type']][] = [
|
16
|
+
// CSS
|
17
|
+
['style.css', 'text/css', 'css'],
|
18
|
+
['path/to/style.CSS', 'text/css', 'css'], // Case-insensitive extension
|
19
|
+
['style.css?v=1.0', 'text/css', 'css'], // With query string
|
20
|
+
['/path/style.css#id', 'text/css', 'css'], // With fragment
|
21
|
+
['https://example.com/a/b/c/style.css?q=1', 'text/css', 'css'], // Remote URL
|
14
22
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
['path/to/style.CSS', 'text/css', 'css'], // Case-insensitive extension
|
20
|
-
['style.css?v=1.0', 'text/css', 'css'], // With query string
|
21
|
-
['/path/style.css#id', 'text/css', 'css'], // With fragment
|
22
|
-
['https://example.com/a/b/c/style.css?q=1', 'text/css', 'css'], // Remote URL
|
23
|
+
// JS
|
24
|
+
['script.js', 'application/javascript', 'js'],
|
25
|
+
['script.mjs', 'application/javascript', 'js'],
|
26
|
+
['https://cdn.com/lib.js', 'application/javascript', 'js'],
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
+
// Images
|
29
|
+
['logo.png', 'image/png', 'image'],
|
30
|
+
['photo.jpg', 'image/jpeg', 'image'],
|
31
|
+
['image.jpeg', 'image/jpeg', 'image'],
|
32
|
+
['anim.gif', 'image/gif', 'image'],
|
33
|
+
['icon.svg', 'image/svg+xml', 'image'],
|
34
|
+
['image.webp', 'image/webp', 'image'],
|
35
|
+
['favicon.ico', 'image/x-icon', 'image'],
|
36
|
+
['image.avif', 'image/avif', 'image'],
|
28
37
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
['image.webp', 'image/webp', 'image'],
|
36
|
-
['favicon.ico', 'image/x-icon', 'image'],
|
37
|
-
['image.avif', 'image/avif', 'image'],
|
38
|
+
// Fonts
|
39
|
+
['font.woff', 'font/woff', 'font'],
|
40
|
+
['font.woff2', 'font/woff2', 'font'],
|
41
|
+
['font.ttf', 'font/ttf', 'font'],
|
42
|
+
['font.otf', 'font/otf', 'font'],
|
43
|
+
['font.eot', 'application/vnd.ms-fontobject', 'font'],
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
// Audio/Video ('other')
|
46
|
+
['audio.mp3', 'audio/mpeg', 'other'],
|
47
|
+
['audio.ogg', 'audio/ogg', 'other'],
|
48
|
+
['audio.wav', 'audio/wav', 'other'],
|
49
|
+
['video.mp4', 'video/mp4', 'other'],
|
50
|
+
['video.webm', 'video/webm', 'other'],
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
// Other ('other')
|
53
|
+
['data.json', 'application/json', 'other'],
|
54
|
+
['manifest.webmanifest', 'application/manifest+json', 'other'],
|
55
|
+
['document.xml', 'application/xml', 'other'],
|
56
|
+
['page.html', 'text/html', 'other'],
|
57
|
+
['notes.txt', 'text/plain', 'other'],
|
52
58
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
// Edge cases
|
60
|
+
['file_without_extension', defaultResult.mime, defaultResult.assetType],
|
61
|
+
['file.unknown', defaultResult.mime, defaultResult.assetType],
|
62
|
+
['.', defaultResult.mime, defaultResult.assetType], // Just a dot
|
63
|
+
['image.', defaultResult.mime, defaultResult.assetType], // Dot at the end
|
64
|
+
// URLs with complex paths/queries but known extensions
|
65
|
+
['https://example.com/complex/path.with.dots/image.png?a=1&b=2#frag', 'image/png', 'image'],
|
66
|
+
['file:///C:/Users/Test/Documents/my%20font.ttf', 'font/ttf', 'font'], // File URI
|
67
|
+
];
|
59
68
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
// URLs with complex paths/queries but known extensions
|
66
|
-
['https://example.com/complex/path.with.dots/image.png?a=1&b=2#frag', 'image/png', 'image'],
|
67
|
-
['file:///C:/Users/Test/Documents/my%20font.ttf', 'font/ttf', 'font'], // File URI
|
68
|
-
];
|
69
|
+
// it.each(testCases)('should return correct type for "%s"', (input, expectedMime, expectedAssetType) => {
|
70
|
+
// const result = guessMimeType(input);
|
71
|
+
// expect(result.mime).toBe(expectedMime);
|
72
|
+
// expect(result.assetType).toBe(expectedAssetType);
|
73
|
+
// });
|
69
74
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
it('should return default for null or empty input', () => {
|
77
|
-
// @ts-expect-error Testing invalid input
|
78
|
-
expect(guessMimeType(null)).toEqual(defaultResult);
|
79
|
-
expect(guessMimeType('')).toEqual(defaultResult);
|
80
|
-
expect(guessMimeType(undefined as any)).toEqual(defaultResult); // Test undefined
|
81
|
-
});
|
75
|
+
it('should return default for null or empty input', () => {
|
76
|
+
// @ts-expect-error Testing invalid input
|
77
|
+
expect(guessMimeType(null)).toEqual(defaultResult);
|
78
|
+
expect(guessMimeType('')).toEqual(defaultResult);
|
79
|
+
expect(guessMimeType(undefined as any)).toEqual(defaultResult); // Test undefined
|
82
80
|
});
|
81
|
+
});
|
83
82
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
83
|
+
// Test deprecated getFontMimeType (should just delegate)
|
84
|
+
describe('getFontMimeType() [Deprecated]', () => {
|
85
|
+
it('should return correct font MIME type', () => {
|
86
|
+
expect(getFontMimeType('font.woff2')).toBe('font/woff2');
|
87
|
+
expect(getFontMimeType('font.ttf')).toBe('font/ttf');
|
88
|
+
});
|
90
89
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
});
|
90
|
+
it('should delegate to guessMimeType and return default for non-fonts', () => {
|
91
|
+
expect(getFontMimeType('style.css')).toBe('text/css'); // Returns CSS mime
|
92
|
+
expect(getFontMimeType('unknown.ext')).toBe('application/octet-stream'); // Returns default
|
93
|
+
});
|
94
|
+
});
|
95
|
+
});
|
@@ -1,21 +1,23 @@
|
|
1
|
+
/**
|
2
|
+
* @file tests/unit/utils/slugify.test.ts
|
3
|
+
* @description Unit tests for the slugify util.
|
4
|
+
*/
|
5
|
+
|
1
6
|
import { slugify } from '../../../src/utils/slugify'; // Adjust path if needed
|
2
7
|
import { describe, it, expect } from '@jest/globals';
|
3
8
|
|
4
9
|
describe('slugify()', () => {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
it('should handle typical URLs', () => {
|
11
|
+
// --- Expectations matching the corrected slugify logic ---
|
12
|
+
expect(slugify('https://site.com/path/page.html')).toBe('path-page');
|
13
|
+
expect(slugify('products/item-1.html')).toBe('products-item-1');
|
14
|
+
expect(slugify(' search?q=test page 2 ')).toBe('search-q-test-page-2');
|
15
|
+
expect(slugify('/path/with/slashes/')).toBe('path-with-slashes');
|
16
|
+
// ----------------------------------------------------------
|
17
|
+
expect(slugify('')).toBe('index');
|
18
|
+
});
|
15
19
|
});
|
16
20
|
|
17
|
-
|
18
|
-
|
19
21
|
// describe('🔧 sanitizeSlug()', () => {
|
20
22
|
// const tests: Array<[string, string]> = [
|
21
23
|
// // Basic pages
|
package/tsconfig.build.json
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
{
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
"extends": "./tsconfig.json", // Inherit from tsconfig.json
|
3
|
+
"compilerOptions": {
|
4
|
+
"noEmit": false, // Ensure files are emitted
|
5
|
+
"declaration": true, // Generate type declaration files
|
6
|
+
"emitDeclarationOnly": true, // Only generate .d.ts files
|
7
|
+
"outDir": "dist/types" // Output directory for declaration files
|
8
|
+
},
|
9
|
+
"include": ["src/**/*"] // Include all TypeScript files in src
|
10
|
+
}
|
package/tsconfig.jest.json
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
"isolatedModules": false, // Often better to set false when using CommonJS/ts-jest
|
7
7
|
"esModuleInterop": true,
|
8
8
|
"allowSyntheticDefaultImports": true,
|
9
|
+
"resolveJsonModule": true,
|
9
10
|
"sourceMap": true,
|
10
11
|
"noEmit": true,
|
11
12
|
"target": "ES2022", // Keep target if your Node version supports it
|
@@ -14,4 +15,4 @@
|
|
14
15
|
},
|
15
16
|
"include": ["src/**/*", "tests/**/*"],
|
16
17
|
"exclude": ["node_modules", "dist"]
|
17
|
-
}
|
18
|
+
}
|
package/tsconfig.json
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
{
|
2
2
|
"compilerOptions": {
|
3
3
|
"target": "ES2022",
|
4
|
-
"module": "ESNext",
|
5
|
-
"moduleResolution": "Bundler",
|
4
|
+
"module": "ESNext", // ✅ ESM output
|
5
|
+
"moduleResolution": "Bundler", // ✅ lets TS handle extensions properly
|
6
6
|
"declaration": true,
|
7
7
|
"sourceMap": true,
|
8
8
|
"outDir": "./dist",
|
package/tsup.config.ts
CHANGED
@@ -24,19 +24,19 @@ export default defineConfig([
|
|
24
24
|
'cli-entry': 'src/cli/cli-entry.ts',
|
25
25
|
},
|
26
26
|
outDir: 'dist/cli',
|
27
|
-
format: ['cjs'],
|
27
|
+
format: ['cjs'], // ✅ Required for CLI to work with npx
|
28
28
|
platform: 'node',
|
29
29
|
target: 'node18',
|
30
30
|
splitting: false,
|
31
|
-
clean: true,
|
32
|
-
dts: false,
|
31
|
+
clean: true, // Wipe dist/cli clean on each build
|
32
|
+
dts: false, // No types for CLI
|
33
33
|
sourcemap: true,
|
34
34
|
banner: {
|
35
|
-
js: '#!/usr/bin/env node',
|
35
|
+
js: '#!/usr/bin/env node', // ✅ Required for CLI shebang
|
36
36
|
},
|
37
37
|
outExtension() {
|
38
38
|
return {
|
39
|
-
js: '.cjs',
|
39
|
+
js: '.cjs', // ✅ Required: prevents ESM misinterpretation
|
40
40
|
};
|
41
41
|
},
|
42
42
|
esbuildOptions(options) {
|
@@ -54,12 +54,12 @@ export default defineConfig([
|
|
54
54
|
index: 'src/index.ts',
|
55
55
|
},
|
56
56
|
outDir: 'dist',
|
57
|
-
format: ['esm'],
|
57
|
+
format: ['esm'], // ✅ Modern ESM output for consumers
|
58
58
|
platform: 'node',
|
59
59
|
target: 'node18',
|
60
60
|
splitting: false,
|
61
|
-
clean: false,
|
62
|
-
dts: true,
|
61
|
+
clean: false, // Don't wipe CLI build!
|
62
|
+
dts: true, // ✅ Generate TypeScript declarations
|
63
63
|
sourcemap: true,
|
64
64
|
outExtension() {
|
65
65
|
return {
|
package/typedoc.json
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
{
|
2
|
-
"entryPoints": [
|
3
|
-
"src/index.ts",
|
4
|
-
"src/types.ts",
|
5
|
-
"src/cli/cli.ts"
|
6
|
-
],
|
2
|
+
"entryPoints": ["src/index.ts", "src/types.ts", "src/cli/cli.ts"],
|
7
3
|
"out": "docs/api",
|
8
4
|
"plugin": ["typedoc-plugin-markdown"],
|
9
5
|
"tsconfig": "tsconfig.json",
|
@@ -15,9 +11,9 @@
|
|
15
11
|
"theme": "markdown",
|
16
12
|
"entryPointStrategy": "expand",
|
17
13
|
"exclude": [
|
18
|
-
"**/node_modules/**",
|
19
|
-
"**/test/**",
|
20
|
-
"**/tests/**",
|
14
|
+
"**/node_modules/**",
|
15
|
+
"**/test/**",
|
16
|
+
"**/tests/**",
|
21
17
|
"**/dist/**",
|
22
18
|
"**/*.spec.ts",
|
23
19
|
"**/*.test.ts"
|
@@ -25,4 +21,4 @@
|
|
25
21
|
"sort": ["alphabetical"],
|
26
22
|
"categorizeByGroup": true,
|
27
23
|
"sourceLinkTemplate": "https://github.com/manicinc/portapack/blob/master/{path}#L{line}"
|
28
|
-
}
|
24
|
+
}
|
package/docs/demo.md
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
---
|
2
|
-
# 🌐 Live Demo
|
3
|
-
|
4
|
-
## What You’ll See
|
5
|
-
|
6
|
-
- A fully portable HTML site
|
7
|
-
- Every internal page inlined
|
8
|
-
- No external requests
|
9
|
-
|
10
|
-
---
|
11
|
-
|
12
|
-
## Example Output
|
13
|
-
|
14
|
-
> Download this page and open it offline. It works!
|
15
|
-
|
16
|
-
<!-- [Download Demo Portable HTML](./bootstrap-packed.html) -->
|
17
|
-
|
18
|
-
---
|
19
|
-
|
20
|
-
## How It Was Generated
|
21
|
-
|
22
|
-
```bash
|
23
|
-
portapack -i https://getbootstrap.com --recursive --max-depth 1 -o bootstrap-packed.html
|
24
|
-
```
|
25
|
-
|
26
|
-
---
|
27
|
-
|
28
|
-
## Client-Side Navigation
|
29
|
-
|
30
|
-
Recursively packed pages are wrapped in `<template>` blocks with an embedded router.
|
31
|
-
|
32
|
-
```html
|
33
|
-
<template id="page-home">
|
34
|
-
<h1>Homepage</h1>
|
35
|
-
</template>
|
36
|
-
<template id="page-about">
|
37
|
-
<h1>About</h1>
|
38
|
-
</template>
|
39
|
-
```
|
40
|
-
|
41
|
-
```js
|
42
|
-
window.addEventListener('hashchange', () => {
|
43
|
-
const id = location.hash.replace('#', '') || 'home';
|
44
|
-
showPage(id);
|
45
|
-
});
|
46
|
-
```
|
File without changes
|
File without changes
|