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
@@ -18,44 +18,43 @@
18
18
  * @returns A safe, lowercase slug string.
19
19
  */
20
20
  export function slugify(url: string): string {
21
- if (!url || typeof url !== 'string') return 'index';
21
+ if (!url || typeof url !== 'string') return 'index';
22
22
 
23
- let cleaned = url.trim();
24
- let pathAndSearch = '';
23
+ let cleaned = url.trim();
24
+ let pathAndSearch = '';
25
25
 
26
- try {
27
- const urlObj = new URL(url, 'https://placeholder.base');
28
- pathAndSearch = (urlObj.pathname ?? '') + (urlObj.search ?? '');
29
- } catch {
30
- pathAndSearch = cleaned.split('#')[0]; // Remove fragment
31
- }
26
+ try {
27
+ const urlObj = new URL(url, 'https://placeholder.base');
28
+ pathAndSearch = (urlObj.pathname ?? '') + (urlObj.search ?? '');
29
+ } catch {
30
+ pathAndSearch = cleaned.split('#')[0]; // Remove fragment
31
+ }
32
32
 
33
- // Decode URI components AFTER parsing from URL to handle %20 etc.
34
- try {
35
- cleaned = decodeURIComponent(pathAndSearch);
36
- } catch (e) {
37
- cleaned = pathAndSearch; // Proceed if decoding fails
38
- }
33
+ // Decode URI components AFTER parsing from URL to handle %20 etc.
34
+ try {
35
+ cleaned = decodeURIComponent(pathAndSearch);
36
+ } catch (e) {
37
+ cleaned = pathAndSearch; // Proceed if decoding fails
38
+ }
39
39
 
40
- cleaned = cleaned
41
- // Remove common web extensions FIRST
42
- .replace(/\.(html?|php|aspx?|jsp)$/i, '')
43
- // Replace path separators and common separators/spaces with a hyphen
44
- .replace(/[\s/?=&\\]+/g, '-') // Target spaces, /, ?, =, &, \
45
- // Remove any remaining characters that are not alphanumeric, hyphen, underscore, or period
46
- .replace(/[^\w._-]+/g, '') // Allow word chars, '.', '_', '-'
47
- // Collapse consecutive hyphens
48
- .replace(/-+/g, '-')
49
- // Trim leading/trailing hyphens
50
- .replace(/^-+|-+$/g, '')
51
- // Convert to lowercase
52
- .toLowerCase();
40
+ cleaned = cleaned
41
+ // Remove common web extensions FIRST
42
+ .replace(/\.(html?|php|aspx?|jsp)$/i, '')
43
+ // Replace path separators and common separators/spaces with a hyphen
44
+ .replace(/[\s/?=&\\]+/g, '-') // Target spaces, /, ?, =, &, \
45
+ // Remove any remaining characters that are not alphanumeric, hyphen, underscore, or period
46
+ .replace(/[^\w._-]+/g, '') // Allow word chars, '.', '_', '-'
47
+ // Collapse consecutive hyphens
48
+ .replace(/-+/g, '-')
49
+ // Trim leading/trailing hyphens
50
+ .replace(/^-+|-+$/g, '')
51
+ // Convert to lowercase
52
+ .toLowerCase();
53
53
 
54
- // Return 'index' if the process results in an empty string
55
- return cleaned || 'index';
54
+ // Return 'index' if the process results in an empty string
55
+ return cleaned || 'index';
56
56
  }
57
57
 
58
-
59
58
  /**
60
59
  * Converts a URL or path string into a clean slug suitable for use as an HTML ID.
61
60
  * Note: This implementation might be very similar or identical to slugify depending on exact needs.
@@ -65,6 +64,6 @@ export function slugify(url: string): string {
65
64
  * @returns A safe, lowercase slug string (e.g. "products-item-1", "search-q-test-page-2")
66
65
  */
67
66
  export function sanitizeSlug(rawUrl: string): string {
68
- // Re-use the improved slugify logic for consistency
69
- return slugify(rawUrl);
70
- }
67
+ // Re-use the improved slugify logic for consistency
68
+ return slugify(rawUrl);
69
+ }
@@ -1,4 +1,3 @@
1
- // tests/unit/cli/cli-entry.test.ts
2
1
  import type { CLIResult } from '../../../src/types';
3
2
  import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
4
3
 
@@ -7,9 +6,9 @@ import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals
7
6
  const mockRunCliFn = jest.fn<() => Promise<CLIResult>>();
8
7
 
9
8
  jest.mock('../../../src/cli/cli', () => ({
10
- __esModule: true,
11
- runCli: mockRunCliFn,
12
- main: mockRunCliFn, // Mock both exports for safety
9
+ __esModule: true,
10
+ runCli: mockRunCliFn,
11
+ main: mockRunCliFn, // Mock both exports for safety
13
12
  }));
14
13
 
15
14
  // Spy on process methods IF startCLI interacts with them directly
@@ -25,69 +24,72 @@ let stderrSpy: jest.SpiedFunction<typeof process.stderr.write>;
25
24
  import { startCLI } from '../../../src/cli/cli-entry';
26
25
 
27
26
  describe('CLI Entry Point Function (startCLI)', () => {
28
- const originalArgv = [...process.argv]; // Clone original argv
29
-
30
- beforeEach(() => {
31
- jest.clearAllMocks();
32
-
33
- // Reset argv for each test (startCLI uses process.argv internally)
34
- process.argv = ['node', '/path/to/cli-entry.js', 'default-arg'];
35
-
36
- // Default mock implementation for the dependency (runCli)
37
- mockRunCliFn.mockResolvedValue({ exitCode: 0, stdout: 'Default Success', stderr: '' });
38
-
39
- // Spies (optional for testing startCLI directly, but good practice)
40
- exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
41
- stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
42
- stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
43
- });
44
-
45
- afterEach(() => {
46
- process.argv = originalArgv; // Restore original argv
47
- // jest.restoreAllMocks(); // Usually covered by clearAllMocks
48
- });
49
-
50
- it('should call the underlying runCli function with process.argv', async () => {
51
- const testArgs = ['node', 'cli-entry.js', 'input.file', '-o', 'output.file'];
52
- process.argv = testArgs; // Set specific argv for this test
53
-
54
- await startCLI(); // Execute the function exported from cli-entry
55
-
56
- expect(mockRunCliFn).toHaveBeenCalledTimes(1);
57
- // Verify runCli received the arguments startCLI got from process.argv
58
- expect(mockRunCliFn).toHaveBeenCalledWith(testArgs);
59
- // startCLI itself doesn't call process.exit or write, the surrounding block does
60
- expect(exitSpy).not.toHaveBeenCalled();
61
- expect(stdoutSpy).not.toHaveBeenCalled();
62
- expect(stderrSpy).not.toHaveBeenCalled();
63
- });
64
-
65
- it('should return the result object from runCli', async () => {
66
- const expectedResult: CLIResult = { exitCode: 2, stdout: 'Programmatic output', stderr: 'Some warning' };
67
- mockRunCliFn.mockResolvedValue(expectedResult); // Configure mock return
68
- process.argv = ['node', 'cli.js', 'input.html']; // Set argv for this call
69
-
70
- // Call startCLI and check its return value
71
- const result = await startCLI();
72
-
73
- expect(result).toEqual(expectedResult); // Verify return value
74
- expect(mockRunCliFn).toHaveBeenCalledTimes(1);
75
- expect(mockRunCliFn).toHaveBeenCalledWith(process.argv); // Check args passed
76
- expect(exitSpy).not.toHaveBeenCalled(); // startCLI doesn't exit
77
- });
78
-
79
- it('should return the rejected promise if runCli rejects', async () => {
80
- const testArgs = ['node', 'cli.js', 'crash'];
81
- process.argv = testArgs;
82
- const testError = new Error('Unhandled crash');
83
- mockRunCliFn.mockRejectedValue(testError); // Configure mock rejection
84
-
85
- // Expect startCLI itself to reject when runCli rejects
86
- await expect(startCLI()).rejects.toThrow(testError);
87
-
88
- expect(mockRunCliFn).toHaveBeenCalledTimes(1);
89
- expect(mockRunCliFn).toHaveBeenCalledWith(testArgs);
90
- expect(exitSpy).not.toHaveBeenCalled(); // No exit from startCLI
91
- });
92
-
93
- });
27
+ const originalArgv = [...process.argv]; // Clone original argv
28
+
29
+ beforeEach(() => {
30
+ jest.clearAllMocks();
31
+
32
+ // Reset argv for each test (startCLI uses process.argv internally)
33
+ process.argv = ['node', '/path/to/cli-entry.js', 'default-arg'];
34
+
35
+ // Default mock implementation for the dependency (runCli)
36
+ mockRunCliFn.mockResolvedValue({ exitCode: 0, stdout: 'Default Success', stderr: '' });
37
+
38
+ // Spies (optional for testing startCLI directly, but good practice)
39
+ exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never);
40
+ stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
41
+ stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => true);
42
+ });
43
+
44
+ afterEach(() => {
45
+ process.argv = originalArgv; // Restore original argv
46
+ // jest.restoreAllMocks(); // Usually covered by clearAllMocks
47
+ });
48
+
49
+ it('should call the underlying runCli function with process.argv', async () => {
50
+ const testArgs = ['node', 'cli-entry.js', 'input.file', '-o', 'output.file'];
51
+ process.argv = testArgs; // Set specific argv for this test
52
+
53
+ await startCLI(); // Execute the function exported from cli-entry
54
+
55
+ expect(mockRunCliFn).toHaveBeenCalledTimes(1);
56
+ // Verify runCli received the arguments startCLI got from process.argv
57
+ expect(mockRunCliFn).toHaveBeenCalledWith(testArgs);
58
+ // startCLI itself doesn't call process.exit or write, the surrounding block does
59
+ expect(exitSpy).not.toHaveBeenCalled();
60
+ expect(stdoutSpy).not.toHaveBeenCalled();
61
+ expect(stderrSpy).not.toHaveBeenCalled();
62
+ });
63
+
64
+ it('should return the result object from runCli', async () => {
65
+ const expectedResult: CLIResult = {
66
+ exitCode: 2,
67
+ stdout: 'Programmatic output',
68
+ stderr: 'Some warning',
69
+ };
70
+ mockRunCliFn.mockResolvedValue(expectedResult); // Configure mock return
71
+ process.argv = ['node', 'cli.js', 'input.html']; // Set argv for this call
72
+
73
+ // Call startCLI and check its return value
74
+ const result = await startCLI();
75
+
76
+ expect(result).toEqual(expectedResult); // Verify return value
77
+ expect(mockRunCliFn).toHaveBeenCalledTimes(1);
78
+ expect(mockRunCliFn).toHaveBeenCalledWith(process.argv); // Check args passed
79
+ expect(exitSpy).not.toHaveBeenCalled(); // startCLI doesn't exit
80
+ });
81
+
82
+ it('should return the rejected promise if runCli rejects', async () => {
83
+ const testArgs = ['node', 'cli.js', 'crash'];
84
+ process.argv = testArgs;
85
+ const testError = new Error('Unhandled crash');
86
+ mockRunCliFn.mockRejectedValue(testError); // Configure mock rejection
87
+
88
+ // Expect startCLI itself to reject when runCli rejects
89
+ await expect(startCLI()).rejects.toThrow(testError);
90
+
91
+ expect(mockRunCliFn).toHaveBeenCalledTimes(1);
92
+ expect(mockRunCliFn).toHaveBeenCalledWith(testArgs);
93
+ expect(exitSpy).not.toHaveBeenCalled(); // No exit from startCLI
94
+ });
95
+ });