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.
- package/.eslintrc.json +67 -8
- package/.releaserc.js +25 -27
- package/CHANGELOG.md +14 -22
- package/LICENSE.md +21 -0
- package/README.md +22 -53
- package/commitlint.config.js +30 -34
- package/dist/cli/cli-entry.cjs +183 -98
- package/dist/cli/cli-entry.cjs.map +1 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +178 -97
- package/dist/index.js.map +1 -1
- package/docs/.vitepress/config.ts +38 -33
- package/docs/.vitepress/sidebar-generator.ts +89 -38
- package/docs/architecture.md +186 -0
- package/docs/cli.md +23 -23
- package/docs/code-of-conduct.md +7 -1
- package/docs/configuration.md +12 -11
- package/docs/contributing.md +6 -2
- package/docs/deployment.md +10 -5
- package/docs/development.md +8 -5
- package/docs/getting-started.md +13 -13
- 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/roadmap.md +233 -0
- package/docs/site.webmanifest +1 -0
- package/docs/troubleshooting.md +12 -1
- package/examples/main.ts +5 -30
- 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 +253 -222
- package/src/core/extractor.ts +632 -565
- 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 +793 -549
- 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 +1 -1
- package/tsconfig.json +2 -2
- package/tsup.config.ts +8 -9
- package/typedoc.json +5 -9
- /package/docs/{portapack-transparent.png → public/portapack-transparent.png} +0 -0
- /package/docs/{portapack.jpg → public/portapack.jpg} +0 -0
package/src/utils/slugify.ts
CHANGED
@@ -18,44 +18,43 @@
|
|
18
18
|
* @returns A safe, lowercase slug string.
|
19
19
|
*/
|
20
20
|
export function slugify(url: string): string {
|
21
|
-
|
21
|
+
if (!url || typeof url !== 'string') return 'index';
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
let cleaned = url.trim();
|
24
|
+
let pathAndSearch = '';
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
69
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
+
});
|