portapack 0.3.1 → 0.3.3

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/.releaserc.js +25 -27
  3. package/CHANGELOG.md +14 -22
  4. package/LICENSE.md +21 -0
  5. package/README.md +22 -53
  6. package/commitlint.config.js +30 -34
  7. package/dist/cli/cli-entry.cjs +183 -98
  8. package/dist/cli/cli-entry.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -3
  10. package/dist/index.js +178 -97
  11. package/dist/index.js.map +1 -1
  12. package/docs/.vitepress/config.ts +38 -33
  13. package/docs/.vitepress/sidebar-generator.ts +89 -38
  14. package/docs/architecture.md +186 -0
  15. package/docs/cli.md +23 -23
  16. package/docs/code-of-conduct.md +7 -1
  17. package/docs/configuration.md +12 -11
  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 +13 -13
  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/roadmap.md +233 -0
  30. package/docs/site.webmanifest +1 -0
  31. package/docs/troubleshooting.md +12 -1
  32. package/examples/main.ts +5 -30
  33. package/examples/sample-project/script.js +1 -1
  34. package/jest.config.ts +8 -13
  35. package/nodemon.json +5 -10
  36. package/package.json +2 -5
  37. package/src/cli/cli-entry.ts +2 -2
  38. package/src/cli/cli.ts +21 -16
  39. package/src/cli/options.ts +127 -113
  40. package/src/core/bundler.ts +253 -222
  41. package/src/core/extractor.ts +632 -565
  42. package/src/core/minifier.ts +173 -162
  43. package/src/core/packer.ts +141 -137
  44. package/src/core/parser.ts +74 -73
  45. package/src/core/web-fetcher.ts +270 -258
  46. package/src/index.ts +18 -17
  47. package/src/types.ts +9 -11
  48. package/src/utils/font.ts +12 -6
  49. package/src/utils/logger.ts +110 -105
  50. package/src/utils/meta.ts +75 -76
  51. package/src/utils/mime.ts +50 -50
  52. package/src/utils/slugify.ts +33 -34
  53. package/tests/unit/cli/cli-entry.test.ts +72 -70
  54. package/tests/unit/cli/cli.test.ts +314 -278
  55. package/tests/unit/cli/options.test.ts +294 -301
  56. package/tests/unit/core/bundler.test.ts +426 -329
  57. package/tests/unit/core/extractor.test.ts +793 -549
  58. package/tests/unit/core/minifier.test.ts +374 -274
  59. package/tests/unit/core/packer.test.ts +298 -264
  60. package/tests/unit/core/parser.test.ts +538 -150
  61. package/tests/unit/core/web-fetcher.test.ts +389 -359
  62. package/tests/unit/index.test.ts +238 -197
  63. package/tests/unit/utils/font.test.ts +26 -21
  64. package/tests/unit/utils/logger.test.ts +267 -260
  65. package/tests/unit/utils/meta.test.ts +29 -28
  66. package/tests/unit/utils/mime.test.ts +73 -74
  67. package/tests/unit/utils/slugify.test.ts +14 -12
  68. package/tsconfig.build.json +9 -10
  69. package/tsconfig.jest.json +1 -1
  70. package/tsconfig.json +2 -2
  71. package/tsup.config.ts +8 -9
  72. package/typedoc.json +5 -9
  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
+ });