portapack 0.2.1
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 +9 -0
- package/.github/workflows/ci.yml +73 -0
- package/.github/workflows/deploy-pages.yml +56 -0
- package/.prettierrc +9 -0
- package/.releaserc.js +29 -0
- package/CHANGELOG.md +21 -0
- package/README.md +288 -0
- package/commitlint.config.js +36 -0
- package/dist/cli/cli-entry.js +1694 -0
- package/dist/cli/cli-entry.js.map +1 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +1405 -0
- package/dist/index.js.map +1 -0
- package/docs/.vitepress/config.ts +89 -0
- package/docs/.vitepress/sidebar-generator.ts +73 -0
- package/docs/cli.md +117 -0
- package/docs/code-of-conduct.md +65 -0
- package/docs/configuration.md +151 -0
- package/docs/contributing.md +107 -0
- package/docs/demo.md +46 -0
- package/docs/deployment.md +132 -0
- package/docs/development.md +168 -0
- package/docs/getting-started.md +106 -0
- package/docs/index.md +40 -0
- package/docs/portapack-transparent.png +0 -0
- package/docs/portapack.jpg +0 -0
- package/docs/troubleshooting.md +107 -0
- package/examples/main.ts +118 -0
- package/examples/sample-project/index.html +12 -0
- package/examples/sample-project/logo.png +1 -0
- package/examples/sample-project/script.js +1 -0
- package/examples/sample-project/styles.css +1 -0
- package/jest.config.ts +124 -0
- package/jest.setup.cjs +211 -0
- package/nodemon.json +11 -0
- package/output.html +1 -0
- package/package.json +161 -0
- package/site-packed.html +1 -0
- package/src/cli/cli-entry.ts +28 -0
- package/src/cli/cli.ts +139 -0
- package/src/cli/options.ts +151 -0
- package/src/core/bundler.ts +201 -0
- package/src/core/extractor.ts +618 -0
- package/src/core/minifier.ts +233 -0
- package/src/core/packer.ts +191 -0
- package/src/core/parser.ts +115 -0
- package/src/core/web-fetcher.ts +292 -0
- package/src/index.ts +262 -0
- package/src/types.ts +163 -0
- package/src/utils/font.ts +41 -0
- package/src/utils/logger.ts +139 -0
- package/src/utils/meta.ts +100 -0
- package/src/utils/mime.ts +90 -0
- package/src/utils/slugify.ts +70 -0
- package/test-output.html +0 -0
- package/tests/__fixtures__/sample-project/index.html +5 -0
- package/tests/unit/cli/cli-entry.test.ts +104 -0
- package/tests/unit/cli/cli.test.ts +230 -0
- package/tests/unit/cli/options.test.ts +316 -0
- package/tests/unit/core/bundler.test.ts +287 -0
- package/tests/unit/core/extractor.test.ts +1129 -0
- package/tests/unit/core/minifier.test.ts +414 -0
- package/tests/unit/core/packer.test.ts +193 -0
- package/tests/unit/core/parser.test.ts +540 -0
- package/tests/unit/core/web-fetcher.test.ts +374 -0
- package/tests/unit/index.test.ts +339 -0
- package/tests/unit/utils/font.test.ts +81 -0
- package/tests/unit/utils/logger.test.ts +275 -0
- package/tests/unit/utils/meta.test.ts +70 -0
- package/tests/unit/utils/mime.test.ts +96 -0
- package/tests/unit/utils/slugify.test.ts +71 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.jest.json +17 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +71 -0
- package/typedoc.json +28 -0
package/src/types.ts
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
/**
|
2
|
+
* @file types.ts
|
3
|
+
*
|
4
|
+
* @description
|
5
|
+
* Centralized types used across the PortaPack CLI, API, core modules, and bundling pipeline.
|
6
|
+
*
|
7
|
+
* This file defines:
|
8
|
+
* - Asset structure
|
9
|
+
* - HTML parsing result
|
10
|
+
* - Bundling options and metadata
|
11
|
+
* - Page structures for recursive bundling
|
12
|
+
* - CLI execution output format
|
13
|
+
*/
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Represents a single discovered, downloaded, or embedded asset.
|
17
|
+
* This includes JS, CSS, images, fonts, etc.
|
18
|
+
*/
|
19
|
+
export interface Asset {
|
20
|
+
type: 'css' | 'js' | 'image' | 'font' | 'video' | 'audio' | 'other'; // Add video and audio
|
21
|
+
|
22
|
+
/** The resolved or original URL of the asset */
|
23
|
+
url: string;
|
24
|
+
|
25
|
+
/** Inlined or fetched content */
|
26
|
+
content?: string; // Content is optional as it might not be embedded
|
27
|
+
|
28
|
+
/** Font-specific metadata for font-face usage */
|
29
|
+
fontMeta?: {
|
30
|
+
familyName: string;
|
31
|
+
weight?: number;
|
32
|
+
style?: 'normal' | 'italic' | 'oblique';
|
33
|
+
format?: string;
|
34
|
+
};
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Represents raw HTML and any linked/discovered assets.
|
39
|
+
* Result of the parsing stage.
|
40
|
+
*/
|
41
|
+
export interface ParsedHTML {
|
42
|
+
htmlContent: string;
|
43
|
+
assets: Asset[]; // List of assets found in the HTML
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* Represents a single page crawled during recursive bundling.
|
48
|
+
* Used as input for the multi-page bundler.
|
49
|
+
*/
|
50
|
+
export interface PageEntry {
|
51
|
+
/** Full resolved URL of the crawled page */
|
52
|
+
url: string;
|
53
|
+
|
54
|
+
/** Raw HTML content of the crawled page */
|
55
|
+
html: string;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Configuration options provided by the user via CLI or API call.
|
60
|
+
* Controls various aspects of the bundling process.
|
61
|
+
*/
|
62
|
+
export interface BundleOptions {
|
63
|
+
/** Embed all discovered assets as data URIs (default: true) */
|
64
|
+
embedAssets?: boolean;
|
65
|
+
|
66
|
+
/** Enable HTML minification using html-minifier-terser (default: true) */
|
67
|
+
minifyHtml?: boolean;
|
68
|
+
|
69
|
+
/** Enable CSS minification using clean-css (default: true) */
|
70
|
+
minifyCss?: boolean;
|
71
|
+
|
72
|
+
/** Enable JavaScript minification using terser (default: true) */
|
73
|
+
minifyJs?: boolean;
|
74
|
+
|
75
|
+
/** Base URL for resolving relative links, especially for remote fetches or complex local structures */
|
76
|
+
baseUrl?: string;
|
77
|
+
|
78
|
+
/** Enable verbose logging during CLI execution */
|
79
|
+
verbose?: boolean;
|
80
|
+
|
81
|
+
/** Skip writing output file to disk (CLI dry-run mode) */
|
82
|
+
dryRun?: boolean;
|
83
|
+
|
84
|
+
/** Enable recursive crawling. If a number, specifies max depth. If true, uses default depth. */
|
85
|
+
recursive?: number | boolean;
|
86
|
+
|
87
|
+
/** Optional output file path override (CLI uses this) */
|
88
|
+
output?: string;
|
89
|
+
|
90
|
+
/** Log level for the internal logger */
|
91
|
+
logLevel?: LogLevel;
|
92
|
+
}
|
93
|
+
|
94
|
+
// --- LogLevel Enum ---
|
95
|
+
// Defines available log levels as a numeric enum for comparisons.
|
96
|
+
export enum LogLevel {
|
97
|
+
NONE = 0, // No logging (equivalent to 'silent')
|
98
|
+
ERROR = 1, // Only errors
|
99
|
+
WARN = 2, // Errors and warnings
|
100
|
+
INFO = 3, // Errors, warnings, and info (Default)
|
101
|
+
DEBUG = 4 // All messages (Verbose)
|
102
|
+
}
|
103
|
+
|
104
|
+
// --- String Literal Type for LogLevel Names (Optional, useful for CLI parsing) ---
|
105
|
+
export type LogLevelName = 'debug' | 'info' | 'warn' | 'error' | 'silent' | 'none';
|
106
|
+
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Summary statistics and metadata returned after the packing/bundling process completes.
|
110
|
+
*/
|
111
|
+
export interface BundleMetadata {
|
112
|
+
/** Source HTML file path or URL */
|
113
|
+
input: string;
|
114
|
+
|
115
|
+
/** Total number of unique assets discovered (CSS, JS, images, fonts etc.) */
|
116
|
+
assetCount: number; // Kept as required - should always be calculated or defaulted (e.g., to 0)
|
117
|
+
|
118
|
+
/** Final output HTML size in bytes */
|
119
|
+
outputSize: number;
|
120
|
+
|
121
|
+
/** Elapsed build time in milliseconds */
|
122
|
+
buildTimeMs: number;
|
123
|
+
|
124
|
+
/** If recursive bundling was performed, the number of pages successfully crawled and included */
|
125
|
+
pagesBundled?: number; // Optional, only relevant for recursive mode
|
126
|
+
|
127
|
+
/** Any non-critical errors or warnings encountered during bundling (e.g., asset fetch failure) */
|
128
|
+
errors?: string[]; // Optional array of error/warning messages
|
129
|
+
}
|
130
|
+
|
131
|
+
/**
|
132
|
+
* Standard result object returned from the main public API functions.
|
133
|
+
*/
|
134
|
+
export interface BuildResult {
|
135
|
+
/** The final generated HTML string */
|
136
|
+
html: string;
|
137
|
+
/** Metadata summarizing the build process */
|
138
|
+
metadata: BundleMetadata;
|
139
|
+
}
|
140
|
+
|
141
|
+
|
142
|
+
/** CLI-specific options extending BundleOptions. */
|
143
|
+
export interface CLIOptions extends BundleOptions {
|
144
|
+
/** Input file or URL (positional). */
|
145
|
+
input?: string;
|
146
|
+
/** Max depth for recursive crawling (numeric alias for recursive). */
|
147
|
+
maxDepth?: number; // Used by commander, then merged into 'recursive'
|
148
|
+
minify?: boolean; // Minify assets (defaults to true)
|
149
|
+
}
|
150
|
+
|
151
|
+
/**
|
152
|
+
* Result object specifically for the CLI runner, capturing output streams and exit code.
|
153
|
+
*/
|
154
|
+
export interface CLIResult {
|
155
|
+
/** Captured content written to stdout */
|
156
|
+
stdout?: string;
|
157
|
+
|
158
|
+
/** Captured content written to stderr */
|
159
|
+
stderr?: string;
|
160
|
+
|
161
|
+
/** Final exit code intended for the process (0 for success, non-zero for errors) */
|
162
|
+
exitCode: number;
|
163
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
/**
|
2
|
+
* utils/font.ts
|
3
|
+
*
|
4
|
+
* Utilities for detecting and encoding font files for embedding.
|
5
|
+
*/
|
6
|
+
|
7
|
+
import path from 'path';
|
8
|
+
import fs from 'fs/promises';
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Returns the correct MIME type for a given font file.
|
12
|
+
*
|
13
|
+
* @param fontUrl - The path or URL of the font file
|
14
|
+
*/
|
15
|
+
export function getFontMimeType(fontUrl: string): string {
|
16
|
+
const ext = path.extname(fontUrl).toLowerCase().replace('.', '');
|
17
|
+
|
18
|
+
switch (ext) {
|
19
|
+
case 'woff': return 'font/woff';
|
20
|
+
case 'woff2': return 'font/woff2';
|
21
|
+
case 'ttf': return 'font/ttf';
|
22
|
+
case 'otf': return 'font/otf';
|
23
|
+
case 'eot': return 'application/vnd.ms-fontobject';
|
24
|
+
default: return 'application/octet-stream';
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Reads a font file and encodes it as a base64 data URI.
|
30
|
+
*
|
31
|
+
* NOTE: Not currently used in the pipeline, but useful for testing and future features.
|
32
|
+
*
|
33
|
+
* @param fontPath - Absolute or relative path to font
|
34
|
+
* @returns Full `data:` URI as a string
|
35
|
+
*/
|
36
|
+
export async function encodeFontToDataURI(fontPath: string): Promise<string> {
|
37
|
+
const mime = getFontMimeType(fontPath);
|
38
|
+
const buffer = await fs.readFile(fontPath);
|
39
|
+
const base64 = buffer.toString('base64');
|
40
|
+
return `data:${mime};base64,${base64}`;
|
41
|
+
}
|
@@ -0,0 +1,139 @@
|
|
1
|
+
/**
|
2
|
+
* @file src/utils/logger.ts
|
3
|
+
* @description Provides a standardized logging utility with configurable levels (based on an enum)
|
4
|
+
* to control output verbosity throughout the application (core, API, CLI).
|
5
|
+
*/
|
6
|
+
|
7
|
+
// FIX: Use a regular import for the enum, not 'import type'
|
8
|
+
import { LogLevel } from '../types';
|
9
|
+
// Assuming LogLevel enum is defined and exported in '../types' like:
|
10
|
+
// export enum LogLevel { NONE = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4 }
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Optional configuration for creating a Logger instance.
|
14
|
+
* (Note: Currently constructor only accepts LogLevel directly)
|
15
|
+
*/
|
16
|
+
export interface LoggerOptions {
|
17
|
+
level?: LogLevel;
|
18
|
+
}
|
19
|
+
|
20
|
+
/**
|
21
|
+
* A simple logger class that allows filtering messages based on severity levels.
|
22
|
+
* Uses standard console methods (debug, info, warn, error) for output.
|
23
|
+
*/
|
24
|
+
export class Logger {
|
25
|
+
/** The current minimum log level required for a message to be output. */
|
26
|
+
public level: LogLevel;
|
27
|
+
|
28
|
+
/**
|
29
|
+
* Creates a new Logger instance.
|
30
|
+
* Defaults to LogLevel.INFO if no level is provided.
|
31
|
+
*
|
32
|
+
* @param {LogLevel} [level=LogLevel.INFO] - The initial log level for this logger instance.
|
33
|
+
* Must be one of the values from the LogLevel enum.
|
34
|
+
*/
|
35
|
+
constructor(level: LogLevel = LogLevel.INFO) { // Defaulting to INFO level using the enum value
|
36
|
+
// Ensure a valid LogLevel enum member is provided or default correctly
|
37
|
+
this.level = (level !== undefined && LogLevel[level] !== undefined)
|
38
|
+
? level
|
39
|
+
: LogLevel.INFO; // Use the enum value for default
|
40
|
+
}
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Updates the logger's current level. Messages below this level will be suppressed.
|
44
|
+
*
|
45
|
+
* @param {LogLevel} level - The new log level to set. Must be a LogLevel enum member.
|
46
|
+
*/
|
47
|
+
setLevel(level: LogLevel): void {
|
48
|
+
this.level = level;
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Logs a debug message if the current log level is DEBUG or higher.
|
53
|
+
*
|
54
|
+
* @param {string} message - The debug message string.
|
55
|
+
*/
|
56
|
+
debug(message: string): void {
|
57
|
+
// Use enum member for comparison
|
58
|
+
if (this.level >= LogLevel.DEBUG) {
|
59
|
+
console.debug(`[DEBUG] ${message}`);
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
/**
|
64
|
+
* Logs an informational message if the current log level is INFO or higher.
|
65
|
+
*
|
66
|
+
* @param {string} message - The informational message string.
|
67
|
+
*/
|
68
|
+
info(message: string): void {
|
69
|
+
// Use enum member for comparison
|
70
|
+
if (this.level >= LogLevel.INFO) {
|
71
|
+
console.info(`[INFO] ${message}`);
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Logs a warning message if the current log level is WARN or higher.
|
77
|
+
*
|
78
|
+
* @param {string} message - The warning message string.
|
79
|
+
*/
|
80
|
+
warn(message: string): void {
|
81
|
+
// Use enum member for comparison
|
82
|
+
if (this.level >= LogLevel.WARN) {
|
83
|
+
console.warn(`[WARN] ${message}`);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Logs an error message if the current log level is ERROR or higher.
|
89
|
+
*
|
90
|
+
* @param {string} message - The error message string.
|
91
|
+
*/
|
92
|
+
error(message: string): void {
|
93
|
+
// Use enum member for comparison
|
94
|
+
if (this.level >= LogLevel.ERROR) {
|
95
|
+
console.error(`[ERROR] ${message}`);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Static factory method to create a Logger instance based on a simple boolean `verbose` flag.
|
101
|
+
*
|
102
|
+
* @static
|
103
|
+
* @param {{ verbose?: boolean }} [options={}] - An object potentially containing a `verbose` flag.
|
104
|
+
* @returns {Logger} A new Logger instance set to LogLevel.DEBUG if options.verbose is true,
|
105
|
+
* otherwise set to LogLevel.INFO.
|
106
|
+
*/
|
107
|
+
static fromVerboseFlag(options: { verbose?: boolean } = {}): Logger {
|
108
|
+
// Use enum members for assignment
|
109
|
+
return new Logger(options.verbose ? LogLevel.DEBUG : LogLevel.INFO);
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Static factory method to create a Logger instance based on a LogLevel string name.
|
114
|
+
* Useful for creating a logger from config files or environments variables.
|
115
|
+
*
|
116
|
+
* @static
|
117
|
+
* @param {string | undefined} levelName - The name of the log level (e.g., 'debug', 'info', 'warn', 'error', 'silent'/'none'). Case-insensitive.
|
118
|
+
* @param {LogLevel} [defaultLevel=LogLevel.INFO] - The level to use if levelName is invalid or undefined.
|
119
|
+
* @returns {Logger} A new Logger instance set to the corresponding LogLevel.
|
120
|
+
*/
|
121
|
+
static fromLevelName(levelName?: string, defaultLevel: LogLevel = LogLevel.INFO): Logger {
|
122
|
+
if (!levelName) {
|
123
|
+
return new Logger(defaultLevel);
|
124
|
+
}
|
125
|
+
switch (levelName.toLowerCase()) {
|
126
|
+
// Return enum members
|
127
|
+
case 'debug': return new Logger(LogLevel.DEBUG);
|
128
|
+
case 'info': return new Logger(LogLevel.INFO);
|
129
|
+
case 'warn': return new Logger(LogLevel.WARN);
|
130
|
+
case 'error': return new Logger(LogLevel.ERROR);
|
131
|
+
case 'silent':
|
132
|
+
case 'none': return new Logger(LogLevel.NONE);
|
133
|
+
default:
|
134
|
+
// Use console.warn directly here as logger might not be ready
|
135
|
+
console.warn(`[Logger] Invalid log level name "${levelName}". Defaulting to ${LogLevel[defaultLevel]}.`);
|
136
|
+
return new Logger(defaultLevel);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
@@ -0,0 +1,100 @@
|
|
1
|
+
/**
|
2
|
+
* @file src/utils/meta.ts
|
3
|
+
* @description Utility class for tracking bundle statistics like size, time,
|
4
|
+
* asset counts, page counts, and errors during the build process.
|
5
|
+
* Used by both CLI and API to return metadata consistently.
|
6
|
+
*/
|
7
|
+
|
8
|
+
import type { BundleMetadata } from '../types'; // Assuming types are in ../types
|
9
|
+
|
10
|
+
/**
|
11
|
+
* Tracks build performance (timing, output size) and collects metadata
|
12
|
+
* (asset counts, page counts, errors) during the HTML bundling process.
|
13
|
+
*/
|
14
|
+
export class BuildTimer {
|
15
|
+
private startTime: number;
|
16
|
+
private input: string;
|
17
|
+
private pagesBundled?: number; // Tracks pages for recursive bundles
|
18
|
+
private assetCount: number = 0; // Tracks discovered/processed assets
|
19
|
+
private errors: string[] = []; // Collects warnings/errors
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Creates and starts a build timer session for a given input.
|
23
|
+
*
|
24
|
+
* @param {string} input - The source file path or URL being processed.
|
25
|
+
*/
|
26
|
+
constructor(input: string) {
|
27
|
+
this.startTime = Date.now();
|
28
|
+
this.input = input;
|
29
|
+
}
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Explicitly sets the number of assets discovered or processed.
|
33
|
+
* This might be called after asset extraction/minification.
|
34
|
+
*
|
35
|
+
* @param {number} count - The total number of assets.
|
36
|
+
*/
|
37
|
+
setAssetCount(count: number): void {
|
38
|
+
this.assetCount = count;
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Records a warning or error message encountered during the build.
|
43
|
+
* These are added to the final metadata.
|
44
|
+
*
|
45
|
+
* @param {string} message - The warning or error description.
|
46
|
+
*/
|
47
|
+
addError(message: string): void {
|
48
|
+
this.errors.push(message);
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Sets the number of pages bundled, typically used in multi-page
|
53
|
+
* or recursive bundling scenarios.
|
54
|
+
*
|
55
|
+
* @param {number} count - The number of HTML pages included in the bundle.
|
56
|
+
*/
|
57
|
+
setPageCount(count: number): void {
|
58
|
+
this.pagesBundled = count;
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Stops the timer, calculates final metrics, and returns the complete
|
63
|
+
* BundleMetadata object. Merges any explicitly provided metadata
|
64
|
+
* (like assetCount calculated elsewhere) with the timer's tracked data.
|
65
|
+
*
|
66
|
+
* @param {string} finalHtml - The final generated HTML string, used to calculate output size.
|
67
|
+
* @param {Partial<BundleMetadata>} [extra] - Optional object containing metadata fields
|
68
|
+
* (like assetCount or pre-calculated errors) that should override the timer's internal values.
|
69
|
+
* @returns {BundleMetadata} The finalized metadata object for the build process.
|
70
|
+
*/
|
71
|
+
finish(html: string, extra?: Partial<BundleMetadata>): BundleMetadata {
|
72
|
+
const buildTimeMs = Date.now() - this.startTime;
|
73
|
+
const outputSize = Buffer.byteLength(html || '', 'utf-8');
|
74
|
+
|
75
|
+
// Combine internal errors with any errors passed in 'extra', avoiding duplicates
|
76
|
+
// FIX: Ensure extra.errors is treated as an empty array if undefined/null
|
77
|
+
const combinedErrors = Array.from(new Set([...this.errors, ...(extra?.errors ?? [])]));
|
78
|
+
|
79
|
+
const finalMetadata: BundleMetadata = {
|
80
|
+
input: this.input,
|
81
|
+
outputSize,
|
82
|
+
buildTimeMs,
|
83
|
+
assetCount: extra?.assetCount ?? this.assetCount,
|
84
|
+
pagesBundled: extra?.pagesBundled ?? this.pagesBundled,
|
85
|
+
// Assign the combined errors array
|
86
|
+
errors: combinedErrors,
|
87
|
+
};
|
88
|
+
|
89
|
+
// Clean up optional fields if they weren't set/provided or are empty
|
90
|
+
if (finalMetadata.pagesBundled === undefined) {
|
91
|
+
delete finalMetadata.pagesBundled;
|
92
|
+
}
|
93
|
+
// Delete errors only if the *combined* array is empty
|
94
|
+
if (finalMetadata.errors?.length === 0) {
|
95
|
+
delete finalMetadata.errors;
|
96
|
+
}
|
97
|
+
|
98
|
+
return finalMetadata;
|
99
|
+
}
|
100
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
/**
|
2
|
+
* @file src/utils/mime.ts
|
3
|
+
* @description Utilities for guessing MIME types and asset types from URLs/paths.
|
4
|
+
*/
|
5
|
+
|
6
|
+
import path from 'path';
|
7
|
+
import type { Asset } from '../types'; // Assuming types are in ../types
|
8
|
+
|
9
|
+
/**
|
10
|
+
* Maps common file extensions to their corresponding MIME types and general Asset types.
|
11
|
+
*/
|
12
|
+
const MIME_MAP: Record<string, { mime: string; assetType: Asset['type'] }> = {
|
13
|
+
// CSS
|
14
|
+
'.css': { mime: 'text/css', assetType: 'css' },
|
15
|
+
// JavaScript
|
16
|
+
'.js': { mime: 'application/javascript', assetType: 'js' },
|
17
|
+
'.mjs': { mime: 'application/javascript', assetType: 'js' },
|
18
|
+
// Images
|
19
|
+
'.png': { mime: 'image/png', assetType: 'image' },
|
20
|
+
'.jpg': { mime: 'image/jpeg', assetType: 'image' },
|
21
|
+
'.jpeg': { mime: 'image/jpeg', assetType: 'image' },
|
22
|
+
'.gif': { mime: 'image/gif', assetType: 'image' },
|
23
|
+
'.svg': { mime: 'image/svg+xml', assetType: 'image' },
|
24
|
+
'.webp': { mime: 'image/webp', assetType: 'image' },
|
25
|
+
'.ico': { mime: 'image/x-icon', assetType: 'image' },
|
26
|
+
'.avif': { mime: 'image/avif', assetType: 'image' },
|
27
|
+
// Fonts
|
28
|
+
'.woff': { mime: 'font/woff', assetType: 'font' },
|
29
|
+
'.woff2': { mime: 'font/woff2', assetType: 'font' },
|
30
|
+
'.ttf': { mime: 'font/ttf', assetType: 'font' },
|
31
|
+
'.otf': { mime: 'font/otf', assetType: 'font' },
|
32
|
+
'.eot': { mime: 'application/vnd.ms-fontobject', assetType: 'font' },
|
33
|
+
// Audio/Video (add more as needed)
|
34
|
+
'.mp3': { mime: 'audio/mpeg', assetType: 'other' },
|
35
|
+
'.ogg': { mime: 'audio/ogg', assetType: 'other' },
|
36
|
+
'.wav': { mime: 'audio/wav', assetType: 'other' },
|
37
|
+
'.mp4': { mime: 'video/mp4', assetType: 'other' },
|
38
|
+
'.webm': { mime: 'video/webm', assetType: 'other' },
|
39
|
+
// Other common web types
|
40
|
+
'.json': { mime: 'application/json', assetType: 'other' },
|
41
|
+
'.webmanifest': { mime: 'application/manifest+json', assetType: 'other' },
|
42
|
+
'.xml': { mime: 'application/xml', assetType: 'other' },
|
43
|
+
'.html': { mime: 'text/html', assetType: 'other' }, // Usually not needed as asset, but for completeness
|
44
|
+
'.txt': { mime: 'text/plain', assetType: 'other' },
|
45
|
+
};
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Default MIME type and Asset type for unknown file extensions.
|
49
|
+
*/
|
50
|
+
const DEFAULT_MIME_TYPE = {
|
51
|
+
mime: 'application/octet-stream',
|
52
|
+
assetType: 'other' as Asset['type'] // Explicit cast needed
|
53
|
+
};
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Guesses the MIME type and general Asset type based on a URL or file path's extension.
|
57
|
+
*
|
58
|
+
* @param {string} urlOrPath - The URL or file path string.
|
59
|
+
* @returns {{ mime: string; assetType: Asset['type'] }} An object containing the guessed MIME type
|
60
|
+
* and the corresponding Asset type (e.g., 'image', 'font', 'css', 'js', 'other'). Returns a default
|
61
|
+
* if the extension is unknown.
|
62
|
+
*/
|
63
|
+
export function guessMimeType(urlOrPath: string): { mime: string; assetType: Asset['type'] } {
|
64
|
+
if (!urlOrPath) {
|
65
|
+
return DEFAULT_MIME_TYPE;
|
66
|
+
}
|
67
|
+
// Extract the extension, handling potential query parameters or fragments
|
68
|
+
let ext = '';
|
69
|
+
try {
|
70
|
+
// Use URL parsing first to handle URLs correctly
|
71
|
+
const parsedUrl = new URL(urlOrPath);
|
72
|
+
ext = path.extname(parsedUrl.pathname).toLowerCase();
|
73
|
+
} catch {
|
74
|
+
// If it's not a valid URL, treat it as a path
|
75
|
+
ext = path.extname(urlOrPath).toLowerCase();
|
76
|
+
}
|
77
|
+
|
78
|
+
return MIME_MAP[ext] || DEFAULT_MIME_TYPE;
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Gets the appropriate font MIME type based on the file extension.
|
83
|
+
* Deprecated: Prefer `guessMimeType`.
|
84
|
+
* @deprecated Use guessMimeType instead.
|
85
|
+
* @param {string} fontUrl - The URL or path of the font file.
|
86
|
+
* @returns {string} The corresponding font MIME type or a default.
|
87
|
+
*/
|
88
|
+
export function getFontMimeType(fontUrl: string): string {
|
89
|
+
return guessMimeType(fontUrl).mime; // Delegate to the main function
|
90
|
+
}
|
@@ -0,0 +1,70 @@
|
|
1
|
+
/**
|
2
|
+
* @file src/utils/slugify.ts
|
3
|
+
* @description Converts any URL or string to a safe HTML slug usable in IDs, hashes, filenames, etc.
|
4
|
+
*/
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Converts a URL or path string into a clean slug suitable for use as an HTML ID or filename segment.
|
8
|
+
* - Handles relative and absolute URLs.
|
9
|
+
* - Removes common file extensions (.html, .htm, .php, etc.).
|
10
|
+
* - Removes URL fragments (#...).
|
11
|
+
* - Attempts to parse pathname and search parameters.
|
12
|
+
* - Replaces spaces, slashes, and other unsafe characters with hyphens.
|
13
|
+
* - Converts to lowercase.
|
14
|
+
* - Collapses and trims hyphens.
|
15
|
+
* - Returns 'index' for empty or invalid input.
|
16
|
+
*
|
17
|
+
* @param url - The raw URL or string to slugify.
|
18
|
+
* @returns A safe, lowercase slug string.
|
19
|
+
*/
|
20
|
+
export function slugify(url: string): string {
|
21
|
+
if (!url || typeof url !== 'string') return 'index';
|
22
|
+
|
23
|
+
let cleaned = url.trim();
|
24
|
+
let pathAndSearch = '';
|
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
|
+
}
|
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
|
+
}
|
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();
|
53
|
+
|
54
|
+
// Return 'index' if the process results in an empty string
|
55
|
+
return cleaned || 'index';
|
56
|
+
}
|
57
|
+
|
58
|
+
|
59
|
+
/**
|
60
|
+
* Converts a URL or path string into a clean slug suitable for use as an HTML ID.
|
61
|
+
* Note: This implementation might be very similar or identical to slugify depending on exact needs.
|
62
|
+
* This example uses the refined slugify logic. Consider consolidating if appropriate.
|
63
|
+
*
|
64
|
+
* @param rawUrl - The raw page URL or path.
|
65
|
+
* @returns A safe, lowercase slug string (e.g. "products-item-1", "search-q-test-page-2")
|
66
|
+
*/
|
67
|
+
export function sanitizeSlug(rawUrl: string): string {
|
68
|
+
// Re-use the improved slugify logic for consistency
|
69
|
+
return slugify(rawUrl);
|
70
|
+
}
|
package/test-output.html
ADDED
File without changes
|