@stati/core 1.14.0 → 1.15.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.
@@ -1,21 +1,26 @@
1
1
  /**
2
2
  * TypeScript compilation utilities using esbuild.
3
3
  * Provides functions for compiling TypeScript files and watching for changes.
4
+ * Supports multiple bundles with per-page targeting.
4
5
  * @module core/utils/typescript
5
6
  */
6
7
  import * as esbuild from 'esbuild';
7
8
  import * as path from 'node:path';
8
9
  import * as fs from 'node:fs/promises';
9
10
  import { pathExists } from './fs.utils.js';
10
- import { DEFAULT_TS_SRC_DIR, DEFAULT_TS_OUT_DIR, DEFAULT_TS_ENTRY_POINT, DEFAULT_TS_BUNDLE_NAME, DEFAULT_OUT_DIR, } from '../../constants.js';
11
+ import { validateUniqueBundleNames } from './bundle-matching.utils.js';
12
+ import { DEFAULT_TS_SRC_DIR, DEFAULT_TS_OUT_DIR, DEFAULT_BUNDLES, DEFAULT_OUT_DIR, } from '../../constants.js';
13
+ /**
14
+ * Resolves TypeScript config with defaults based on mode.
15
+ * @internal
16
+ */
11
17
  function resolveConfig(config, mode) {
12
18
  const isProduction = mode === 'production';
13
19
  return {
14
20
  enabled: config.enabled,
15
21
  srcDir: config.srcDir ?? DEFAULT_TS_SRC_DIR,
16
22
  outDir: config.outDir ?? DEFAULT_TS_OUT_DIR,
17
- entryPoint: config.entryPoint ?? DEFAULT_TS_ENTRY_POINT,
18
- bundleName: config.bundleName ?? DEFAULT_TS_BUNDLE_NAME,
23
+ bundles: config.bundles ?? [...DEFAULT_BUNDLES],
19
24
  // hash/minify: always false in dev (config ignored), configurable in prod (default true)
20
25
  hash: isProduction && (config.hash ?? true),
21
26
  minify: isProduction && (config.minify ?? true),
@@ -24,136 +29,196 @@ function resolveConfig(config, mode) {
24
29
  };
25
30
  }
26
31
  /**
27
- * Compile TypeScript files using esbuild.
28
- * Returns the generated bundle filename for template injection.
29
- *
30
- * @param options - Compilation options
31
- * @returns The compilation result with bundle filename
32
- *
33
- * @example
34
- * ```typescript
35
- * const result = await compileTypeScript({
36
- * projectRoot: process.cwd(),
37
- * config: { enabled: true },
38
- * mode: 'production',
39
- * logger: console,
40
- * });
41
- * console.log(result.bundleFilename); // 'bundle-a1b2c3d4.js'
42
- * ```
32
+ * Compile a single bundle using esbuild.
33
+ * @internal
43
34
  */
44
- export async function compileTypeScript(options) {
45
- const { projectRoot, config, mode, logger, outDir: globalOutDir } = options;
46
- const resolved = resolveConfig(config, mode);
47
- const entryPath = path.join(projectRoot, resolved.srcDir, resolved.entryPoint);
48
- // Output to configured build output directory (default: dist)
49
- const outDir = path.join(projectRoot, globalOutDir || DEFAULT_OUT_DIR, resolved.outDir);
35
+ async function compileSingleBundle(bundleConfig, resolvedConfig, projectRoot, globalOutDir, logger) {
36
+ const entryPath = path.join(projectRoot, resolvedConfig.srcDir, bundleConfig.entryPoint);
37
+ const outDir = path.join(projectRoot, globalOutDir, resolvedConfig.outDir);
50
38
  // Validate entry point exists
51
39
  if (!(await pathExists(entryPath))) {
52
- logger.warning(`TypeScript entry point not found: ${entryPath}`);
53
- logger.warning('Skipping TypeScript compilation.');
54
- return {};
40
+ logger.warning(`TypeScript entry point not found: ${entryPath} (bundle: ${bundleConfig.bundleName})`);
41
+ return null;
55
42
  }
56
- logger.info(''); // Add empty line before TypeScript compilation
57
- logger.info('Compiling TypeScript...');
58
43
  try {
59
44
  const result = await esbuild.build({
60
45
  entryPoints: [entryPath],
61
46
  bundle: true,
62
47
  outdir: outDir,
63
- entryNames: resolved.hash ? `${resolved.bundleName}-[hash]` : resolved.bundleName,
64
- minify: resolved.minify,
65
- sourcemap: resolved.sourceMaps,
48
+ entryNames: resolvedConfig.hash
49
+ ? `${bundleConfig.bundleName}-[hash]`
50
+ : bundleConfig.bundleName,
51
+ minify: resolvedConfig.minify,
52
+ sourcemap: resolvedConfig.sourceMaps,
66
53
  target: 'es2022',
67
54
  format: 'esm',
68
55
  platform: 'browser',
69
56
  logLevel: 'silent',
70
57
  metafile: true,
71
58
  });
72
- // Extract the generated filename for template injection
59
+ // Extract the generated filename for this bundle
73
60
  const outputs = Object.keys(result.metafile?.outputs ?? {});
74
61
  const bundleFile = outputs.find((f) => f.endsWith('.js'));
75
- const bundleFilename = bundleFile ? path.basename(bundleFile) : `${resolved.bundleName}.js`;
76
- logger.success(`TypeScript compiled: ${bundleFilename}`);
77
- return { bundleFilename };
62
+ const bundleFilename = bundleFile ? path.basename(bundleFile) : `${bundleConfig.bundleName}.js`;
63
+ // Construct the path relative to site root
64
+ const bundlePath = path.posix.join('/', resolvedConfig.outDir, bundleFilename);
65
+ return {
66
+ config: bundleConfig,
67
+ filename: bundleFilename,
68
+ path: bundlePath,
69
+ };
78
70
  }
79
71
  catch (error) {
80
72
  if (error instanceof Error) {
81
- logger.error(`TypeScript compilation failed: ${error.message}`);
73
+ logger.error(`Bundle '${bundleConfig.bundleName}' compilation failed: ${error.message}`);
82
74
  }
83
75
  throw error;
84
76
  }
85
77
  }
78
+ /**
79
+ * Compile TypeScript files using esbuild.
80
+ * Supports multiple bundles with parallel compilation.
81
+ *
82
+ * @param options - Compilation options
83
+ * @returns Array of compilation results for each bundle
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const results = await compileTypeScript({
88
+ * projectRoot: process.cwd(),
89
+ * config: {
90
+ * enabled: true,
91
+ * bundles: [
92
+ * { entryPoint: 'core.ts', bundleName: 'core' },
93
+ * { entryPoint: 'docs.ts', bundleName: 'docs', include: ['/docs/**'] }
94
+ * ]
95
+ * },
96
+ * mode: 'production',
97
+ * logger: console,
98
+ * });
99
+ * console.log(results[0].bundlePath); // '/_assets/core-a1b2c3d4.js'
100
+ * ```
101
+ */
102
+ export async function compileTypeScript(options) {
103
+ const { projectRoot, config, mode, logger, outDir: globalOutDir } = options;
104
+ const resolved = resolveConfig(config, mode);
105
+ const outputDir = globalOutDir || DEFAULT_OUT_DIR;
106
+ // Handle empty bundles array
107
+ if (resolved.bundles.length === 0) {
108
+ logger.warning('TypeScript is enabled but no bundles are configured. Add bundles to your stati.config.ts or disable TypeScript.');
109
+ return [];
110
+ }
111
+ // Validate unique bundle names early - fail fast with clear error message
112
+ validateUniqueBundleNames(resolved.bundles);
113
+ logger.info('');
114
+ logger.info(`Compiling TypeScript (${resolved.bundles.length} bundle${resolved.bundles.length > 1 ? 's' : ''})...`);
115
+ // Compile all bundles in parallel
116
+ const compilationPromises = resolved.bundles.map((bundleConfig) => compileSingleBundle(bundleConfig, resolved, projectRoot, outputDir, logger));
117
+ const results = await Promise.all(compilationPromises);
118
+ // Filter out null results (skipped bundles) and collect successful ones
119
+ const successfulResults = results.filter((r) => r !== null);
120
+ if (successfulResults.length > 0) {
121
+ const bundleNames = successfulResults.map((r) => r.filename).join(', ');
122
+ logger.success(`TypeScript compiled: ${bundleNames}`);
123
+ }
124
+ else if (resolved.bundles.length > 0) {
125
+ logger.warning('No TypeScript bundles were compiled (all entry points missing).');
126
+ }
127
+ return successfulResults;
128
+ }
86
129
  /**
87
130
  * Create an esbuild watch context for development.
88
131
  * Watches for TypeScript file changes and recompiles automatically.
132
+ * Supports multiple bundles with selective recompilation.
89
133
  *
90
134
  * @param options - Watch options including rebuild callback
91
- * @returns The esbuild build context for cleanup
135
+ * @returns Array of esbuild build contexts for cleanup
92
136
  *
93
137
  * @example
94
138
  * ```typescript
95
- * const watcher = await createTypeScriptWatcher({
139
+ * const watchers = await createTypeScriptWatcher({
96
140
  * projectRoot: process.cwd(),
97
141
  * config: { enabled: true },
98
- * mode: 'development',
99
142
  * logger: console,
100
- * onRebuild: () => console.log('Rebuilt!'),
143
+ * onRebuild: (results, compileTimeMs) => console.log(`Rebuilt ${results.length} bundles in ${compileTimeMs}ms`),
101
144
  * });
102
145
  *
103
146
  * // Later, cleanup:
104
- * await watcher.dispose();
147
+ * await Promise.all(watchers.map(w => w.dispose()));
105
148
  * ```
106
149
  */
107
150
  export async function createTypeScriptWatcher(options) {
108
- const { projectRoot, config, mode, logger, onRebuild, outDir: globalOutDir } = options;
109
- const resolved = resolveConfig(config, mode);
110
- const entryPath = path.join(projectRoot, resolved.srcDir, resolved.entryPoint);
111
- // Output to configured build output directory (default: dist)
112
- const outDir = path.join(projectRoot, globalOutDir || DEFAULT_OUT_DIR, resolved.outDir);
113
- // Validate entry point exists
114
- if (!(await pathExists(entryPath))) {
115
- logger.warning(`TypeScript entry point not found: ${entryPath}`);
116
- throw new Error(`Entry point not found: ${entryPath}`);
117
- }
118
- const context = await esbuild.context({
119
- entryPoints: [entryPath],
120
- bundle: true,
121
- outdir: outDir,
122
- entryNames: resolved.bundleName, // Stable filename in dev mode (no hash)
123
- minify: resolved.minify,
124
- sourcemap: resolved.sourceMaps,
125
- target: 'es2022',
126
- format: 'esm',
127
- platform: 'browser',
128
- logLevel: 'silent',
129
- plugins: [
130
- {
131
- name: 'stati-rebuild-notify',
132
- setup(build) {
133
- let startTime;
134
- build.onStart(() => {
135
- startTime = Date.now();
136
- });
137
- build.onEnd((result) => {
138
- const compileTime = Date.now() - startTime;
139
- if (result.errors.length > 0) {
140
- result.errors.forEach((err) => {
141
- logger.error(`TypeScript error: ${err.text}`);
142
- });
143
- }
144
- else {
145
- const bundlePath = `${globalOutDir || DEFAULT_OUT_DIR}/${resolved.outDir}/${resolved.bundleName}.js`;
146
- onRebuild(bundlePath, compileTime);
147
- }
148
- });
151
+ const { projectRoot, config, logger, onRebuild, outDir: globalOutDir } = options;
152
+ const resolved = resolveConfig(config, 'development');
153
+ const outputDir = globalOutDir || DEFAULT_OUT_DIR;
154
+ const outDir = path.join(projectRoot, outputDir, resolved.outDir);
155
+ const contexts = [];
156
+ const latestResults = new Map();
157
+ for (const bundleConfig of resolved.bundles) {
158
+ const entryPath = path.join(projectRoot, resolved.srcDir, bundleConfig.entryPoint);
159
+ // Validate entry point exists
160
+ if (!(await pathExists(entryPath))) {
161
+ logger.warning(`TypeScript entry point not found: ${entryPath} (bundle: ${bundleConfig.bundleName})`);
162
+ continue;
163
+ }
164
+ const context = await esbuild.context({
165
+ entryPoints: [entryPath],
166
+ bundle: true,
167
+ outdir: outDir,
168
+ entryNames: bundleConfig.bundleName, // Stable filename in dev mode (no hash)
169
+ minify: false, // Dev mode: never minify for fast rebuilds
170
+ sourcemap: true, // Dev mode: always enable for debugging
171
+ target: 'es2022',
172
+ format: 'esm',
173
+ platform: 'browser',
174
+ logLevel: 'silent',
175
+ metafile: true,
176
+ plugins: [
177
+ {
178
+ name: 'stati-rebuild-notify',
179
+ setup(build) {
180
+ let startTime;
181
+ build.onStart(() => {
182
+ startTime = Date.now();
183
+ });
184
+ build.onEnd((result) => {
185
+ const compileTime = Date.now() - startTime;
186
+ if (result.errors.length > 0) {
187
+ result.errors.forEach((err) => {
188
+ logger.error(`TypeScript error in '${bundleConfig.bundleName}': ${err.text}`);
189
+ });
190
+ }
191
+ else {
192
+ // Extract the generated filename
193
+ const outputs = Object.keys(result.metafile?.outputs ?? {});
194
+ const bundleFile = outputs.find((f) => f.endsWith('.js'));
195
+ const bundleFilename = bundleFile
196
+ ? path.basename(bundleFile)
197
+ : `${bundleConfig.bundleName}.js`;
198
+ const bundlePath = path.posix.join('/', resolved.outDir, bundleFilename);
199
+ const bundleResult = {
200
+ config: bundleConfig,
201
+ filename: bundleFilename,
202
+ path: bundlePath,
203
+ };
204
+ latestResults.set(bundleConfig.bundleName, bundleResult);
205
+ logger.info(`TypeScript '${bundleConfig.bundleName}' recompiled.`);
206
+ // Notify with all current results and compile time
207
+ onRebuild(Array.from(latestResults.values()), compileTime);
208
+ }
209
+ });
210
+ },
149
211
  },
150
- },
151
- ],
152
- });
153
- // Start watching
154
- await context.watch();
155
- logger.info(`Watching TypeScript files in ${resolved.srcDir}/`);
156
- return context;
212
+ ],
213
+ });
214
+ // Start watching
215
+ await context.watch();
216
+ contexts.push(context);
217
+ }
218
+ if (contexts.length > 0) {
219
+ logger.info(`Watching TypeScript files in ${resolved.srcDir}/ (${contexts.length} bundle${contexts.length > 1 ? 's' : ''})`);
220
+ }
221
+ return contexts;
157
222
  }
158
223
  /**
159
224
  * Compile stati.config.ts to a temporary JS file for import.
@@ -198,38 +263,86 @@ export async function cleanupCompiledConfig(compiledPath) {
198
263
  }
199
264
  }
200
265
  /**
201
- * Auto-inject TypeScript bundle script tag into HTML before </body>.
202
- * Similar to SEO auto-injection, this adds the script tag automatically
266
+ * Validates a bundle path for safety against XSS attacks.
267
+ * Only allows safe ASCII characters: alphanumeric, hyphens, underscores, dots, and forward slashes.
268
+ * Must start with / and end with .js, no encoded characters or unicode allowed.
269
+ *
270
+ * @param bundlePath - The bundle path to validate
271
+ * @returns true if the path is safe, false otherwise
272
+ *
273
+ * @internal
274
+ */
275
+ export function isValidBundlePath(bundlePath) {
276
+ // Reject non-string or empty paths
277
+ if (typeof bundlePath !== 'string' || bundlePath.length === 0) {
278
+ return false;
279
+ }
280
+ // Reject paths with null bytes (can bypass security checks)
281
+ if (bundlePath.includes('\0')) {
282
+ return false;
283
+ }
284
+ // Reject paths with control characters or non-ASCII characters
285
+ // Check that all characters are in printable ASCII range (32-126)
286
+ for (let i = 0; i < bundlePath.length; i++) {
287
+ const charCode = bundlePath.charCodeAt(i);
288
+ if (charCode < 32 || charCode > 126) {
289
+ return false;
290
+ }
291
+ }
292
+ // Reject URL-encoded characters (e.g., %20, %3C) and unicode escapes (e.g., \u003C)
293
+ if (/%[0-9a-fA-F]{2}/.test(bundlePath) || /\\u[0-9a-fA-F]{4}/.test(bundlePath)) {
294
+ return false;
295
+ }
296
+ // Reject HTML entities (e.g., &lt; &#60; &#x3C;)
297
+ if (/&#?[a-zA-Z0-9]+;/.test(bundlePath)) {
298
+ return false;
299
+ }
300
+ // Reject path traversal attempts
301
+ if (bundlePath.includes('..') || bundlePath.includes('//')) {
302
+ return false;
303
+ }
304
+ // Validate against strict safe pattern
305
+ const safePathPattern = /^\/[a-zA-Z0-9_\-./]+\.js$/;
306
+ return safePathPattern.test(bundlePath);
307
+ }
308
+ /**
309
+ * Checks if a bundle script tag is already present in the HTML.
310
+ *
311
+ * @param html - The HTML content to check
312
+ * @param bundlePath - The bundle path to look for
313
+ * @returns true if the bundle is already included, false otherwise
314
+ *
315
+ * @internal
316
+ */
317
+ function isBundleAlreadyIncluded(html, bundlePath) {
318
+ const escapedPath = bundlePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
319
+ const scriptTagPattern = new RegExp(`<script[^>]*\\ssrc=["']${escapedPath}["'][^>]*>`, 'i');
320
+ return scriptTagPattern.test(html);
321
+ }
322
+ /**
323
+ * Auto-inject TypeScript bundle script tags into HTML before </body>.
324
+ * Similar to SEO auto-injection, this adds script tags automatically
203
325
  * so users don't need to modify their templates.
326
+ * Supports multiple bundles - injects all matched bundles in order.
204
327
  *
205
328
  * @param html - Rendered HTML content
206
- * @param bundlePath - Path to the compiled bundle (e.g., '/_assets/bundle-a1b2c3d4.js')
207
- * @returns HTML with injected script tag
329
+ * @param bundlePaths - Array of paths to compiled bundles (e.g., ['/_assets/core.js', '/_assets/docs.js'])
330
+ * @returns HTML with injected script tags
208
331
  *
209
332
  * @example
210
333
  * ```typescript
211
334
  * const html = '<html><body>Content</body></html>';
212
- * const enhanced = autoInjectBundle(html, '/_assets/bundle.js');
213
- * // Returns: '<html><body>Content\n <script type="module" src="/_assets/bundle.js"></script>\n</body></html>'
335
+ * const enhanced = autoInjectBundles(html, ['/_assets/core.js', '/_assets/docs.js']);
336
+ * // Returns HTML with both script tags injected before </body>
214
337
  * ```
215
338
  */
216
- export function autoInjectBundle(html, bundlePath) {
217
- if (!bundlePath) {
218
- return html;
219
- }
220
- // Sanitize bundlePath to prevent XSS attacks
221
- // Only allow safe characters: alphanumeric, hyphens, underscores, dots, and forward slashes
222
- // Must start with / and end with .js
223
- const safePathPattern = /^\/[\w\-./]+\.js$/;
224
- if (!safePathPattern.test(bundlePath)) {
225
- // Invalid path format, skip injection to prevent potential XSS
339
+ export function autoInjectBundles(html, bundlePaths) {
340
+ if (!bundlePaths || bundlePaths.length === 0) {
226
341
  return html;
227
342
  }
228
- // Check if the bundle script tag is already included (avoid duplicate injection)
229
- // Must check for actual script tag, not just any occurrence of the path
230
- // (e.g., modulepreload links should not prevent script injection)
231
- const scriptTagPattern = new RegExp(`<script[^>]*\\ssrc=["']${bundlePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}["'][^>]*>`, 'i');
232
- if (scriptTagPattern.test(html)) {
343
+ // Filter paths: must be valid and not already included in HTML
344
+ const validPaths = bundlePaths.filter((bundlePath) => isValidBundlePath(bundlePath) && !isBundleAlreadyIncluded(html, bundlePath));
345
+ if (validPaths.length === 0) {
233
346
  return html;
234
347
  }
235
348
  // Find </body> tag (case-insensitive)
@@ -240,7 +353,9 @@ export function autoInjectBundle(html, bundlePath) {
240
353
  const bodyClosePos = bodyCloseMatch.index;
241
354
  const before = html.substring(0, bodyClosePos);
242
355
  const after = html.substring(bodyClosePos);
243
- // Inject script tag before </body> with proper indentation
244
- const scriptTag = ` <script type="module" src="${bundlePath}"></script>\n`;
245
- return `${before}${scriptTag}${after}`;
356
+ // Inject all script tags before </body>
357
+ const scriptTags = validPaths
358
+ .map((bundlePath) => `<script type="module" src="${bundlePath}"></script>`)
359
+ .join('\n');
360
+ return `${before}${scriptTags}\n${after}`;
246
361
  }
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@
19
19
  * await build({ clean: true });
20
20
  * ```
21
21
  */
22
- export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, BuildHooks, NavNode, ISGConfig, AgingRule, BuildStats, } from './types/index.js';
22
+ export type { StatiConfig, PageModel, FrontMatter, BuildContext, PageContext, BuildHooks, NavNode, ISGConfig, AgingRule, BuildStats, BundleConfig, } from './types/index.js';
23
23
  export type { SEOMetadata, SEOConfig, SEOContext, SEOValidationResult, SEOTagType, RobotsConfig, OpenGraphConfig, OpenGraphImage, OpenGraphArticle, TwitterCardConfig, AuthorConfig, } from './types/index.js';
24
24
  export type { SitemapConfig, SitemapEntry, SitemapGenerationResult, ChangeFrequency, } from './types/index.js';
25
25
  export type { RSSConfig, RSSFeedConfig, RSSGenerationResult } from './types/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,GACX,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1F,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,YAAY,EACV,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,UAAU,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EACV,WAAW,EACX,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAE1B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAGtF,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAG1F,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,2BAA2B,EAC3B,UAAU,EACV,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAE7D"}
@@ -111,6 +111,56 @@ export interface SEOConfig {
111
111
  /** Enable debug logging for SEO generation (default: false) */
112
112
  debug?: boolean;
113
113
  }
114
+ /**
115
+ * Configuration for a single TypeScript bundle.
116
+ * Defines how a bundle is compiled and which pages should include it.
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * // Global bundle (included on all pages)
121
+ * const coreBundle: BundleConfig = {
122
+ * entryPoint: 'core.ts',
123
+ * bundleName: 'core'
124
+ * };
125
+ *
126
+ * // Targeted bundle (only on specific pages)
127
+ * const docsBundle: BundleConfig = {
128
+ * entryPoint: 'docs.ts',
129
+ * bundleName: 'docs',
130
+ * include: ['/docs/**', '/api/**'],
131
+ * exclude: ['/docs/legacy/**']
132
+ * };
133
+ * ```
134
+ */
135
+ export interface BundleConfig {
136
+ /**
137
+ * Entry point file name relative to srcDir.
138
+ * @example 'main.ts', 'features/playground.ts'
139
+ */
140
+ entryPoint: string;
141
+ /**
142
+ * Output bundle file name (without extension).
143
+ * Final filename includes hash in production: `[bundleName]-[hash].js`
144
+ */
145
+ bundleName: string;
146
+ /**
147
+ * Glob patterns for pages that should include this bundle.
148
+ * Matches against page output path (e.g., '/docs/api/hooks.html').
149
+ * If omitted, bundle is included on ALL pages (global bundle).
150
+ *
151
+ * Supports glob syntax: `*`, `**`, `?`, `[abc]`, `{a,b}`
152
+ *
153
+ * @example ['/docs/**', '/api/**']
154
+ */
155
+ include?: string[];
156
+ /**
157
+ * Glob patterns for pages to exclude from this bundle.
158
+ * Takes precedence over include patterns.
159
+ *
160
+ * @example ['/docs/legacy/**']
161
+ */
162
+ exclude?: string[];
163
+ }
114
164
  /**
115
165
  * TypeScript compilation configuration.
116
166
  * Controls how Stati compiles TypeScript files using esbuild.
@@ -120,15 +170,23 @@ export interface SEOConfig {
120
170
  *
121
171
  * @example
122
172
  * ```typescript
123
- * const config: StatiConfig = {
173
+ * // Simple configuration (defaults to single global bundle)
174
+ * const simpleConfig: StatiConfig = {
175
+ * typescript: {
176
+ * enabled: true
177
+ * // Defaults to: bundles: [{ entryPoint: 'main.ts', bundleName: 'main' }]
178
+ * }
179
+ * };
180
+ *
181
+ * // Multiple bundles with per-page targeting
182
+ * const multiConfig: StatiConfig = {
124
183
  * typescript: {
125
184
  * enabled: true,
126
185
  * srcDir: 'src',
127
- * outDir: '_assets',
128
- * entryPoint: 'main.ts',
129
- * bundleName: 'bundle',
130
- * // hash and minify default to true in production
131
- * // set to false only if you need to debug production builds
186
+ * bundles: [
187
+ * { entryPoint: 'core.ts', bundleName: 'core' },
188
+ * { entryPoint: 'docs.ts', bundleName: 'docs', include: ['/docs/**'] }
189
+ * ]
132
190
  * }
133
191
  * };
134
192
  * ```
@@ -151,17 +209,6 @@ export interface TypeScriptConfig {
151
209
  * @default '_assets'
152
210
  */
153
211
  outDir?: string;
154
- /**
155
- * Entry point file name (without path).
156
- * @default 'main.ts'
157
- */
158
- entryPoint?: string;
159
- /**
160
- * Output bundle file name (without extension).
161
- * The final filename will include a content hash: `[bundleName]-[hash].js`
162
- * @default 'bundle'
163
- */
164
- bundleName?: string;
165
212
  /**
166
213
  * Include content hash in bundle filename for cache busting in production.
167
214
  * When true, outputs `bundle-a1b2c3d4.js`. When false, outputs `bundle.js`.
@@ -175,6 +222,25 @@ export interface TypeScriptConfig {
175
222
  * @default true
176
223
  */
177
224
  minify?: boolean;
225
+ /**
226
+ * Array of bundle configurations.
227
+ * Each bundle can target specific pages using include/exclude patterns.
228
+ * If omitted, defaults to a single global bundle:
229
+ * `[{ entryPoint: 'main.ts', bundleName: 'main' }]`
230
+ *
231
+ * When defined, completely overrides the default.
232
+ */
233
+ bundles?: BundleConfig[];
234
+ /**
235
+ * Automatically inject bundle script tags before </body>.
236
+ * When enabled (default), Stati automatically adds script tags for matched bundles.
237
+ * Disable if you want manual control over script placement in templates.
238
+ *
239
+ * When disabled, use `stati.assets.bundlePaths` in templates to access bundle paths.
240
+ *
241
+ * @default true
242
+ */
243
+ autoInject?: boolean;
178
244
  }
179
245
  /**
180
246
  * Robots.txt generation configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;GAEG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,wCAAwC;IACxC,QAAQ,CAAC,EAAE;QACT,oGAAoG;QACpG,OAAO,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;QACzC,oDAAoD;QACpD,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,UAAU,KAAK,IAAI,CAAC;KACtC,CAAC;IACF,wCAAwC;IACxC,GAAG,CAAC,EAAE;QACJ,8BAA8B;QAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;KACnD,CAAC;IACF,kDAAkD;IAClD,GAAG,CAAC,EAAE,OAAO,UAAU,EAAE,SAAS,CAAC;IACnC,wBAAwB;IACxB,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB,uCAAuC;IACvC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,wCAAwC;IACxC,GAAG,CAAC,EAAE,OAAO,UAAU,EAAE,SAAS,CAAC;IACnC,sCAAsC;IACtC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,uCAAuC;IACvC,GAAG,CAAC,EAAE;QACJ,kDAAkD;QAClD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,yDAAyD;QACzD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,6DAA6D;QAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,mCAAmC;IACnC,OAAO,CAAC,EAAE;QACR,8CAA8C;QAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,qDAAqD;QACrD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,6DAA6D;QAC7D,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;IACF,4BAA4B;IAC5B,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,gEAAgE;IAChE,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAEnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IAEzB;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,KAAK,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC,CAAC;IACH,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4FAA4F;IAC5F,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,MAAM,EAAE,WAAW,CAAC;IACpB,sCAAsC;IACtC,KAAK,EAAE,OAAO,cAAc,EAAE,SAAS,EAAE,CAAC;CAC3C;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,IAAI,EAAE,OAAO,cAAc,EAAE,SAAS,CAAC;IACvC,uCAAuC;IACvC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,gDAAgD;IAChD,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,mDAAmD;IACnD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1D,kDAAkD;IAClD,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -4,18 +4,24 @@
4
4
  /**
5
5
  * TypeScript bundle asset information.
6
6
  * Available when TypeScript compilation is enabled.
7
+ * Contains paths to all bundles matched for the current page.
7
8
  *
8
9
  * @example
9
10
  * ```typescript
10
11
  * // In Eta templates, access via stati.assets
11
- * <script type="module" src="<%= stati.assets.bundlePath %>"></script>
12
+ * // Auto-injection handles most cases, but manual control is available:
13
+ * <% for (const path of stati.assets.bundlePaths) { %>
14
+ * <script type="module" src="<%= path %>"></script>
15
+ * <% } %>
12
16
  * ```
13
17
  */
14
18
  export interface StatiAssets {
15
- /** Bundle filename only (e.g., 'bundle-a1b2c3d4.js') */
16
- bundleName?: string;
17
- /** Full path to bundle (e.g., '/_assets/bundle-a1b2c3d4.js') */
18
- bundlePath?: string;
19
+ /**
20
+ * Array of all bundle paths matched for this page.
21
+ * Paths are in config order: ['/_assets/core-abc.js', '/_assets/docs-def.js']
22
+ * Always an array, empty [] if no TypeScript enabled or no bundles match.
23
+ */
24
+ bundlePaths: string[];
19
25
  }
20
26
  /**
21
27
  * Front matter metadata extracted from content files.