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.
Files changed (74) hide show
  1. package/.eslintrc.json +67 -8
  2. package/.github/workflows/ci.yml +5 -4
  3. package/.releaserc.js +25 -27
  4. package/CHANGELOG.md +12 -19
  5. package/LICENSE.md +21 -0
  6. package/README.md +34 -36
  7. package/commitlint.config.js +30 -34
  8. package/dist/cli/cli-entry.cjs +199 -135
  9. package/dist/cli/cli-entry.cjs.map +1 -1
  10. package/dist/index.d.ts +0 -3
  11. package/dist/index.js +194 -134
  12. package/dist/index.js.map +1 -1
  13. package/docs/.vitepress/config.ts +36 -34
  14. package/docs/.vitepress/sidebar-generator.ts +89 -38
  15. package/docs/cli.md +29 -82
  16. package/docs/code-of-conduct.md +7 -1
  17. package/docs/configuration.md +103 -117
  18. package/docs/contributing.md +6 -2
  19. package/docs/deployment.md +10 -5
  20. package/docs/development.md +8 -5
  21. package/docs/getting-started.md +76 -45
  22. package/docs/index.md +1 -1
  23. package/docs/public/android-chrome-192x192.png +0 -0
  24. package/docs/public/android-chrome-512x512.png +0 -0
  25. package/docs/public/apple-touch-icon.png +0 -0
  26. package/docs/public/favicon-16x16.png +0 -0
  27. package/docs/public/favicon-32x32.png +0 -0
  28. package/docs/public/favicon.ico +0 -0
  29. package/docs/site.webmanifest +1 -0
  30. package/docs/troubleshooting.md +12 -1
  31. package/examples/main.ts +7 -10
  32. package/examples/sample-project/script.js +1 -1
  33. package/jest.config.ts +8 -13
  34. package/nodemon.json +5 -10
  35. package/package.json +2 -5
  36. package/src/cli/cli-entry.ts +2 -2
  37. package/src/cli/cli.ts +21 -16
  38. package/src/cli/options.ts +127 -113
  39. package/src/core/bundler.ts +254 -221
  40. package/src/core/extractor.ts +639 -520
  41. package/src/core/minifier.ts +173 -162
  42. package/src/core/packer.ts +141 -137
  43. package/src/core/parser.ts +74 -73
  44. package/src/core/web-fetcher.ts +270 -258
  45. package/src/index.ts +18 -17
  46. package/src/types.ts +9 -11
  47. package/src/utils/font.ts +12 -6
  48. package/src/utils/logger.ts +110 -105
  49. package/src/utils/meta.ts +75 -76
  50. package/src/utils/mime.ts +50 -50
  51. package/src/utils/slugify.ts +33 -34
  52. package/tests/unit/cli/cli-entry.test.ts +72 -70
  53. package/tests/unit/cli/cli.test.ts +314 -278
  54. package/tests/unit/cli/options.test.ts +294 -301
  55. package/tests/unit/core/bundler.test.ts +426 -329
  56. package/tests/unit/core/extractor.test.ts +828 -380
  57. package/tests/unit/core/minifier.test.ts +374 -274
  58. package/tests/unit/core/packer.test.ts +298 -264
  59. package/tests/unit/core/parser.test.ts +538 -150
  60. package/tests/unit/core/web-fetcher.test.ts +389 -359
  61. package/tests/unit/index.test.ts +238 -197
  62. package/tests/unit/utils/font.test.ts +26 -21
  63. package/tests/unit/utils/logger.test.ts +267 -260
  64. package/tests/unit/utils/meta.test.ts +29 -28
  65. package/tests/unit/utils/mime.test.ts +73 -74
  66. package/tests/unit/utils/slugify.test.ts +14 -12
  67. package/tsconfig.build.json +9 -10
  68. package/tsconfig.jest.json +2 -1
  69. package/tsconfig.json +2 -2
  70. package/tsup.config.ts +8 -8
  71. package/typedoc.json +5 -9
  72. package/docs/demo.md +0 -46
  73. /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
  74. /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
- describe('guessMimeType()', () => {
13
- const defaultResult = { mime: 'application/octet-stream', assetType: 'other' as Asset['type'] };
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
- // Test cases: [input, expectedMime, expectedAssetType]
16
- const testCases: [string, string, Asset['type']][] = [
17
- // CSS
18
- ['style.css', 'text/css', 'css'],
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
- // JS
25
- ['script.js', 'application/javascript', 'js'],
26
- ['script.mjs', 'application/javascript', 'js'],
27
- ['https://cdn.com/lib.js', 'application/javascript', 'js'],
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
- // Images
30
- ['logo.png', 'image/png', 'image'],
31
- ['photo.jpg', 'image/jpeg', 'image'],
32
- ['image.jpeg', 'image/jpeg', 'image'],
33
- ['anim.gif', 'image/gif', 'image'],
34
- ['icon.svg', 'image/svg+xml', 'image'],
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
- // Fonts
40
- ['font.woff', 'font/woff', 'font'],
41
- ['font.woff2', 'font/woff2', 'font'],
42
- ['font.ttf', 'font/ttf', 'font'],
43
- ['font.otf', 'font/otf', 'font'],
44
- ['font.eot', 'application/vnd.ms-fontobject', 'font'],
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
- // Audio/Video ('other')
47
- ['audio.mp3', 'audio/mpeg', 'other'],
48
- ['audio.ogg', 'audio/ogg', 'other'],
49
- ['audio.wav', 'audio/wav', 'other'],
50
- ['video.mp4', 'video/mp4', 'other'],
51
- ['video.webm', 'video/webm', 'other'],
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
- // Other ('other')
54
- ['data.json', 'application/json', 'other'],
55
- ['manifest.webmanifest', 'application/manifest+json', 'other'],
56
- ['document.xml', 'application/xml', 'other'],
57
- ['page.html', 'text/html', 'other'],
58
- ['notes.txt', 'text/plain', 'other'],
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
- // Edge cases
61
- ['file_without_extension', defaultResult.mime, defaultResult.assetType],
62
- ['file.unknown', defaultResult.mime, defaultResult.assetType],
63
- ['.', defaultResult.mime, defaultResult.assetType], // Just a dot
64
- ['image.', defaultResult.mime, defaultResult.assetType], // Dot at the end
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
- // it.each(testCases)('should return correct type for "%s"', (input, expectedMime, expectedAssetType) => {
71
- // const result = guessMimeType(input);
72
- // expect(result.mime).toBe(expectedMime);
73
- // expect(result.assetType).toBe(expectedAssetType);
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
- // Test deprecated getFontMimeType (should just delegate)
85
- describe('getFontMimeType() [Deprecated]', () => {
86
- it('should return correct font MIME type', () => {
87
- expect(getFontMimeType('font.woff2')).toBe('font/woff2');
88
- expect(getFontMimeType('font.ttf')).toBe('font/ttf');
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
- it('should delegate to guessMimeType and return default for non-fonts', () => {
92
- expect(getFontMimeType('style.css')).toBe('text/css'); // Returns CSS mime
93
- expect(getFontMimeType('unknown.ext')).toBe('application/octet-stream'); // Returns default
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
- it('should handle typical URLs', () => {
6
- // --- Expectations matching the corrected slugify logic ---
7
- expect(slugify('https://site.com/path/page.html')).toBe('path-page');
8
- expect(slugify('products/item-1.html')).toBe('products-item-1');
9
- expect(slugify(' search?q=test page 2 ')).toBe('search-q-test-page-2');
10
- expect(slugify('/path/with/slashes/')).toBe('path-with-slashes');
11
- // ----------------------------------------------------------
12
- expect(slugify('')).toBe('index');
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
@@ -1,11 +1,10 @@
1
1
  {
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
- }
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
+ }
@@ -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", // ✅ ESM output
5
- "moduleResolution": "Bundler", // ✅ lets TS handle extensions properly
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'], // ✅ Required for CLI to work with npx
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, // Wipe dist/cli clean on each build
32
- dts: false, // No types for CLI
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', // ✅ Required for CLI shebang
35
+ js: '#!/usr/bin/env node', // ✅ Required for CLI shebang
36
36
  },
37
37
  outExtension() {
38
38
  return {
39
- js: '.cjs', // ✅ Required: prevents ESM misinterpretation
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'], // ✅ Modern ESM output for consumers
57
+ format: ['esm'], // ✅ Modern ESM output for consumers
58
58
  platform: 'node',
59
59
  target: 'node18',
60
60
  splitting: false,
61
- clean: false, // Don't wipe CLI build!
62
- dts: true, // ✅ Generate TypeScript declarations
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