@openelement/ssg 0.41.0-alpha.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.
@@ -0,0 +1,99 @@
1
+ /**
2
+ * @openelement/adapter-vite - Route Type Generator
3
+ *
4
+ * Generates `.openElement/routes.d.ts` with type-safe route parameter definitions
5
+ * for the `virtual:open-routes` module. Only routes that have dynamic [param]
6
+ * segments are included in the output.
7
+ *
8
+ * v0.25.0: Initial implementation for build-time route type code generation.
9
+ */ /**
10
+ * Convert a file path to a route path, preserving [param] bracket syntax.
11
+ * Unlike `filePathToRoutePath()` in route-scanner (which converts to `:param`
12
+ * for Hono/URLPattern), this preserves `[param]` to match the file-system
13
+ * convention that developers see in their route files.
14
+ *
15
+ * e.g., 'blog/[slug].ts' → '/blog/[slug]'
16
+ * e.g., 'index.ts' → '/'
17
+ */ function filePathToBracketRoute(filePath) {
18
+ let p = filePath.replace(/\.[^.]+$/, '');
19
+ if (p === 'index') return '/';
20
+ if (p.endsWith('/index')) {
21
+ p = p.slice(0, -6);
22
+ if (p === '') return '/';
23
+ }
24
+ if (!p.startsWith('/')) p = '/' + p;
25
+ return p;
26
+ }
27
+ /**
28
+ * Generate TypeScript type literal for route params.
29
+ * Converts param names to a Record<string, string> type.
30
+ *
31
+ * Example: ['slug'] → `{ slug: string }`
32
+ * Example: ['package', 'component'] → `{ package: string; component: string }`
33
+ */ function generateParamsType(params) {
34
+ if (params.length === 0) {
35
+ return 'Record<string, never>';
36
+ }
37
+ const fields = params.map((p)=>`${p}: string`);
38
+ return `{ ${fields.join('; ')} }`;
39
+ }
40
+ /**
41
+ * Generate the `.openElement/routes.d.ts` declaration file content.
42
+ *
43
+ * Only routes that have dynamic parameters (non-empty `params` array)
44
+ * are included in the generated output. Static routes with no params
45
+ * are excluded since they don't need type-safe parameter access.
46
+ *
47
+ * Example output:
48
+ * ```typescript
49
+ * declare module 'virtual:open-routes' {
50
+ * interface RouteParams {
51
+ * '/blog/[slug]': { slug: string };
52
+ * '/registry/[package]/[component]': { package: string; component: string };
53
+ * }
54
+ * }
55
+ * ```
56
+ *
57
+ * @param routes - Array of route entries from the route scanner
58
+ * @returns TypeScript declaration file content as a string
59
+ */ export function generateRouteTypes(routes) {
60
+ // Filter for page routes that have dynamic params
61
+ const paramRoutes = routes.filter((r)=>r.type === 'page' && r.params && r.params.length > 0);
62
+ if (paramRoutes.length === 0) {
63
+ // No routes with params - generate a placeholder interface
64
+ return [
65
+ '/**',
66
+ ' * Auto-generated by @openelement/adapter-vite (v0.25.0)',
67
+ ' * DO NOT EDIT MANUALLY',
68
+ ' */',
69
+ '',
70
+ "declare module 'virtual:open-routes' {",
71
+ ' // eslint-disable-next-line @typescript-eslint/no-empty-interface',
72
+ ' export interface RouteParams {',
73
+ ' }',
74
+ '}',
75
+ ''
76
+ ].join('\n');
77
+ }
78
+ // Generate entries for each route with params
79
+ const paramEntries = paramRoutes.map((r)=>{
80
+ const bracketPath = filePathToBracketRoute(r.filePath);
81
+ const path = JSON.stringify(bracketPath);
82
+ const type = generateParamsType(r.params);
83
+ return ` ${path}: ${type};`;
84
+ });
85
+ return [
86
+ '/**',
87
+ ' * Auto-generated by @openelement/adapter-vite (v0.25.0)',
88
+ ' * DO NOT EDIT MANUALLY',
89
+ ' */',
90
+ '',
91
+ "declare module 'virtual:open-routes' {",
92
+ ' export interface RouteParams {',
93
+ ...paramEntries,
94
+ ' }',
95
+ '}',
96
+ ''
97
+ ].join('\n');
98
+ }
99
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3JvdXRlLXR5cGUtZ2VuZXJhdG9yLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQG9wZW5lbGVtZW50L2FkYXB0ZXItdml0ZSAtIFJvdXRlIFR5cGUgR2VuZXJhdG9yXG4gKlxuICogR2VuZXJhdGVzIGAub3BlbkVsZW1lbnQvcm91dGVzLmQudHNgIHdpdGggdHlwZS1zYWZlIHJvdXRlIHBhcmFtZXRlciBkZWZpbml0aW9uc1xuICogZm9yIHRoZSBgdmlydHVhbDpvcGVuLXJvdXRlc2AgbW9kdWxlLiBPbmx5IHJvdXRlcyB0aGF0IGhhdmUgZHluYW1pYyBbcGFyYW1dXG4gKiBzZWdtZW50cyBhcmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dC5cbiAqXG4gKiB2MC4yNS4wOiBJbml0aWFsIGltcGxlbWVudGF0aW9uIGZvciBidWlsZC10aW1lIHJvdXRlIHR5cGUgY29kZSBnZW5lcmF0aW9uLlxuICovXG5cbmltcG9ydCB0eXBlIHsgUm91dGVFbnRyeSB9IGZyb20gJ0BvcGVuZWxlbWVudC9wcm90b2NvbC9mcmFtZXdvcmsnO1xuXG4vKipcbiAqIENvbnZlcnQgYSBmaWxlIHBhdGggdG8gYSByb3V0ZSBwYXRoLCBwcmVzZXJ2aW5nIFtwYXJhbV0gYnJhY2tldCBzeW50YXguXG4gKiBVbmxpa2UgYGZpbGVQYXRoVG9Sb3V0ZVBhdGgoKWAgaW4gcm91dGUtc2Nhbm5lciAod2hpY2ggY29udmVydHMgdG8gYDpwYXJhbWBcbiAqIGZvciBIb25vL1VSTFBhdHRlcm4pLCB0aGlzIHByZXNlcnZlcyBgW3BhcmFtXWAgdG8gbWF0Y2ggdGhlIGZpbGUtc3lzdGVtXG4gKiBjb252ZW50aW9uIHRoYXQgZGV2ZWxvcGVycyBzZWUgaW4gdGhlaXIgcm91dGUgZmlsZXMuXG4gKlxuICogZS5nLiwgJ2Jsb2cvW3NsdWddLnRzJyDihpIgJy9ibG9nL1tzbHVnXSdcbiAqIGUuZy4sICdpbmRleC50cycg4oaSICcvJ1xuICovXG5mdW5jdGlvbiBmaWxlUGF0aFRvQnJhY2tldFJvdXRlKGZpbGVQYXRoOiBzdHJpbmcpOiBzdHJpbmcge1xuICBsZXQgcCA9IGZpbGVQYXRoLnJlcGxhY2UoL1xcLlteLl0rJC8sICcnKTtcblxuICBpZiAocCA9PT0gJ2luZGV4JykgcmV0dXJuICcvJztcbiAgaWYgKHAuZW5kc1dpdGgoJy9pbmRleCcpKSB7XG4gICAgcCA9IHAuc2xpY2UoMCwgLTYpO1xuICAgIGlmIChwID09PSAnJykgcmV0dXJuICcvJztcbiAgfVxuICBpZiAoIXAuc3RhcnRzV2l0aCgnLycpKSBwID0gJy8nICsgcDtcblxuICByZXR1cm4gcDtcbn1cblxuLyoqXG4gKiBHZW5lcmF0ZSBUeXBlU2NyaXB0IHR5cGUgbGl0ZXJhbCBmb3Igcm91dGUgcGFyYW1zLlxuICogQ29udmVydHMgcGFyYW0gbmFtZXMgdG8gYSBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+IHR5cGUuXG4gKlxuICogRXhhbXBsZTogWydzbHVnJ10g4oaSIGB7IHNsdWc6IHN0cmluZyB9YFxuICogRXhhbXBsZTogWydwYWNrYWdlJywgJ2NvbXBvbmVudCddIOKGkiBgeyBwYWNrYWdlOiBzdHJpbmc7IGNvbXBvbmVudDogc3RyaW5nIH1gXG4gKi9cbmZ1bmN0aW9uIGdlbmVyYXRlUGFyYW1zVHlwZShwYXJhbXM6IHN0cmluZ1tdKTogc3RyaW5nIHtcbiAgaWYgKHBhcmFtcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gJ1JlY29yZDxzdHJpbmcsIG5ldmVyPic7XG4gIH1cbiAgY29uc3QgZmllbGRzID0gcGFyYW1zLm1hcCgocCkgPT4gYCR7cH06IHN0cmluZ2ApO1xuICByZXR1cm4gYHsgJHtmaWVsZHMuam9pbignOyAnKX0gfWA7XG59XG5cbi8qKlxuICogR2VuZXJhdGUgdGhlIGAub3BlbkVsZW1lbnQvcm91dGVzLmQudHNgIGRlY2xhcmF0aW9uIGZpbGUgY29udGVudC5cbiAqXG4gKiBPbmx5IHJvdXRlcyB0aGF0IGhhdmUgZHluYW1pYyBwYXJhbWV0ZXJzIChub24tZW1wdHkgYHBhcmFtc2AgYXJyYXkpXG4gKiBhcmUgaW5jbHVkZWQgaW4gdGhlIGdlbmVyYXRlZCBvdXRwdXQuIFN0YXRpYyByb3V0ZXMgd2l0aCBubyBwYXJhbXNcbiAqIGFyZSBleGNsdWRlZCBzaW5jZSB0aGV5IGRvbid0IG5lZWQgdHlwZS1zYWZlIHBhcmFtZXRlciBhY2Nlc3MuXG4gKlxuICogRXhhbXBsZSBvdXRwdXQ6XG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBkZWNsYXJlIG1vZHVsZSAndmlydHVhbDpvcGVuLXJvdXRlcycge1xuICogICBpbnRlcmZhY2UgUm91dGVQYXJhbXMge1xuICogICAgICcvYmxvZy9bc2x1Z10nOiB7IHNsdWc6IHN0cmluZyB9O1xuICogICAgICcvcmVnaXN0cnkvW3BhY2thZ2VdL1tjb21wb25lbnRdJzogeyBwYWNrYWdlOiBzdHJpbmc7IGNvbXBvbmVudDogc3RyaW5nIH07XG4gKiAgIH1cbiAqIH1cbiAqIGBgYFxuICpcbiAqIEBwYXJhbSByb3V0ZXMgLSBBcnJheSBvZiByb3V0ZSBlbnRyaWVzIGZyb20gdGhlIHJvdXRlIHNjYW5uZXJcbiAqIEByZXR1cm5zIFR5cGVTY3JpcHQgZGVjbGFyYXRpb24gZmlsZSBjb250ZW50IGFzIGEgc3RyaW5nXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZW5lcmF0ZVJvdXRlVHlwZXMocm91dGVzOiBSb3V0ZUVudHJ5W10pOiBzdHJpbmcge1xuICAvLyBGaWx0ZXIgZm9yIHBhZ2Ugcm91dGVzIHRoYXQgaGF2ZSBkeW5hbWljIHBhcmFtc1xuICBjb25zdCBwYXJhbVJvdXRlcyA9IHJvdXRlcy5maWx0ZXIoXG4gICAgKHIpID0+IHIudHlwZSA9PT0gJ3BhZ2UnICYmIHIucGFyYW1zICYmIHIucGFyYW1zLmxlbmd0aCA+IDAsXG4gICk7XG5cbiAgaWYgKHBhcmFtUm91dGVzLmxlbmd0aCA9PT0gMCkge1xuICAgIC8vIE5vIHJvdXRlcyB3aXRoIHBhcmFtcyAtIGdlbmVyYXRlIGEgcGxhY2Vob2xkZXIgaW50ZXJmYWNlXG4gICAgcmV0dXJuIFtcbiAgICAgICcvKionLFxuICAgICAgJyAqIEF1dG8tZ2VuZXJhdGVkIGJ5IEBvcGVuZWxlbWVudC9hZGFwdGVyLXZpdGUgKHYwLjI1LjApJyxcbiAgICAgICcgKiBETyBOT1QgRURJVCBNQU5VQUxMWScsXG4gICAgICAnICovJyxcbiAgICAgICcnLFxuICAgICAgXCJkZWNsYXJlIG1vZHVsZSAndmlydHVhbDpvcGVuLXJvdXRlcycge1wiLFxuICAgICAgJyAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1lbXB0eS1pbnRlcmZhY2UnLFxuICAgICAgJyAgZXhwb3J0IGludGVyZmFjZSBSb3V0ZVBhcmFtcyB7JyxcbiAgICAgICcgIH0nLFxuICAgICAgJ30nLFxuICAgICAgJycsXG4gICAgXS5qb2luKCdcXG4nKTtcbiAgfVxuXG4gIC8vIEdlbmVyYXRlIGVudHJpZXMgZm9yIGVhY2ggcm91dGUgd2l0aCBwYXJhbXNcbiAgY29uc3QgcGFyYW1FbnRyaWVzID0gcGFyYW1Sb3V0ZXMubWFwKChyKSA9PiB7XG4gICAgY29uc3QgYnJhY2tldFBhdGggPSBmaWxlUGF0aFRvQnJhY2tldFJvdXRlKHIuZmlsZVBhdGgpO1xuICAgIGNvbnN0IHBhdGggPSBKU09OLnN0cmluZ2lmeShicmFja2V0UGF0aCk7XG4gICAgY29uc3QgdHlwZSA9IGdlbmVyYXRlUGFyYW1zVHlwZShyLnBhcmFtcyEpO1xuICAgIHJldHVybiBgICAgICR7cGF0aH06ICR7dHlwZX07YDtcbiAgfSk7XG5cbiAgcmV0dXJuIFtcbiAgICAnLyoqJyxcbiAgICAnICogQXV0by1nZW5lcmF0ZWQgYnkgQG9wZW5lbGVtZW50L2FkYXB0ZXItdml0ZSAodjAuMjUuMCknLFxuICAgICcgKiBETyBOT1QgRURJVCBNQU5VQUxMWScsXG4gICAgJyAqLycsXG4gICAgJycsXG4gICAgXCJkZWNsYXJlIG1vZHVsZSAndmlydHVhbDpvcGVuLXJvdXRlcycge1wiLFxuICAgICcgIGV4cG9ydCBpbnRlcmZhY2UgUm91dGVQYXJhbXMgeycsXG4gICAgLi4ucGFyYW1FbnRyaWVzLFxuICAgICcgIH0nLFxuICAgICd9JyxcbiAgICAnJyxcbiAgXS5qb2luKCdcXG4nKTtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Q0FRQyxHQUlEOzs7Ozs7OztDQVFDLEdBQ0QsU0FBUyx1QkFBdUIsUUFBZ0I7RUFDOUMsSUFBSSxJQUFJLFNBQVMsT0FBTyxDQUFDLFlBQVk7RUFFckMsSUFBSSxNQUFNLFNBQVMsT0FBTztFQUMxQixJQUFJLEVBQUUsUUFBUSxDQUFDLFdBQVc7SUFDeEIsSUFBSSxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUM7SUFDaEIsSUFBSSxNQUFNLElBQUksT0FBTztFQUN2QjtFQUNBLElBQUksQ0FBQyxFQUFFLFVBQVUsQ0FBQyxNQUFNLElBQUksTUFBTTtFQUVsQyxPQUFPO0FBQ1Q7QUFFQTs7Ozs7O0NBTUMsR0FDRCxTQUFTLG1CQUFtQixNQUFnQjtFQUMxQyxJQUFJLE9BQU8sTUFBTSxLQUFLLEdBQUc7SUFDdkIsT0FBTztFQUNUO0VBQ0EsTUFBTSxTQUFTLE9BQU8sR0FBRyxDQUFDLENBQUMsSUFBTSxHQUFHLEVBQUUsUUFBUSxDQUFDO0VBQy9DLE9BQU8sQ0FBQyxFQUFFLEVBQUUsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7QUFDbkM7QUFFQTs7Ozs7Ozs7Ozs7Ozs7Ozs7OztDQW1CQyxHQUNELE9BQU8sU0FBUyxtQkFBbUIsTUFBb0I7RUFDckQsa0RBQWtEO0VBQ2xELE1BQU0sY0FBYyxPQUFPLE1BQU0sQ0FDL0IsQ0FBQyxJQUFNLEVBQUUsSUFBSSxLQUFLLFVBQVUsRUFBRSxNQUFNLElBQUksRUFBRSxNQUFNLENBQUMsTUFBTSxHQUFHO0VBRzVELElBQUksWUFBWSxNQUFNLEtBQUssR0FBRztJQUM1QiwyREFBMkQ7SUFDM0QsT0FBTztNQUNMO01BQ0E7TUFDQTtNQUNBO01BQ0E7TUFDQTtNQUNBO01BQ0E7TUFDQTtNQUNBO01BQ0E7S0FDRCxDQUFDLElBQUksQ0FBQztFQUNUO0VBRUEsOENBQThDO0VBQzlDLE1BQU0sZUFBZSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0lBQ3BDLE1BQU0sY0FBYyx1QkFBdUIsRUFBRSxRQUFRO0lBQ3JELE1BQU0sT0FBTyxLQUFLLFNBQVMsQ0FBQztJQUM1QixNQUFNLE9BQU8sbUJBQW1CLEVBQUUsTUFBTTtJQUN4QyxPQUFPLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxFQUFFLEtBQUssQ0FBQyxDQUFDO0VBQ2hDO0VBRUEsT0FBTztJQUNMO0lBQ0E7SUFDQTtJQUNBO0lBQ0E7SUFDQTtJQUNBO09BQ0c7SUFDSDtJQUNBO0lBQ0E7R0FDRCxDQUFDLElBQUksQ0FBQztBQUNUIn0=
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @openelement/ssg - Dynamic route expansion
3
+ *
4
+ * Handles dynamic route rendering using getStaticPaths() + renderRoute()
5
+ * from the SSR bundle.
6
+ */ import { join } from 'node:path';
7
+ import { mkdirSync, writeFileSync } from 'node:fs';
8
+ import { createLogger } from '@openelement/core/logger';
9
+ import { formatError } from '@openelement/core/errors';
10
+ import { collectPageOutput, resolveDynamicRoutePath } from './ssg-helpers.js';
11
+ const log = createLogger('ssg');
12
+ /**
13
+ * Expand dynamic routes by calling getStaticPaths() and renderRoute()
14
+ * for each parameter set.
15
+ *
16
+ * Returns a map of static path params keyed by route path, which is
17
+ * consumed later when building the ISR manifest.
18
+ */ export async function expandDynamicRoutes(dynamicRoutes, renderRoute, getStaticPaths, options, root, outDir, pageDiagnostics) {
19
+ const staticPathParamsByRoute = new Map();
20
+ if (dynamicRoutes.length > 0 && renderRoute && getStaticPaths) {
21
+ for (const route of dynamicRoutes){
22
+ const paramNames = route.paramNames;
23
+ let paramsList;
24
+ try {
25
+ paramsList = await getStaticPaths(route.path);
26
+ } catch (e) {
27
+ log.warn(`Failed to get static paths for ${route.path}: ${formatError(e)}`);
28
+ continue;
29
+ }
30
+ staticPathParamsByRoute.set(route.path, paramsList);
31
+ if (paramsList.length === 0) {
32
+ log.info(`Dynamic route ${route.path} has no static paths - skipping`);
33
+ continue;
34
+ }
35
+ for (const params of paramsList){
36
+ let resolvedPath;
37
+ try {
38
+ resolvedPath = resolveDynamicRoutePath(route.path, paramNames, params);
39
+ } catch (e) {
40
+ log.warn(`Skipping unsafe dynamic route ${route.path}: ${formatError(e)}`);
41
+ continue;
42
+ }
43
+ try {
44
+ const output = await renderRoute(route.path, {
45
+ params,
46
+ title: options.html?.title,
47
+ lang: options.html?.lang,
48
+ headExtras: options.headExtras
49
+ });
50
+ const html = collectPageOutput(resolvedPath, output, pageDiagnostics);
51
+ const outputDir = join(root, outDir);
52
+ const pageDir = join(outputDir, resolvedPath);
53
+ mkdirSync(pageDir, {
54
+ recursive: true
55
+ });
56
+ writeFileSync(join(pageDir, 'index.html'), html, 'utf-8');
57
+ log.info(`Dynamic route: ${resolvedPath} -> ${resolvedPath}/index.html`);
58
+ } catch (e) {
59
+ log.warn(`Failed to render dynamic route ${resolvedPath}: ${formatError(e)}`);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return staticPathParamsByRoute;
65
+ }
66
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3NzZy1keW5hbWljLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQG9wZW5lbGVtZW50L3NzZyAtIER5bmFtaWMgcm91dGUgZXhwYW5zaW9uXG4gKlxuICogSGFuZGxlcyBkeW5hbWljIHJvdXRlIHJlbmRlcmluZyB1c2luZyBnZXRTdGF0aWNQYXRocygpICsgcmVuZGVyUm91dGUoKVxuICogZnJvbSB0aGUgU1NSIGJ1bmRsZS5cbiAqL1xuXG5pbXBvcnQgeyBqb2luIH0gZnJvbSAnbm9kZTpwYXRoJztcbmltcG9ydCB7IG1rZGlyU3luYywgd3JpdGVGaWxlU3luYyB9IGZyb20gJ25vZGU6ZnMnO1xuaW1wb3J0IHR5cGUgeyBTc2dQYWdlT3V0cHV0LCBTc2dSZW5kZXJPcHRpb25zIH0gZnJvbSAnQG9wZW5lbGVtZW50L3Byb3RvY29sL3NzZyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICdAb3BlbmVsZW1lbnQvY29yZS9sb2dnZXInO1xuaW1wb3J0IHsgZm9ybWF0RXJyb3IgfSBmcm9tICdAb3BlbmVsZW1lbnQvY29yZS9lcnJvcnMnO1xuaW1wb3J0IHsgY29sbGVjdFBhZ2VPdXRwdXQsIHR5cGUgUGFnZURpYWdub3N0aWMsIHJlc29sdmVEeW5hbWljUm91dGVQYXRoIH0gZnJvbSAnLi9zc2ctaGVscGVycy5qcyc7XG5cbmNvbnN0IGxvZyA9IGNyZWF0ZUxvZ2dlcignc3NnJyk7XG5cbmludGVyZmFjZSBSb3V0ZUluZm9JdGVtIHtcbiAgcGF0aDogc3RyaW5nO1xuICB0YWdOYW1lOiBzdHJpbmc7XG4gIGlzRHluYW1pYzogYm9vbGVhbjtcbiAgcGFyYW1OYW1lczogc3RyaW5nW107XG4gIHJldmFsaWRhdGU/OiBudW1iZXI7XG4gIHBhcmFtcz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz47XG59XG5cbi8qKlxuICogRXhwYW5kIGR5bmFtaWMgcm91dGVzIGJ5IGNhbGxpbmcgZ2V0U3RhdGljUGF0aHMoKSBhbmQgcmVuZGVyUm91dGUoKVxuICogZm9yIGVhY2ggcGFyYW1ldGVyIHNldC5cbiAqXG4gKiBSZXR1cm5zIGEgbWFwIG9mIHN0YXRpYyBwYXRoIHBhcmFtcyBrZXllZCBieSByb3V0ZSBwYXRoLCB3aGljaCBpc1xuICogY29uc3VtZWQgbGF0ZXIgd2hlbiBidWlsZGluZyB0aGUgSVNSIG1hbmlmZXN0LlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gZXhwYW5kRHluYW1pY1JvdXRlcyhcbiAgZHluYW1pY1JvdXRlczogUm91dGVJbmZvSXRlbVtdLFxuICByZW5kZXJSb3V0ZTpcbiAgICB8ICgocGF0aDogc3RyaW5nLCBvcHRzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pID0+IFByb21pc2U8U3NnUGFnZU91dHB1dD4pXG4gICAgfCB1bmRlZmluZWQsXG4gIGdldFN0YXRpY1BhdGhzOlxuICAgIHwgKChwYXRoOiBzdHJpbmcpID0+IFByb21pc2U8QXJyYXk8UmVjb3JkPHN0cmluZywgc3RyaW5nPj4+KVxuICAgIHwgdW5kZWZpbmVkLFxuICBvcHRpb25zOiBTc2dSZW5kZXJPcHRpb25zLFxuICByb290OiBzdHJpbmcsXG4gIG91dERpcjogc3RyaW5nLFxuICBwYWdlRGlhZ25vc3RpY3M6IFBhZ2VEaWFnbm9zdGljW10sXG4pOiBQcm9taXNlPE1hcDxzdHJpbmcsIEFycmF5PFJlY29yZDxzdHJpbmcsIHN0cmluZz4+Pj4ge1xuICBjb25zdCBzdGF0aWNQYXRoUGFyYW1zQnlSb3V0ZSA9IG5ldyBNYXA8c3RyaW5nLCBBcnJheTxSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+Pj4oKTtcblxuICBpZiAoZHluYW1pY1JvdXRlcy5sZW5ndGggPiAwICYmIHJlbmRlclJvdXRlICYmIGdldFN0YXRpY1BhdGhzKSB7XG4gICAgZm9yIChjb25zdCByb3V0ZSBvZiBkeW5hbWljUm91dGVzKSB7XG4gICAgICBjb25zdCBwYXJhbU5hbWVzID0gcm91dGUucGFyYW1OYW1lcztcbiAgICAgIGxldCBwYXJhbXNMaXN0OiBBcnJheTxSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+PjtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgcGFyYW1zTGlzdCA9IGF3YWl0IGdldFN0YXRpY1BhdGhzKHJvdXRlLnBhdGgpO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICBsb2cud2FybihcbiAgICAgICAgICBgRmFpbGVkIHRvIGdldCBzdGF0aWMgcGF0aHMgZm9yICR7cm91dGUucGF0aH06ICR7Zm9ybWF0RXJyb3IoZSl9YCxcbiAgICAgICAgKTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICBzdGF0aWNQYXRoUGFyYW1zQnlSb3V0ZS5zZXQocm91dGUucGF0aCwgcGFyYW1zTGlzdCk7XG5cbiAgICAgIGlmIChwYXJhbXNMaXN0Lmxlbmd0aCA9PT0gMCkge1xuICAgICAgICBsb2cuaW5mbyhgRHluYW1pYyByb3V0ZSAke3JvdXRlLnBhdGh9IGhhcyBubyBzdGF0aWMgcGF0aHMgLSBza2lwcGluZ2ApO1xuICAgICAgICBjb250aW51ZTtcbiAgICAgIH1cblxuICAgICAgZm9yIChjb25zdCBwYXJhbXMgb2YgcGFyYW1zTGlzdCkge1xuICAgICAgICBsZXQgcmVzb2x2ZWRQYXRoOiBzdHJpbmc7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgcmVzb2x2ZWRQYXRoID0gcmVzb2x2ZUR5bmFtaWNSb3V0ZVBhdGgoXG4gICAgICAgICAgICByb3V0ZS5wYXRoLFxuICAgICAgICAgICAgcGFyYW1OYW1lcyxcbiAgICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICApO1xuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgbG9nLndhcm4oXG4gICAgICAgICAgICBgU2tpcHBpbmcgdW5zYWZlIGR5bmFtaWMgcm91dGUgJHtyb3V0ZS5wYXRofTogJHtmb3JtYXRFcnJvcihlKX1gLFxuICAgICAgICAgICk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cblxuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IG91dHB1dCA9IGF3YWl0IHJlbmRlclJvdXRlKHJvdXRlLnBhdGgsIHtcbiAgICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICAgIHRpdGxlOiBvcHRpb25zLmh0bWw/LnRpdGxlLFxuICAgICAgICAgICAgbGFuZzogb3B0aW9ucy5odG1sPy5sYW5nLFxuICAgICAgICAgICAgaGVhZEV4dHJhczogb3B0aW9ucy5oZWFkRXh0cmFzLFxuICAgICAgICAgIH0pO1xuICAgICAgICAgIGNvbnN0IGh0bWwgPSBjb2xsZWN0UGFnZU91dHB1dChyZXNvbHZlZFBhdGgsIG91dHB1dCwgcGFnZURpYWdub3N0aWNzKTtcblxuICAgICAgICAgIGNvbnN0IG91dHB1dERpciA9IGpvaW4ocm9vdCwgb3V0RGlyKTtcbiAgICAgICAgICBjb25zdCBwYWdlRGlyID0gam9pbihvdXRwdXREaXIsIHJlc29sdmVkUGF0aCk7XG4gICAgICAgICAgbWtkaXJTeW5jKHBhZ2VEaXIsIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgICAgICAgIHdyaXRlRmlsZVN5bmMoam9pbihwYWdlRGlyLCAnaW5kZXguaHRtbCcpLCBodG1sLCAndXRmLTgnKTtcbiAgICAgICAgICBsb2cuaW5mbyhcbiAgICAgICAgICAgIGBEeW5hbWljIHJvdXRlOiAke3Jlc29sdmVkUGF0aH0gLT4gJHtyZXNvbHZlZFBhdGh9L2luZGV4Lmh0bWxgLFxuICAgICAgICAgICk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICBsb2cud2FybihcbiAgICAgICAgICAgIGBGYWlsZWQgdG8gcmVuZGVyIGR5bmFtaWMgcm91dGUgJHtyZXNvbHZlZFBhdGh9OiAke2Zvcm1hdEVycm9yKGUpfWAsXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIHJldHVybiBzdGF0aWNQYXRoUGFyYW1zQnlSb3V0ZTtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Q0FLQyxHQUVELFNBQVMsSUFBSSxRQUFRLFlBQVk7QUFDakMsU0FBUyxTQUFTLEVBQUUsYUFBYSxRQUFRLFVBQVU7QUFFbkQsU0FBUyxZQUFZLFFBQVEsMkJBQTJCO0FBQ3hELFNBQVMsV0FBVyxRQUFRLDJCQUEyQjtBQUN2RCxTQUFTLGlCQUFpQixFQUF1Qix1QkFBdUIsUUFBUSxtQkFBbUI7QUFFbkcsTUFBTSxNQUFNLGFBQWE7QUFXekI7Ozs7OztDQU1DLEdBQ0QsT0FBTyxlQUFlLG9CQUNwQixhQUE4QixFQUM5QixXQUVhLEVBQ2IsY0FFYSxFQUNiLE9BQXlCLEVBQ3pCLElBQVksRUFDWixNQUFjLEVBQ2QsZUFBaUM7RUFFakMsTUFBTSwwQkFBMEIsSUFBSTtFQUVwQyxJQUFJLGNBQWMsTUFBTSxHQUFHLEtBQUssZUFBZSxnQkFBZ0I7SUFDN0QsS0FBSyxNQUFNLFNBQVMsY0FBZTtNQUNqQyxNQUFNLGFBQWEsTUFBTSxVQUFVO01BQ25DLElBQUk7TUFFSixJQUFJO1FBQ0YsYUFBYSxNQUFNLGVBQWUsTUFBTSxJQUFJO01BQzlDLEVBQUUsT0FBTyxHQUFHO1FBQ1YsSUFBSSxJQUFJLENBQ04sQ0FBQywrQkFBK0IsRUFBRSxNQUFNLElBQUksQ0FBQyxFQUFFLEVBQUUsWUFBWSxJQUFJO1FBRW5FO01BQ0Y7TUFDQSx3QkFBd0IsR0FBRyxDQUFDLE1BQU0sSUFBSSxFQUFFO01BRXhDLElBQUksV0FBVyxNQUFNLEtBQUssR0FBRztRQUMzQixJQUFJLElBQUksQ0FBQyxDQUFDLGNBQWMsRUFBRSxNQUFNLElBQUksQ0FBQywrQkFBK0IsQ0FBQztRQUNyRTtNQUNGO01BRUEsS0FBSyxNQUFNLFVBQVUsV0FBWTtRQUMvQixJQUFJO1FBQ0osSUFBSTtVQUNGLGVBQWUsd0JBQ2IsTUFBTSxJQUFJLEVBQ1YsWUFDQTtRQUVKLEVBQUUsT0FBTyxHQUFHO1VBQ1YsSUFBSSxJQUFJLENBQ04sQ0FBQyw4QkFBOEIsRUFBRSxNQUFNLElBQUksQ0FBQyxFQUFFLEVBQUUsWUFBWSxJQUFJO1VBRWxFO1FBQ0Y7UUFFQSxJQUFJO1VBQ0YsTUFBTSxTQUFTLE1BQU0sWUFBWSxNQUFNLElBQUksRUFBRTtZQUMzQztZQUNBLE9BQU8sUUFBUSxJQUFJLEVBQUU7WUFDckIsTUFBTSxRQUFRLElBQUksRUFBRTtZQUNwQixZQUFZLFFBQVEsVUFBVTtVQUNoQztVQUNBLE1BQU0sT0FBTyxrQkFBa0IsY0FBYyxRQUFRO1VBRXJELE1BQU0sWUFBWSxLQUFLLE1BQU07VUFDN0IsTUFBTSxVQUFVLEtBQUssV0FBVztVQUNoQyxVQUFVLFNBQVM7WUFBRSxXQUFXO1VBQUs7VUFDckMsY0FBYyxLQUFLLFNBQVMsZUFBZSxNQUFNO1VBQ2pELElBQUksSUFBSSxDQUNOLENBQUMsZUFBZSxFQUFFLGFBQWEsSUFBSSxFQUFFLGFBQWEsV0FBVyxDQUFDO1FBRWxFLEVBQUUsT0FBTyxHQUFHO1VBQ1YsSUFBSSxJQUFJLENBQ04sQ0FBQywrQkFBK0IsRUFBRSxhQUFhLEVBQUUsRUFBRSxZQUFZLElBQUk7UUFFdkU7TUFDRjtJQUNGO0VBQ0Y7RUFFQSxPQUFPO0FBQ1QifQ==
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resolve a dynamic route path by substituting param values.
3
+ * Validates param values to prevent path traversal and control characters.
4
+ */ export declare function resolveDynamicRoutePath(routePath: string, paramNames: string[], params: Record<string, string>): string;
5
+ /**
6
+ * Stable SHA-256 hash for SSG-generated asset names.
7
+ * Returns a deterministic lowercase hex string.
8
+ */ export declare function stableHash(str: string): Promise<string>;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @openelement/ssg - SSG helper utilities
3
+ *
4
+ * Pure utility functions used by the SSG render pipeline.
5
+ * This module sits at the bottom of the dependency graph.
6
+ */ import { join } from 'node:path';
7
+ import { readdirSync } from 'node:fs';
8
+ import { createIsrCacheKey } from '@openelement/core/isr';
9
+ // ─── Path / URL helpers ────────────────────────────────────────
10
+ /** Recursively find all .html files under a directory. */ export function findHtmlFiles(dir) {
11
+ const results = [];
12
+ try {
13
+ const entries = readdirSync(dir, {
14
+ withFileTypes: true
15
+ });
16
+ for (const entry of entries){
17
+ const fullPath = join(dir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ results.push(...findHtmlFiles(fullPath));
20
+ } else if (entry.name.endsWith('.html')) {
21
+ results.push(fullPath);
22
+ }
23
+ }
24
+ } catch {
25
+ // Directory may not exist yet
26
+ }
27
+ return results;
28
+ }
29
+ // ─── Route helpers ─────────────────────────────────────────────
30
+ /**
31
+ * Resolve a dynamic route path by substituting param values.
32
+ * Validates param values to prevent path traversal and control characters.
33
+ */ export function resolveDynamicRoutePath(routePath, paramNames, params) {
34
+ let resolvedPath = routePath;
35
+ for (const name of paramNames){
36
+ const raw = params[name];
37
+ if (raw === undefined || raw === null || raw === '') {
38
+ throw new Error(`Missing value for route parameter "${name}" in ${routePath}`);
39
+ }
40
+ const value = String(raw);
41
+ if (value === '.' || value === '..' || /[\\/]/.test(value)) {
42
+ throw new Error(`Unsafe value for route parameter "${name}" in ${routePath}: ${value}`);
43
+ }
44
+ // Encode spaces and other URL-unsafe chars, but preserve @ for scoped packages.
45
+ // Full encodeURIComponent would encode @ -> %40, breaking file-to-URL matching.
46
+ const safeValue = value.replace(/ /g, '%20');
47
+ resolvedPath = resolvedPath.replace(`:${name}`, safeValue);
48
+ }
49
+ return resolvedPath;
50
+ }
51
+ // ─── Hash helpers ──────────────────────────────────────────────
52
+ /**
53
+ * Stable SHA-256 hash for SSG-generated asset names.
54
+ * Returns a deterministic lowercase hex string.
55
+ */ export async function stableHash(str) {
56
+ const encoder = new TextEncoder();
57
+ const digest = await crypto.subtle.digest('SHA-256', encoder.encode(str));
58
+ return Array.from(new Uint8Array(digest)).map((b)=>b.toString(16).padStart(2, '0')).join('');
59
+ }
60
+ // ─── ISR manifest builder ──────────────────────────────────────
61
+ export function buildIsrManifestEntries(routeInfo, staticPathParamsByRoute) {
62
+ const entries = [];
63
+ for (const route of routeInfo){
64
+ const revalidate = typeof route.revalidate === 'number' && route.revalidate > 0 ? route.revalidate : undefined;
65
+ if (!revalidate) continue;
66
+ const paramsList = route.isDynamic ? staticPathParamsByRoute.get(route.path) ?? [] : [
67
+ route.params ?? {}
68
+ ];
69
+ for (const params of paramsList){
70
+ entries.push({
71
+ path: route.path,
72
+ revalidate,
73
+ cacheKey: createIsrCacheKey(route.path, params),
74
+ params
75
+ });
76
+ }
77
+ }
78
+ return entries;
79
+ }
80
+ /**
81
+ * Collect per-page render diagnostics (backward-compat with string output).
82
+ * Returns the rendered HTML string.
83
+ */ export function collectPageOutput(routePath, output, pageDiagnostics) {
84
+ const html = typeof output === 'string' ? output : output.html;
85
+ if (typeof output !== 'string') {
86
+ pageDiagnostics.push({
87
+ path: routePath,
88
+ errors: output.errors,
89
+ hydrationHints: output.hydrationHints,
90
+ componentCount: output.componentCount,
91
+ renderTimeMs: output.renderTimeMs
92
+ });
93
+ }
94
+ return html;
95
+ }
96
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3NzZy1oZWxwZXJzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQG9wZW5lbGVtZW50L3NzZyAtIFNTRyBoZWxwZXIgdXRpbGl0aWVzXG4gKlxuICogUHVyZSB1dGlsaXR5IGZ1bmN0aW9ucyB1c2VkIGJ5IHRoZSBTU0cgcmVuZGVyIHBpcGVsaW5lLlxuICogVGhpcyBtb2R1bGUgc2l0cyBhdCB0aGUgYm90dG9tIG9mIHRoZSBkZXBlbmRlbmN5IGdyYXBoLlxuICovXG5cbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgcmVhZGRpclN5bmMgfSBmcm9tICdub2RlOmZzJztcbmltcG9ydCB0eXBlIHsgSHlkcmF0aW9uSGludCwgUmVuZGVyRXJyb3IgfSBmcm9tICdAb3BlbmVsZW1lbnQvcHJvdG9jb2wvcmVuZGVyJztcbmltcG9ydCB0eXBlIHsgSXNyTWFuaWZlc3RFbnRyeSB9IGZyb20gJ0BvcGVuZWxlbWVudC9wcm90b2NvbC9mcmFtZXdvcmsnO1xuaW1wb3J0IHsgY3JlYXRlSXNyQ2FjaGVLZXkgfSBmcm9tICdAb3BlbmVsZW1lbnQvY29yZS9pc3InO1xuXG4vLyDilIDilIDilIAgUGF0aCAvIFVSTCBoZWxwZXJzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4vKiogUmVjdXJzaXZlbHkgZmluZCBhbGwgLmh0bWwgZmlsZXMgdW5kZXIgYSBkaXJlY3RvcnkuICovXG5leHBvcnQgZnVuY3Rpb24gZmluZEh0bWxGaWxlcyhkaXI6IHN0cmluZyk6IHN0cmluZ1tdIHtcbiAgY29uc3QgcmVzdWx0czogc3RyaW5nW10gPSBbXTtcbiAgdHJ5IHtcbiAgICBjb25zdCBlbnRyaWVzID0gcmVhZGRpclN5bmMoZGlyLCB7IHdpdGhGaWxlVHlwZXM6IHRydWUgfSk7XG4gICAgZm9yIChjb25zdCBlbnRyeSBvZiBlbnRyaWVzKSB7XG4gICAgICBjb25zdCBmdWxsUGF0aCA9IGpvaW4oZGlyLCBlbnRyeS5uYW1lKTtcbiAgICAgIGlmIChlbnRyeS5pc0RpcmVjdG9yeSgpKSB7XG4gICAgICAgIHJlc3VsdHMucHVzaCguLi5maW5kSHRtbEZpbGVzKGZ1bGxQYXRoKSk7XG4gICAgICB9IGVsc2UgaWYgKGVudHJ5Lm5hbWUuZW5kc1dpdGgoJy5odG1sJykpIHtcbiAgICAgICAgcmVzdWx0cy5wdXNoKGZ1bGxQYXRoKTtcbiAgICAgIH1cbiAgICB9XG4gIH0gY2F0Y2gge1xuICAgIC8vIERpcmVjdG9yeSBtYXkgbm90IGV4aXN0IHlldFxuICB9XG4gIHJldHVybiByZXN1bHRzO1xufVxuXG4vLyDilIDilIDilIAgUm91dGUgaGVscGVycyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcblxuLyoqXG4gKiBSZXNvbHZlIGEgZHluYW1pYyByb3V0ZSBwYXRoIGJ5IHN1YnN0aXR1dGluZyBwYXJhbSB2YWx1ZXMuXG4gKiBWYWxpZGF0ZXMgcGFyYW0gdmFsdWVzIHRvIHByZXZlbnQgcGF0aCB0cmF2ZXJzYWwgYW5kIGNvbnRyb2wgY2hhcmFjdGVycy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVEeW5hbWljUm91dGVQYXRoKFxuICByb3V0ZVBhdGg6IHN0cmluZyxcbiAgcGFyYW1OYW1lczogc3RyaW5nW10sXG4gIHBhcmFtczogUmVjb3JkPHN0cmluZywgc3RyaW5nPixcbik6IHN0cmluZyB7XG4gIGxldCByZXNvbHZlZFBhdGggPSByb3V0ZVBhdGg7XG4gIGZvciAoY29uc3QgbmFtZSBvZiBwYXJhbU5hbWVzKSB7XG4gICAgY29uc3QgcmF3ID0gcGFyYW1zW25hbWVdO1xuICAgIGlmIChyYXcgPT09IHVuZGVmaW5lZCB8fCByYXcgPT09IG51bGwgfHwgcmF3ID09PSAnJykge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgTWlzc2luZyB2YWx1ZSBmb3Igcm91dGUgcGFyYW1ldGVyIFwiJHtuYW1lfVwiIGluICR7cm91dGVQYXRofWAsXG4gICAgICApO1xuICAgIH1cblxuICAgIGNvbnN0IHZhbHVlID0gU3RyaW5nKHJhdyk7XG4gICAgaWYgKFxuICAgICAgdmFsdWUgPT09ICcuJyB8fFxuICAgICAgdmFsdWUgPT09ICcuLicgfHxcbiAgICAgIC9bXFxcXC9dLy50ZXN0KHZhbHVlKVxuICAgICkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgVW5zYWZlIHZhbHVlIGZvciByb3V0ZSBwYXJhbWV0ZXIgXCIke25hbWV9XCIgaW4gJHtyb3V0ZVBhdGh9OiAke3ZhbHVlfWAsXG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIEVuY29kZSBzcGFjZXMgYW5kIG90aGVyIFVSTC11bnNhZmUgY2hhcnMsIGJ1dCBwcmVzZXJ2ZSBAIGZvciBzY29wZWQgcGFja2FnZXMuXG4gICAgLy8gRnVsbCBlbmNvZGVVUklDb21wb25lbnQgd291bGQgZW5jb2RlIEAgLT4gJTQwLCBicmVha2luZyBmaWxlLXRvLVVSTCBtYXRjaGluZy5cbiAgICBjb25zdCBzYWZlVmFsdWUgPSB2YWx1ZS5yZXBsYWNlKC8gL2csICclMjAnKTtcbiAgICByZXNvbHZlZFBhdGggPSByZXNvbHZlZFBhdGgucmVwbGFjZShgOiR7bmFtZX1gLCBzYWZlVmFsdWUpO1xuICB9XG4gIHJldHVybiByZXNvbHZlZFBhdGg7XG59XG5cbi8vIOKUgOKUgOKUgCBIYXNoIGhlbHBlcnMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbi8qKlxuICogU3RhYmxlIFNIQS0yNTYgaGFzaCBmb3IgU1NHLWdlbmVyYXRlZCBhc3NldCBuYW1lcy5cbiAqIFJldHVybnMgYSBkZXRlcm1pbmlzdGljIGxvd2VyY2FzZSBoZXggc3RyaW5nLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhYmxlSGFzaChzdHI6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gIGNvbnN0IGVuY29kZXIgPSBuZXcgVGV4dEVuY29kZXIoKTtcbiAgY29uc3QgZGlnZXN0ID0gYXdhaXQgY3J5cHRvLnN1YnRsZS5kaWdlc3QoJ1NIQS0yNTYnLCBlbmNvZGVyLmVuY29kZShzdHIpKTtcbiAgcmV0dXJuIEFycmF5LmZyb20obmV3IFVpbnQ4QXJyYXkoZGlnZXN0KSlcbiAgICAubWFwKChiKSA9PiBiLnRvU3RyaW5nKDE2KS5wYWRTdGFydCgyLCAnMCcpKVxuICAgIC5qb2luKCcnKTtcbn1cblxuLy8g4pSA4pSA4pSAIElTUiBtYW5pZmVzdCBidWlsZGVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRJc3JNYW5pZmVzdEVudHJpZXMoXG4gIHJvdXRlSW5mbzogQXJyYXk8e1xuICAgIHBhdGg6IHN0cmluZztcbiAgICBpc0R5bmFtaWM6IGJvb2xlYW47XG4gICAgcmV2YWxpZGF0ZT86IG51bWJlcjtcbiAgICBwYXJhbXM/OiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+O1xuICB9PixcbiAgc3RhdGljUGF0aFBhcmFtc0J5Um91dGU6IE1hcDxzdHJpbmcsIEFycmF5PFJlY29yZDxzdHJpbmcsIHN0cmluZz4+Pixcbik6IElzck1hbmlmZXN0RW50cnlbXSB7XG4gIGNvbnN0IGVudHJpZXM6IElzck1hbmlmZXN0RW50cnlbXSA9IFtdO1xuICBmb3IgKGNvbnN0IHJvdXRlIG9mIHJvdXRlSW5mbykge1xuICAgIGNvbnN0IHJldmFsaWRhdGUgPSB0eXBlb2Ygcm91dGUucmV2YWxpZGF0ZSA9PT0gJ251bWJlcicgJiYgcm91dGUucmV2YWxpZGF0ZSA+IDBcbiAgICAgID8gcm91dGUucmV2YWxpZGF0ZVxuICAgICAgOiB1bmRlZmluZWQ7XG4gICAgaWYgKCFyZXZhbGlkYXRlKSBjb250aW51ZTtcblxuICAgIGNvbnN0IHBhcmFtc0xpc3QgPSByb3V0ZS5pc0R5bmFtaWNcbiAgICAgID8gc3RhdGljUGF0aFBhcmFtc0J5Um91dGUuZ2V0KHJvdXRlLnBhdGgpID8/IFtdXG4gICAgICA6IFtyb3V0ZS5wYXJhbXMgPz8ge31dO1xuXG4gICAgZm9yIChjb25zdCBwYXJhbXMgb2YgcGFyYW1zTGlzdCkge1xuICAgICAgZW50cmllcy5wdXNoKHtcbiAgICAgICAgcGF0aDogcm91dGUucGF0aCxcbiAgICAgICAgcmV2YWxpZGF0ZSxcbiAgICAgICAgY2FjaGVLZXk6IGNyZWF0ZUlzckNhY2hlS2V5KHJvdXRlLnBhdGgsIHBhcmFtcyksXG4gICAgICAgIHBhcmFtcyxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICByZXR1cm4gZW50cmllcztcbn1cblxuLy8g4pSA4pSA4pSAIFBlci1wYWdlIGRpYWdub3N0aWMgY29sbGVjdG9yIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4vKiogUGFnZS1sZXZlbCByZW5kZXIgZGlhZ25vc3RpYyBlbnRyeSB1c2VkIGR1cmluZyBTU0cgcmVuZGVyLiAqL1xuZXhwb3J0IGludGVyZmFjZSBQYWdlRGlhZ25vc3RpYyB7XG4gIHBhdGg6IHN0cmluZztcbiAgZXJyb3JzOiBSZW5kZXJFcnJvcltdO1xuICBoeWRyYXRpb25IaW50czogSHlkcmF0aW9uSGludFtdO1xuICBjb21wb25lbnRDb3VudDogbnVtYmVyO1xuICByZW5kZXJUaW1lTXM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBDb2xsZWN0IHBlci1wYWdlIHJlbmRlciBkaWFnbm9zdGljcyAoYmFja3dhcmQtY29tcGF0IHdpdGggc3RyaW5nIG91dHB1dCkuXG4gKiBSZXR1cm5zIHRoZSByZW5kZXJlZCBIVE1MIHN0cmluZy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvbGxlY3RQYWdlT3V0cHV0KFxuICByb3V0ZVBhdGg6IHN0cmluZyxcbiAgb3V0cHV0OiB7XG4gICAgaHRtbDogc3RyaW5nO1xuICAgIGVycm9yczogUmVuZGVyRXJyb3JbXTtcbiAgICBoeWRyYXRpb25IaW50czogSHlkcmF0aW9uSGludFtdO1xuICAgIGNvbXBvbmVudENvdW50OiBudW1iZXI7XG4gICAgcmVuZGVyVGltZU1zOiBudW1iZXI7XG4gIH0gfCBzdHJpbmcsXG4gIHBhZ2VEaWFnbm9zdGljczogUGFnZURpYWdub3N0aWNbXSxcbik6IHN0cmluZyB7XG4gIGNvbnN0IGh0bWwgPSB0eXBlb2Ygb3V0cHV0ID09PSAnc3RyaW5nJyA/IG91dHB1dCA6IG91dHB1dC5odG1sO1xuICBpZiAodHlwZW9mIG91dHB1dCAhPT0gJ3N0cmluZycpIHtcbiAgICBwYWdlRGlhZ25vc3RpY3MucHVzaCh7XG4gICAgICBwYXRoOiByb3V0ZVBhdGgsXG4gICAgICBlcnJvcnM6IG91dHB1dC5lcnJvcnMsXG4gICAgICBoeWRyYXRpb25IaW50czogb3V0cHV0Lmh5ZHJhdGlvbkhpbnRzLFxuICAgICAgY29tcG9uZW50Q291bnQ6IG91dHB1dC5jb21wb25lbnRDb3VudCxcbiAgICAgIHJlbmRlclRpbWVNczogb3V0cHV0LnJlbmRlclRpbWVNcyxcbiAgICB9KTtcbiAgfVxuICByZXR1cm4gaHRtbDtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFB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
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @openelement/ssg - i18n locale expansion
3
+ *
4
+ * Expands SSG output for each locale when i18n options are provided.
5
+ * Renders each route for every locale and writes locale-prefixed output.
6
+ */ import { join } from 'node:path';
7
+ import { mkdirSync, writeFileSync } from 'node:fs';
8
+ import { createLogger } from '@openelement/core/logger';
9
+ import { formatError } from '@openelement/core/errors';
10
+ import { collectPageOutput, resolveDynamicRoutePath } from './ssg-helpers.js';
11
+ const log = createLogger('ssg');
12
+ /**
13
+ * Expand rendered pages for each locale when i18n is configured.
14
+ *
15
+ * For each locale and each route, re-renders the route with the locale
16
+ * parameter and writes output under /{locale}/{path}/index.html.
17
+ */ export async function expandI18nLocales(evidence, renderRoute, routeInfo, getStaticPaths, options, root, outDir, pageDiagnostics) {
18
+ const i18nOpts = evidence.i18nOptions || null;
19
+ if (!i18nOpts || !renderRoute) return;
20
+ const locales = i18nOpts.locales || [];
21
+ if (locales.length <= 1) return;
22
+ log.info(`i18n: expanding for locales: ${locales.join(', ')}`);
23
+ for (const locale of locales){
24
+ for (const route of routeInfo){
25
+ let paramsList;
26
+ if (!route.isDynamic) {
27
+ paramsList = [
28
+ {}
29
+ ];
30
+ } else if (getStaticPaths) {
31
+ try {
32
+ paramsList = await getStaticPaths(route.path);
33
+ } catch {
34
+ log.warn(`i18n: getStaticPaths failed for ${route.path}, skipping`);
35
+ continue;
36
+ }
37
+ } else {
38
+ continue;
39
+ }
40
+ if (paramsList.length === 0) continue;
41
+ const paramNames = route.paramNames;
42
+ for (const params of paramsList){
43
+ let resolvedPath;
44
+ try {
45
+ resolvedPath = resolveDynamicRoutePath(route.path, paramNames, params);
46
+ } catch (e) {
47
+ log.warn(`i18n: skipping unsafe dynamic route ${route.path}: ${formatError(e)}`);
48
+ continue;
49
+ }
50
+ const pathSegment = resolvedPath.split('/')[1] || '';
51
+ if (locales.includes(pathSegment)) {
52
+ continue;
53
+ }
54
+ const localePath = '/' + locale + '/' + resolvedPath.replace(/^\//, '');
55
+ try {
56
+ const output = await renderRoute(route.path, {
57
+ params,
58
+ locale,
59
+ title: options.html?.title,
60
+ lang: locale,
61
+ headExtras: options.headExtras
62
+ });
63
+ const html = collectPageOutput(localePath, output, pageDiagnostics);
64
+ const outputDir = join(root, outDir);
65
+ const pageDir = join(outputDir, localePath);
66
+ mkdirSync(pageDir, {
67
+ recursive: true
68
+ });
69
+ writeFileSync(join(pageDir, 'index.html'), html, 'utf-8');
70
+ log.info(`i18n: ${localePath}/index.html`);
71
+ } catch (e) {
72
+ log.warn(`i18n: failed for locale ${locale} on ${resolvedPath}: ${formatError(e)}`);
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3NzZy1pMThuLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQG9wZW5lbGVtZW50L3NzZyAtIGkxOG4gbG9jYWxlIGV4cGFuc2lvblxuICpcbiAqIEV4cGFuZHMgU1NHIG91dHB1dCBmb3IgZWFjaCBsb2NhbGUgd2hlbiBpMThuIG9wdGlvbnMgYXJlIHByb3ZpZGVkLlxuICogUmVuZGVycyBlYWNoIHJvdXRlIGZvciBldmVyeSBsb2NhbGUgYW5kIHdyaXRlcyBsb2NhbGUtcHJlZml4ZWQgb3V0cHV0LlxuICovXG5cbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgbWtkaXJTeW5jLCB3cml0ZUZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgdHlwZSB7IFNzZ1BhZ2VPdXRwdXQsIFNzZ1JlbmRlckV2aWRlbmNlLCBTc2dSZW5kZXJPcHRpb25zIH0gZnJvbSAnQG9wZW5lbGVtZW50L3Byb3RvY29sL3NzZyc7XG5pbXBvcnQgeyBjcmVhdGVMb2dnZXIgfSBmcm9tICdAb3BlbmVsZW1lbnQvY29yZS9sb2dnZXInO1xuaW1wb3J0IHsgZm9ybWF0RXJyb3IgfSBmcm9tICdAb3BlbmVsZW1lbnQvY29yZS9lcnJvcnMnO1xuaW1wb3J0IHsgY29sbGVjdFBhZ2VPdXRwdXQsIHR5cGUgUGFnZURpYWdub3N0aWMsIHJlc29sdmVEeW5hbWljUm91dGVQYXRoIH0gZnJvbSAnLi9zc2ctaGVscGVycy5qcyc7XG5cbmNvbnN0IGxvZyA9IGNyZWF0ZUxvZ2dlcignc3NnJyk7XG5cbmludGVyZmFjZSBSb3V0ZUluZm9JdGVtIHtcbiAgcGF0aDogc3RyaW5nO1xuICB0YWdOYW1lOiBzdHJpbmc7XG4gIGlzRHluYW1pYzogYm9vbGVhbjtcbiAgcGFyYW1OYW1lczogc3RyaW5nW107XG4gIHJldmFsaWRhdGU/OiBudW1iZXI7XG59XG5cbi8qKlxuICogRXhwYW5kIHJlbmRlcmVkIHBhZ2VzIGZvciBlYWNoIGxvY2FsZSB3aGVuIGkxOG4gaXMgY29uZmlndXJlZC5cbiAqXG4gKiBGb3IgZWFjaCBsb2NhbGUgYW5kIGVhY2ggcm91dGUsIHJlLXJlbmRlcnMgdGhlIHJvdXRlIHdpdGggdGhlIGxvY2FsZVxuICogcGFyYW1ldGVyIGFuZCB3cml0ZXMgb3V0cHV0IHVuZGVyIC97bG9jYWxlfS97cGF0aH0vaW5kZXguaHRtbC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGV4cGFuZEkxOG5Mb2NhbGVzKFxuICBldmlkZW5jZTogU3NnUmVuZGVyRXZpZGVuY2UsXG4gIHJlbmRlclJvdXRlOlxuICAgIHwgKChwYXRoOiBzdHJpbmcsIG9wdHM/OiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikgPT4gUHJvbWlzZTxTc2dQYWdlT3V0cHV0PilcbiAgICB8IHVuZGVmaW5lZCxcbiAgcm91dGVJbmZvOiBSb3V0ZUluZm9JdGVtW10sXG4gIGdldFN0YXRpY1BhdGhzOlxuICAgIHwgKChwYXRoOiBzdHJpbmcpID0+IFByb21pc2U8QXJyYXk8UmVjb3JkPHN0cmluZywgc3RyaW5nPj4+KVxuICAgIHwgdW5kZWZpbmVkLFxuICBvcHRpb25zOiBTc2dSZW5kZXJPcHRpb25zLFxuICByb290OiBzdHJpbmcsXG4gIG91dERpcjogc3RyaW5nLFxuICBwYWdlRGlhZ25vc3RpY3M6IFBhZ2VEaWFnbm9zdGljW10sXG4pOiBQcm9taXNlPHZvaWQ+IHtcbiAgY29uc3QgaTE4bk9wdHMgPSBldmlkZW5jZS5pMThuT3B0aW9ucyB8fCBudWxsO1xuICBpZiAoIWkxOG5PcHRzIHx8ICFyZW5kZXJSb3V0ZSkgcmV0dXJuO1xuXG4gIGNvbnN0IGxvY2FsZXM6IHN0cmluZ1tdID0gaTE4bk9wdHMubG9jYWxlcyB8fCBbXTtcbiAgaWYgKGxvY2FsZXMubGVuZ3RoIDw9IDEpIHJldHVybjtcblxuICBsb2cuaW5mbyhgaTE4bjogZXhwYW5kaW5nIGZvciBsb2NhbGVzOiAke2xvY2FsZXMuam9pbignLCAnKX1gKTtcbiAgZm9yIChjb25zdCBsb2NhbGUgb2YgbG9jYWxlcykge1xuICAgIGZvciAoY29uc3Qgcm91dGUgb2Ygcm91dGVJbmZvKSB7XG4gICAgICBsZXQgcGFyYW1zTGlzdDogQXJyYXk8UmVjb3JkPHN0cmluZywgc3RyaW5nPj47XG4gICAgICBpZiAoIXJvdXRlLmlzRHluYW1pYykge1xuICAgICAgICBwYXJhbXNMaXN0ID0gW3t9XTtcbiAgICAgIH0gZWxzZSBpZiAoZ2V0U3RhdGljUGF0aHMpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBwYXJhbXNMaXN0ID0gYXdhaXQgZ2V0U3RhdGljUGF0aHMocm91dGUucGF0aCk7XG4gICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgIGxvZy53YXJuKFxuICAgICAgICAgICAgYGkxOG46IGdldFN0YXRpY1BhdGhzIGZhaWxlZCBmb3IgJHtyb3V0ZS5wYXRofSwgc2tpcHBpbmdgLFxuICAgICAgICAgICk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGNvbnRpbnVlO1xuICAgICAgfVxuICAgICAgaWYgKHBhcmFtc0xpc3QubGVuZ3RoID09PSAwKSBjb250aW51ZTtcblxuICAgICAgY29uc3QgcGFyYW1OYW1lcyA9IHJvdXRlLnBhcmFtTmFtZXM7XG4gICAgICBmb3IgKGNvbnN0IHBhcmFtcyBvZiBwYXJhbXNMaXN0KSB7XG4gICAgICAgIGxldCByZXNvbHZlZFBhdGg6IHN0cmluZztcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICByZXNvbHZlZFBhdGggPSByZXNvbHZlRHluYW1pY1JvdXRlUGF0aChcbiAgICAgICAgICAgIHJvdXRlLnBhdGgsXG4gICAgICAgICAgICBwYXJhbU5hbWVzLFxuICAgICAgICAgICAgcGFyYW1zLFxuICAgICAgICAgICk7XG4gICAgICAgIH0gY2F0Y2ggKGUpIHtcbiAgICAgICAgICBsb2cud2FybihcbiAgICAgICAgICAgIGBpMThuOiBza2lwcGluZyB1bnNhZmUgZHluYW1pYyByb3V0ZSAke3JvdXRlLnBhdGh9OiAke2Zvcm1hdEVycm9yKGUpfWAsXG4gICAgICAgICAgKTtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBwYXRoU2VnbWVudCA9IHJlc29sdmVkUGF0aC5zcGxpdCgnLycpWzFdIHx8ICcnO1xuICAgICAgICBpZiAobG9jYWxlcy5pbmNsdWRlcyhwYXRoU2VnbWVudCkpIHtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICBjb25zdCBsb2NhbGVQYXRoID0gJy8nICsgbG9jYWxlICsgJy8nICsgcmVzb2x2ZWRQYXRoLnJlcGxhY2UoL15cXC8vLCAnJyk7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgcmVuZGVyUm91dGUocm91dGUucGF0aCwge1xuICAgICAgICAgICAgcGFyYW1zLFxuICAgICAgICAgICAgbG9jYWxlLFxuICAgICAgICAgICAgdGl0bGU6IG9wdGlvbnMuaHRtbD8udGl0bGUsXG4gICAgICAgICAgICBsYW5nOiBsb2NhbGUsXG4gICAgICAgICAgICBoZWFkRXh0cmFzOiBvcHRpb25zLmhlYWRFeHRyYXMsXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgY29uc3QgaHRtbCA9IGNvbGxlY3RQYWdlT3V0cHV0KGxvY2FsZVBhdGgsIG91dHB1dCwgcGFnZURpYWdub3N0aWNzKTtcbiAgICAgICAgICBjb25zdCBvdXRwdXREaXIgPSBqb2luKHJvb3QsIG91dERpcik7XG4gICAgICAgICAgY29uc3QgcGFnZURpciA9IGpvaW4ob3V0cHV0RGlyLCBsb2NhbGVQYXRoKTtcbiAgICAgICAgICBta2RpclN5bmMocGFnZURpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICAgICAgd3JpdGVGaWxlU3luYyhqb2luKHBhZ2VEaXIsICdpbmRleC5odG1sJyksIGh0bWwsICd1dGYtOCcpO1xuICAgICAgICAgIGxvZy5pbmZvKGBpMThuOiAke2xvY2FsZVBhdGh9L2luZGV4Lmh0bWxgKTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgIGxvZy53YXJuKFxuICAgICAgICAgICAgYGkxOG46IGZhaWxlZCBmb3IgbG9jYWxlICR7bG9jYWxlfSBvbiAke3Jlc29sdmVkUGF0aH06ICR7Zm9ybWF0RXJyb3IoZSl9YCxcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9XG59XG4iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0NBS0MsR0FFRCxTQUFTLElBQUksUUFBUSxZQUFZO0FBQ2pDLFNBQVMsU0FBUyxFQUFFLGFBQWEsUUFBUSxVQUFVO0FBRW5ELFNBQVMsWUFBWSxRQUFRLDJCQUEyQjtBQUN4RCxTQUFTLFdBQVcsUUFBUSwyQkFBMkI7QUFDdkQsU0FBUyxpQkFBaUIsRUFBdUIsdUJBQXVCLFFBQVEsbUJBQW1CO0FBRW5HLE1BQU0sTUFBTSxhQUFhO0FBVXpCOzs7OztDQUtDLEdBQ0QsT0FBTyxlQUFlLGtCQUNwQixRQUEyQixFQUMzQixXQUVhLEVBQ2IsU0FBMEIsRUFDMUIsY0FFYSxFQUNiLE9BQXlCLEVBQ3pCLElBQVksRUFDWixNQUFjLEVBQ2QsZUFBaUM7RUFFakMsTUFBTSxXQUFXLFNBQVMsV0FBVyxJQUFJO0VBQ3pDLElBQUksQ0FBQyxZQUFZLENBQUMsYUFBYTtFQUUvQixNQUFNLFVBQW9CLFNBQVMsT0FBTyxJQUFJLEVBQUU7RUFDaEQsSUFBSSxRQUFRLE1BQU0sSUFBSSxHQUFHO0VBRXpCLElBQUksSUFBSSxDQUFDLENBQUMsNkJBQTZCLEVBQUUsUUFBUSxJQUFJLENBQUMsT0FBTztFQUM3RCxLQUFLLE1BQU0sVUFBVSxRQUFTO0lBQzVCLEtBQUssTUFBTSxTQUFTLFVBQVc7TUFDN0IsSUFBSTtNQUNKLElBQUksQ0FBQyxNQUFNLFNBQVMsRUFBRTtRQUNwQixhQUFhO1VBQUMsQ0FBQztTQUFFO01BQ25CLE9BQU8sSUFBSSxnQkFBZ0I7UUFDekIsSUFBSTtVQUNGLGFBQWEsTUFBTSxlQUFlLE1BQU0sSUFBSTtRQUM5QyxFQUFFLE9BQU07VUFDTixJQUFJLElBQUksQ0FDTixDQUFDLGdDQUFnQyxFQUFFLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQztVQUUzRDtRQUNGO01BQ0YsT0FBTztRQUNMO01BQ0Y7TUFDQSxJQUFJLFdBQVcsTUFBTSxLQUFLLEdBQUc7TUFFN0IsTUFBTSxhQUFhLE1BQU0sVUFBVTtNQUNuQyxLQUFLLE1BQU0sVUFBVSxXQUFZO1FBQy9CLElBQUk7UUFDSixJQUFJO1VBQ0YsZUFBZSx3QkFDYixNQUFNLElBQUksRUFDVixZQUNBO1FBRUosRUFBRSxPQUFPLEdBQUc7VUFDVixJQUFJLElBQUksQ0FDTixDQUFDLG9DQUFvQyxFQUFFLE1BQU0sSUFBSSxDQUFDLEVBQUUsRUFBRSxZQUFZLElBQUk7VUFFeEU7UUFDRjtRQUNBLE1BQU0sY0FBYyxhQUFhLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxJQUFJO1FBQ2xELElBQUksUUFBUSxRQUFRLENBQUMsY0FBYztVQUNqQztRQUNGO1FBQ0EsTUFBTSxhQUFhLE1BQU0sU0FBUyxNQUFNLGFBQWEsT0FBTyxDQUFDLE9BQU87UUFDcEUsSUFBSTtVQUNGLE1BQU0sU0FBUyxNQUFNLFlBQVksTUFBTSxJQUFJLEVBQUU7WUFDM0M7WUFDQTtZQUNBLE9BQU8sUUFBUSxJQUFJLEVBQUU7WUFDckIsTUFBTTtZQUNOLFlBQVksUUFBUSxVQUFVO1VBQ2hDO1VBQ0EsTUFBTSxPQUFPLGtCQUFrQixZQUFZLFFBQVE7VUFDbkQsTUFBTSxZQUFZLEtBQUssTUFBTTtVQUM3QixNQUFNLFVBQVUsS0FBSyxXQUFXO1VBQ2hDLFVBQVUsU0FBUztZQUFFLFdBQVc7VUFBSztVQUNyQyxjQUFjLEtBQUssU0FBUyxlQUFlLE1BQU07VUFDakQsSUFBSSxJQUFJLENBQUMsQ0FBQyxNQUFNLEVBQUUsV0FBVyxXQUFXLENBQUM7UUFDM0MsRUFBRSxPQUFPLEdBQUc7VUFDVixJQUFJLElBQUksQ0FDTixDQUFDLHdCQUF3QixFQUFFLE9BQU8sSUFBSSxFQUFFLGFBQWEsRUFBRSxFQUFFLFlBQVksSUFBSTtRQUU3RTtNQUNGO0lBQ0Y7RUFDRjtBQUNGIn0=
@@ -0,0 +1,3 @@
1
+ import type { SsgRenderEvidence, SsgRenderOptions, SsrBundle } from '@openelement/protocol/ssg';
2
+ export declare function ssgRender(module: SsrBundle, options: SsgRenderOptions, evidence?: SsgRenderEvidence): Promise<void>;
3
+ export { resolveDynamicRoutePath } from "./ssg-helpers.js";
@@ -0,0 +1,162 @@
1
+ /**
2
+ * adapter-vite internal SSG render pipeline (ADR 0022).
3
+ *
4
+ * Shared SSG rendering logic used by both:
5
+ * - build-ssg.ts (Vite inline mode, called from closeBundle)
6
+ * - ssg.ts (standalone CLI, loads SSR bundle via importmap)
7
+ *
8
+ * This module has zero Vite dependency - it only needs the SSR bundle module.
9
+ *
10
+ * Thin orchestrator that imports focused sub-modules for:
11
+ * - Dynamic route expansion (ssg-dynamic.ts)
12
+ * - i18n locale expansion (ssg-i18n.ts)
13
+ * - DSD report assembly (ssg-report.ts)
14
+ * - Utility helpers (ssg-helpers.ts)
15
+ */ import { join } from 'node:path';
16
+ import process from 'node:process';
17
+ import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
18
+ import { createLogger } from '@openelement/core/logger';
19
+ import { expandDynamicRoutes } from './ssg-dynamic.js';
20
+ import { expandI18nLocales } from './ssg-i18n.js';
21
+ import { assembleDsdReport, writeDsdReport } from './ssg-report.js';
22
+ import { buildIsrManifestEntries, findHtmlFiles } from './ssg-helpers.js';
23
+ import { writeJson } from '@openelement/content/write-json';
24
+ const log = createLogger('ssg');
25
+ // ─── Core render pipeline ──────────────────────────────────────
26
+ export async function ssgRender(module, options, evidence = {}) {
27
+ const root = options.root || process.cwd();
28
+ const outDir = options.outDir || 'dist';
29
+ const basePath = options.base || '/';
30
+ // ── Report collection (v0.15.3: dsd-report.json) ──────────────
31
+ const pageDiagnostics = [];
32
+ // ── Dynamic route expansion via bundle.getStaticPaths() ──────
33
+ const routeInfo = module.routeInfo ?? [];
34
+ const renderRoute = module.renderRoute;
35
+ const getStaticPaths = module.getStaticPaths;
36
+ const dynamicRoutes = routeInfo.filter((r)=>r.isDynamic);
37
+ log.info(`Routes: ${routeInfo.length} total` + (dynamicRoutes.length > 0 ? ` (${dynamicRoutes.length} dynamic: ${dynamicRoutes.map((r)=>r.path).join(', ')})` : ''));
38
+ const staticPathParamsByRoute = await expandDynamicRoutes(dynamicRoutes, renderRoute, getStaticPaths, options, root, outDir, pageDiagnostics);
39
+ // ── Main SSG via Hono's toSSG() ────────────────────────────
40
+ const { toSSG } = await import('hono/ssg');
41
+ const nodeFs = await import('node:fs/promises');
42
+ const nodePath = await import('node:path');
43
+ const fsModule = {
44
+ writeFile: async (path, data)=>{
45
+ const dir = nodePath.dirname(path);
46
+ await nodeFs.mkdir(dir, {
47
+ recursive: true
48
+ }).catch(()=>{});
49
+ await nodeFs.writeFile(path, data);
50
+ },
51
+ mkdir: async (path)=>{
52
+ await nodeFs.mkdir(path, {
53
+ recursive: true
54
+ }).catch(()=>{});
55
+ },
56
+ isDirectory: async (path)=>{
57
+ try {
58
+ return (await nodeFs.stat(path)).isDirectory();
59
+ } catch {
60
+ return false;
61
+ }
62
+ }
63
+ };
64
+ const outputDir = join(root, outDir);
65
+ const app = module.default;
66
+ if (!app) {
67
+ throw new Error('SSR bundle loaded but no Hono app found (no default export)');
68
+ }
69
+ const result = await toSSG(app, fsModule, {
70
+ dir: outputDir
71
+ });
72
+ if (!result.success) throw result.error;
73
+ const isrRoutes = buildIsrManifestEntries(routeInfo, staticPathParamsByRoute);
74
+ if (isrRoutes.length > 0) {
75
+ writeFileSync(join(outputDir, 'isr-manifest.json'), writeJson(isrRoutes), 'utf-8');
76
+ log.info(`ISR manifest -> ${join(outputDir, 'isr-manifest.json')} (${isrRoutes.length} route(s))`);
77
+ }
78
+ // ── Post-processing ─────────────────────────────────────────
79
+ // Rename 404/index.html -> 404.html for GitHub Pages
80
+ const _404Dir = join(outputDir, '404');
81
+ const _404Html = join(outputDir, '404.html');
82
+ const _404Index = join(_404Dir, 'index.html');
83
+ if (existsSync(_404Index)) {
84
+ if (existsSync(_404Html)) {
85
+ log.warn('404.html already exists in output dir - removing before rename');
86
+ rmSync(_404Html, {
87
+ force: true
88
+ });
89
+ }
90
+ renameSync(_404Index, _404Html);
91
+ if (existsSync(_404Dir)) {
92
+ rmSync(_404Dir, {
93
+ recursive: true,
94
+ force: true
95
+ });
96
+ }
97
+ log.info('404 page -> dist/404.html (GitHub Pages)');
98
+ }
99
+ // Convert flat HTML files to clean URLs: about.html -> about/index.html
100
+ const allHtmlFiles = findHtmlFiles(outputDir);
101
+ for (const filePath of allHtmlFiles){
102
+ const rel = nodePath.relative(outputDir, filePath);
103
+ if (rel.endsWith('index.html') || rel === '404.html') continue;
104
+ const baseName = rel.replace(/\.html$/, '');
105
+ const urlBaseName = baseName.replace(/\\/g, '/');
106
+ const dirPath = join(outputDir, baseName);
107
+ const indexPath = join(dirPath, 'index.html');
108
+ if (existsSync(dirPath)) continue;
109
+ mkdirSync(dirPath, {
110
+ recursive: true
111
+ });
112
+ renameSync(filePath, indexPath);
113
+ log.info(`Clean URL: /${urlBaseName} -> ${urlBaseName}/index.html`);
114
+ }
115
+ log.info(`Static site generated -> ${outputDir}`);
116
+ // ── i18n locale expansion (if ctx available) ────────────────
117
+ await expandI18nLocales(evidence, renderRoute, routeInfo, getStaticPaths, options, root, outDir, pageDiagnostics);
118
+ // ── Post-processing modules ─────────────────────────────────
119
+ const { buildIslandChunkMap, injectCspMeta, injectDsdPolyfill, injectViewTransitionMeta, injectSpeculationRules, buildSpeculationRulesJson } = await import('./postprocess.js');
120
+ const islandTagNames = options.islandTagNames || [];
121
+ const _islandChunkMap = buildIslandChunkMap(root, outDir, islandTagNames, basePath);
122
+ if (options.viewTransition !== false) {
123
+ injectViewTransitionMeta(outputDir);
124
+ log.info('View Transitions meta tag injected');
125
+ }
126
+ if (options.speculation) {
127
+ const specOpts = typeof options.speculation === 'boolean' ? {} : options.speculation;
128
+ const rulesJson = buildSpeculationRulesJson(specOpts, routeInfo.map((r)=>({
129
+ path: r.path,
130
+ type: 'page'
131
+ })));
132
+ if (rulesJson) {
133
+ injectSpeculationRules(outputDir, rulesJson);
134
+ log.info('Speculation Rules injected');
135
+ }
136
+ }
137
+ const cspPolicy = options.middleware?.csp?.policy;
138
+ if (cspPolicy) {
139
+ injectCspMeta(outputDir, cspPolicy, options.middleware?.csp?.reportOnly || false, options.middleware?.csp?.nonce || false);
140
+ log.info('CSP meta tag injected');
141
+ }
142
+ injectDsdPolyfill(outputDir);
143
+ log.info('DSD polyfill injected');
144
+ // ── Sitemap (via ctx) ──────────────────────────────────────
145
+ await evidence.onPrintBuildManifest?.({
146
+ root,
147
+ outDir,
148
+ phase: 3,
149
+ headExtras: options.headExtras
150
+ });
151
+ try {
152
+ await evidence.onGenerateSitemap?.(join(root, outDir));
153
+ } catch {
154
+ log.debug('Sitemap generation skipped or failed');
155
+ }
156
+ // ── dsd-report.json (v0.15.3) ──────────────────────────────────
157
+ const report = assembleDsdReport(pageDiagnostics, evidence);
158
+ writeDsdReport(outputDir, report);
159
+ }
160
+ // Re-export resolveDynamicRoutePath for consumers who import from ssg-render.ts
161
+ export { resolveDynamicRoutePath } from './ssg-helpers.js';
162
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3NzZy1yZW5kZXIudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBhZGFwdGVyLXZpdGUgaW50ZXJuYWwgU1NHIHJlbmRlciBwaXBlbGluZSAoQURSIDAwMjIpLlxuICpcbiAqIFNoYXJlZCBTU0cgcmVuZGVyaW5nIGxvZ2ljIHVzZWQgYnkgYm90aDpcbiAqICAgLSBidWlsZC1zc2cudHMgKFZpdGUgaW5saW5lIG1vZGUsIGNhbGxlZCBmcm9tIGNsb3NlQnVuZGxlKVxuICogICAtIHNzZy50cyAoc3RhbmRhbG9uZSBDTEksIGxvYWRzIFNTUiBidW5kbGUgdmlhIGltcG9ydG1hcClcbiAqXG4gKiBUaGlzIG1vZHVsZSBoYXMgemVybyBWaXRlIGRlcGVuZGVuY3kgLSBpdCBvbmx5IG5lZWRzIHRoZSBTU1IgYnVuZGxlIG1vZHVsZS5cbiAqXG4gKiBUaGluIG9yY2hlc3RyYXRvciB0aGF0IGltcG9ydHMgZm9jdXNlZCBzdWItbW9kdWxlcyBmb3I6XG4gKiAgIC0gRHluYW1pYyByb3V0ZSBleHBhbnNpb24gKHNzZy1keW5hbWljLnRzKVxuICogICAtIGkxOG4gbG9jYWxlIGV4cGFuc2lvbiAoc3NnLWkxOG4udHMpXG4gKiAgIC0gRFNEIHJlcG9ydCBhc3NlbWJseSAoc3NnLXJlcG9ydC50cylcbiAqICAgLSBVdGlsaXR5IGhlbHBlcnMgKHNzZy1oZWxwZXJzLnRzKVxuICovXG5cbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHByb2Nlc3MgZnJvbSAnbm9kZTpwcm9jZXNzJztcbmltcG9ydCB7IGV4aXN0c1N5bmMsIG1rZGlyU3luYywgcmVuYW1lU3luYywgcm1TeW5jLCB3cml0ZUZpbGVTeW5jIH0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgdHlwZSB7XG4gIFNzZ1BhZ2VPdXRwdXQsXG4gIFNzZ1JlbmRlckV2aWRlbmNlLFxuICBTc2dSZW5kZXJPcHRpb25zLFxuICBTc3JCdW5kbGUsXG59IGZyb20gJ0BvcGVuZWxlbWVudC9wcm90b2NvbC9zc2cnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQG9wZW5lbGVtZW50L2NvcmUvbG9nZ2VyJztcbmltcG9ydCB7IGV4cGFuZER5bmFtaWNSb3V0ZXMgfSBmcm9tICcuL3NzZy1keW5hbWljLmpzJztcbmltcG9ydCB7IGV4cGFuZEkxOG5Mb2NhbGVzIH0gZnJvbSAnLi9zc2ctaTE4bi5qcyc7XG5pbXBvcnQgeyBhc3NlbWJsZURzZFJlcG9ydCwgd3JpdGVEc2RSZXBvcnQgfSBmcm9tICcuL3NzZy1yZXBvcnQuanMnO1xuaW1wb3J0IHsgYnVpbGRJc3JNYW5pZmVzdEVudHJpZXMsIGZpbmRIdG1sRmlsZXMsIHR5cGUgUGFnZURpYWdub3N0aWMgfSBmcm9tICcuL3NzZy1oZWxwZXJzLmpzJztcbmltcG9ydCB7IHdyaXRlSnNvbiB9IGZyb20gJ0BvcGVuZWxlbWVudC9jb250ZW50L3dyaXRlLWpzb24nO1xuXG5jb25zdCBsb2cgPSBjcmVhdGVMb2dnZXIoJ3NzZycpO1xuXG4vLyDilIDilIDilIAgQ29yZSByZW5kZXIgcGlwZWxpbmUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzc2dSZW5kZXIoXG4gIG1vZHVsZTogU3NyQnVuZGxlLFxuICBvcHRpb25zOiBTc2dSZW5kZXJPcHRpb25zLFxuICBldmlkZW5jZTogU3NnUmVuZGVyRXZpZGVuY2UgPSB7fSxcbik6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCByb290ID0gb3B0aW9ucy5yb290IHx8IHByb2Nlc3MuY3dkKCk7XG4gIGNvbnN0IG91dERpciA9IG9wdGlvbnMub3V0RGlyIHx8ICdkaXN0JztcbiAgY29uc3QgYmFzZVBhdGggPSBvcHRpb25zLmJhc2UgfHwgJy8nO1xuXG4gIC8vIOKUgOKUgCBSZXBvcnQgY29sbGVjdGlvbiAodjAuMTUuMzogZHNkLXJlcG9ydC5qc29uKSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgY29uc3QgcGFnZURpYWdub3N0aWNzOiBQYWdlRGlhZ25vc3RpY1tdID0gW107XG5cbiAgLy8g4pSA4pSAIER5bmFtaWMgcm91dGUgZXhwYW5zaW9uIHZpYSBidW5kbGUuZ2V0U3RhdGljUGF0aHMoKSDilIDilIDilIDilIDilIDilIBcbiAgY29uc3Qgcm91dGVJbmZvID0gKG1vZHVsZS5yb3V0ZUluZm8gPz8gW10pIGFzIEFycmF5PHtcbiAgICBwYXRoOiBzdHJpbmc7XG4gICAgdGFnTmFtZTogc3RyaW5nO1xuICAgIGlzRHluYW1pYzogYm9vbGVhbjtcbiAgICBwYXJhbU5hbWVzOiBzdHJpbmdbXTtcbiAgICByZXZhbGlkYXRlPzogbnVtYmVyO1xuICB9PjtcbiAgY29uc3QgcmVuZGVyUm91dGUgPSBtb2R1bGUucmVuZGVyUm91dGUgYXNcbiAgICB8ICgocGF0aDogc3RyaW5nLCBvcHRzPzogUmVjb3JkPHN0cmluZywgdW5rbm93bj4pID0+IFByb21pc2U8U3NnUGFnZU91dHB1dD4pXG4gICAgfCB1bmRlZmluZWQ7XG4gIGNvbnN0IGdldFN0YXRpY1BhdGhzID0gbW9kdWxlLmdldFN0YXRpY1BhdGhzIGFzXG4gICAgfCAoKHBhdGg6IHN0cmluZykgPT4gUHJvbWlzZTxBcnJheTxSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+Pj4pXG4gICAgfCB1bmRlZmluZWQ7XG5cbiAgY29uc3QgZHluYW1pY1JvdXRlcyA9IHJvdXRlSW5mby5maWx0ZXIoKHIpID0+IHIuaXNEeW5hbWljKTtcbiAgbG9nLmluZm8oXG4gICAgYFJvdXRlczogJHtyb3V0ZUluZm8ubGVuZ3RofSB0b3RhbGAgK1xuICAgICAgKGR5bmFtaWNSb3V0ZXMubGVuZ3RoID4gMFxuICAgICAgICA/IGAgKCR7ZHluYW1pY1JvdXRlcy5sZW5ndGh9IGR5bmFtaWM6ICR7ZHluYW1pY1JvdXRlcy5tYXAoKHIpID0+IHIucGF0aCkuam9pbignLCAnKX0pYFxuICAgICAgICA6ICcnKSxcbiAgKTtcblxuICBjb25zdCBzdGF0aWNQYXRoUGFyYW1zQnlSb3V0ZSA9IGF3YWl0IGV4cGFuZER5bmFtaWNSb3V0ZXMoXG4gICAgZHluYW1pY1JvdXRlcyxcbiAgICByZW5kZXJSb3V0ZSxcbiAgICBnZXRTdGF0aWNQYXRocyxcbiAgICBvcHRpb25zLFxuICAgIHJvb3QsXG4gICAgb3V0RGlyLFxuICAgIHBhZ2VEaWFnbm9zdGljcyxcbiAgKTtcblxuICAvLyDilIDilIAgTWFpbiBTU0cgdmlhIEhvbm8ncyB0b1NTRygpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICBjb25zdCB7IHRvU1NHIH0gPSBhd2FpdCBpbXBvcnQoJ2hvbm8vc3NnJyk7XG4gIGNvbnN0IG5vZGVGcyA9IGF3YWl0IGltcG9ydCgnbm9kZTpmcy9wcm9taXNlcycpO1xuICBjb25zdCBub2RlUGF0aCA9IGF3YWl0IGltcG9ydCgnbm9kZTpwYXRoJyk7XG5cbiAgY29uc3QgZnNNb2R1bGUgPSB7XG4gICAgd3JpdGVGaWxlOiBhc3luYyAocGF0aDogc3RyaW5nLCBkYXRhOiBzdHJpbmcgfCBVaW50OEFycmF5KSA9PiB7XG4gICAgICBjb25zdCBkaXIgPSBub2RlUGF0aC5kaXJuYW1lKHBhdGgpO1xuICAgICAgYXdhaXQgbm9kZUZzLm1rZGlyKGRpciwgeyByZWN1cnNpdmU6IHRydWUgfSkuY2F0Y2goKCkgPT4ge30pO1xuICAgICAgYXdhaXQgbm9kZUZzLndyaXRlRmlsZShwYXRoLCBkYXRhKTtcbiAgICB9LFxuICAgIG1rZGlyOiBhc3luYyAocGF0aDogc3RyaW5nKSA9PiB7XG4gICAgICBhd2FpdCBub2RlRnMubWtkaXIocGF0aCwgeyByZWN1cnNpdmU6IHRydWUgfSkuY2F0Y2goKCkgPT4ge30pO1xuICAgIH0sXG4gICAgaXNEaXJlY3Rvcnk6IGFzeW5jIChwYXRoOiBzdHJpbmcpID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIHJldHVybiAoYXdhaXQgbm9kZUZzLnN0YXQocGF0aCkpLmlzRGlyZWN0b3J5KCk7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgIH0sXG4gIH07XG5cbiAgY29uc3Qgb3V0cHV0RGlyID0gam9pbihyb290LCBvdXREaXIpO1xuICBjb25zdCBhcHAgPSBtb2R1bGUuZGVmYXVsdCBhc1xuICAgIHwgeyBmZXRjaDogKHJlcTogUmVxdWVzdCwgLi4uYXJnczogdW5rbm93bltdKSA9PiBQcm9taXNlPFJlc3BvbnNlPiB9XG4gICAgfCB1bmRlZmluZWQ7XG4gIGlmICghYXBwKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgJ1NTUiBidW5kbGUgbG9hZGVkIGJ1dCBubyBIb25vIGFwcCBmb3VuZCAobm8gZGVmYXVsdCBleHBvcnQpJyxcbiAgICApO1xuICB9XG5cbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdG9TU0coYXBwIGFzIG5ldmVyLCBmc01vZHVsZSwgeyBkaXI6IG91dHB1dERpciB9KTtcblxuICBpZiAoIXJlc3VsdC5zdWNjZXNzKSB0aHJvdyByZXN1bHQuZXJyb3I7XG5cbiAgY29uc3QgaXNyUm91dGVzID0gYnVpbGRJc3JNYW5pZmVzdEVudHJpZXMocm91dGVJbmZvLCBzdGF0aWNQYXRoUGFyYW1zQnlSb3V0ZSk7XG4gIGlmIChpc3JSb3V0ZXMubGVuZ3RoID4gMCkge1xuICAgIHdyaXRlRmlsZVN5bmMoXG4gICAgICBqb2luKG91dHB1dERpciwgJ2lzci1tYW5pZmVzdC5qc29uJyksXG4gICAgICB3cml0ZUpzb24oaXNyUm91dGVzKSxcbiAgICAgICd1dGYtOCcsXG4gICAgKTtcbiAgICBsb2cuaW5mbyhcbiAgICAgIGBJU1IgbWFuaWZlc3QgLT4gJHtqb2luKG91dHB1dERpciwgJ2lzci1tYW5pZmVzdC5qc29uJyl9ICgke2lzclJvdXRlcy5sZW5ndGh9IHJvdXRlKHMpKWAsXG4gICAgKTtcbiAgfVxuXG4gIC8vIOKUgOKUgCBQb3N0LXByb2Nlc3Npbmcg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbiAgLy8gUmVuYW1lIDQwNC9pbmRleC5odG1sIC0+IDQwNC5odG1sIGZvciBHaXRIdWIgUGFnZXNcbiAgY29uc3QgXzQwNERpciA9IGpvaW4ob3V0cHV0RGlyLCAnNDA0Jyk7XG4gIGNvbnN0IF80MDRIdG1sID0gam9pbihvdXRwdXREaXIsICc0MDQuaHRtbCcpO1xuICBjb25zdCBfNDA0SW5kZXggPSBqb2luKF80MDREaXIsICdpbmRleC5odG1sJyk7XG4gIGlmIChleGlzdHNTeW5jKF80MDRJbmRleCkpIHtcbiAgICBpZiAoZXhpc3RzU3luYyhfNDA0SHRtbCkpIHtcbiAgICAgIGxvZy53YXJuKFxuICAgICAgICAnNDA0Lmh0bWwgYWxyZWFkeSBleGlzdHMgaW4gb3V0cHV0IGRpciAtIHJlbW92aW5nIGJlZm9yZSByZW5hbWUnLFxuICAgICAgKTtcbiAgICAgIHJtU3luYyhfNDA0SHRtbCwgeyBmb3JjZTogdHJ1ZSB9KTtcbiAgICB9XG4gICAgcmVuYW1lU3luYyhfNDA0SW5kZXgsIF80MDRIdG1sKTtcbiAgICBpZiAoZXhpc3RzU3luYyhfNDA0RGlyKSkge1xuICAgICAgcm1TeW5jKF80MDREaXIsIHsgcmVjdXJzaXZlOiB0cnVlLCBmb3JjZTogdHJ1ZSB9KTtcbiAgICB9XG4gICAgbG9nLmluZm8oJzQwNCBwYWdlIC0+IGRpc3QvNDA0Lmh0bWwgKEdpdEh1YiBQYWdlcyknKTtcbiAgfVxuXG4gIC8vIENvbnZlcnQgZmxhdCBIVE1MIGZpbGVzIHRvIGNsZWFuIFVSTHM6IGFib3V0Lmh0bWwgLT4gYWJvdXQvaW5kZXguaHRtbFxuICBjb25zdCBhbGxIdG1sRmlsZXMgPSBmaW5kSHRtbEZpbGVzKG91dHB1dERpcik7XG4gIGZvciAoY29uc3QgZmlsZVBhdGggb2YgYWxsSHRtbEZpbGVzKSB7XG4gICAgY29uc3QgcmVsID0gbm9kZVBhdGgucmVsYXRpdmUob3V0cHV0RGlyLCBmaWxlUGF0aCk7XG4gICAgaWYgKHJlbC5lbmRzV2l0aCgnaW5kZXguaHRtbCcpIHx8IHJlbCA9PT0gJzQwNC5odG1sJykgY29udGludWU7XG4gICAgY29uc3QgYmFzZU5hbWUgPSByZWwucmVwbGFjZSgvXFwuaHRtbCQvLCAnJyk7XG4gICAgY29uc3QgdXJsQmFzZU5hbWUgPSBiYXNlTmFtZS5yZXBsYWNlKC9cXFxcL2csICcvJyk7XG4gICAgY29uc3QgZGlyUGF0aCA9IGpvaW4ob3V0cHV0RGlyLCBiYXNlTmFtZSk7XG4gICAgY29uc3QgaW5kZXhQYXRoID0gam9pbihkaXJQYXRoLCAnaW5kZXguaHRtbCcpO1xuICAgIGlmIChleGlzdHNTeW5jKGRpclBhdGgpKSBjb250aW51ZTtcbiAgICBta2RpclN5bmMoZGlyUGF0aCwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgcmVuYW1lU3luYyhmaWxlUGF0aCwgaW5kZXhQYXRoKTtcbiAgICBsb2cuaW5mbyhgQ2xlYW4gVVJMOiAvJHt1cmxCYXNlTmFtZX0gLT4gJHt1cmxCYXNlTmFtZX0vaW5kZXguaHRtbGApO1xuICB9XG5cbiAgbG9nLmluZm8oYFN0YXRpYyBzaXRlIGdlbmVyYXRlZCAtPiAke291dHB1dERpcn1gKTtcblxuICAvLyDilIDilIAgaTE4biBsb2NhbGUgZXhwYW5zaW9uIChpZiBjdHggYXZhaWxhYmxlKSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgYXdhaXQgZXhwYW5kSTE4bkxvY2FsZXMoXG4gICAgZXZpZGVuY2UsXG4gICAgcmVuZGVyUm91dGUsXG4gICAgcm91dGVJbmZvLFxuICAgIGdldFN0YXRpY1BhdGhzLFxuICAgIG9wdGlvbnMsXG4gICAgcm9vdCxcbiAgICBvdXREaXIsXG4gICAgcGFnZURpYWdub3N0aWNzLFxuICApO1xuXG4gIC8vIOKUgOKUgCBQb3N0LXByb2Nlc3NpbmcgbW9kdWxlcyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgY29uc3Qge1xuICAgIGJ1aWxkSXNsYW5kQ2h1bmtNYXAsXG4gICAgaW5qZWN0Q3NwTWV0YSxcbiAgICBpbmplY3REc2RQb2x5ZmlsbCxcbiAgICBpbmplY3RWaWV3VHJhbnNpdGlvbk1ldGEsXG4gICAgaW5qZWN0U3BlY3VsYXRpb25SdWxlcyxcbiAgICBidWlsZFNwZWN1bGF0aW9uUnVsZXNKc29uLFxuICB9ID0gYXdhaXQgaW1wb3J0KCcuL3Bvc3Rwcm9jZXNzLmpzJyk7XG5cbiAgY29uc3QgaXNsYW5kVGFnTmFtZXMgPSBvcHRpb25zLmlzbGFuZFRhZ05hbWVzIHx8IFtdO1xuICBjb25zdCBfaXNsYW5kQ2h1bmtNYXAgPSBidWlsZElzbGFuZENodW5rTWFwKFxuICAgIHJvb3QsXG4gICAgb3V0RGlyLFxuICAgIGlzbGFuZFRhZ05hbWVzLFxuICAgIGJhc2VQYXRoLFxuICApO1xuXG4gIGlmIChvcHRpb25zLnZpZXdUcmFuc2l0aW9uICE9PSBmYWxzZSkge1xuICAgIGluamVjdFZpZXdUcmFuc2l0aW9uTWV0YShvdXRwdXREaXIpO1xuICAgIGxvZy5pbmZvKCdWaWV3IFRyYW5zaXRpb25zIG1ldGEgdGFnIGluamVjdGVkJyk7XG4gIH1cblxuICBpZiAob3B0aW9ucy5zcGVjdWxhdGlvbikge1xuICAgIGNvbnN0IHNwZWNPcHRzID0gdHlwZW9mIG9wdGlvbnMuc3BlY3VsYXRpb24gPT09ICdib29sZWFuJ1xuICAgICAgPyB7fVxuICAgICAgOiAob3B0aW9ucy5zcGVjdWxhdGlvbiBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik7XG4gICAgY29uc3QgcnVsZXNKc29uID0gYnVpbGRTcGVjdWxhdGlvblJ1bGVzSnNvbihcbiAgICAgIHNwZWNPcHRzLFxuICAgICAgcm91dGVJbmZvLm1hcCgocikgPT4gKHsgcGF0aDogci5wYXRoLCB0eXBlOiAncGFnZScgYXMgY29uc3QgfSkpLFxuICAgICk7XG4gICAgaWYgKHJ1bGVzSnNvbikge1xuICAgICAgaW5qZWN0U3BlY3VsYXRpb25SdWxlcyhvdXRwdXREaXIsIHJ1bGVzSnNvbik7XG4gICAgICBsb2cuaW5mbygnU3BlY3VsYXRpb24gUnVsZXMgaW5qZWN0ZWQnKTtcbiAgICB9XG4gIH1cblxuICBjb25zdCBjc3BQb2xpY3kgPSBvcHRpb25zLm1pZGRsZXdhcmU/LmNzcD8ucG9saWN5O1xuICBpZiAoY3NwUG9saWN5KSB7XG4gICAgaW5qZWN0Q3NwTWV0YShcbiAgICAgIG91dHB1dERpcixcbiAgICAgIGNzcFBvbGljeSxcbiAgICAgIG9wdGlvbnMubWlkZGxld2FyZT8uY3NwPy5yZXBvcnRPbmx5IHx8IGZhbHNlLFxuICAgICAgb3B0aW9ucy5taWRkbGV3YXJlPy5jc3A/Lm5vbmNlIHx8IGZhbHNlLFxuICAgICk7XG4gICAgbG9nLmluZm8oJ0NTUCBtZXRhIHRhZyBpbmplY3RlZCcpO1xuICB9XG5cbiAgaW5qZWN0RHNkUG9seWZpbGwob3V0cHV0RGlyKTtcbiAgbG9nLmluZm8oJ0RTRCBwb2x5ZmlsbCBpbmplY3RlZCcpO1xuXG4gIC8vIOKUgOKUgCBTaXRlbWFwICh2aWEgY3R4KSDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgYXdhaXQgZXZpZGVuY2Uub25QcmludEJ1aWxkTWFuaWZlc3Q/Lih7XG4gICAgcm9vdCxcbiAgICBvdXREaXIsXG4gICAgcGhhc2U6IDMsXG4gICAgaGVhZEV4dHJhczogb3B0aW9ucy5oZWFkRXh0cmFzLFxuICB9KTtcblxuICB0cnkge1xuICAgIGF3YWl0IGV2aWRlbmNlLm9uR2VuZXJhdGVTaXRlbWFwPy4oam9pbihyb290LCBvdXREaXIpKTtcbiAgfSBjYXRjaCB7XG4gICAgbG9nLmRlYnVnKCdTaXRlbWFwIGdlbmVyYXRpb24gc2tpcHBlZCBvciBmYWlsZWQnKTtcbiAgfVxuXG4gIC8vIOKUgOKUgCBkc2QtcmVwb3J0Lmpzb24gKHYwLjE1LjMpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICBjb25zdCByZXBvcnQgPSBhc3NlbWJsZURzZFJlcG9ydChwYWdlRGlhZ25vc3RpY3MsIGV2aWRlbmNlKTtcbiAgd3JpdGVEc2RSZXBvcnQob3V0cHV0RGlyLCByZXBvcnQpO1xufVxuXG4vLyBSZS1leHBvcnQgcmVzb2x2ZUR5bmFtaWNSb3V0ZVBhdGggZm9yIGNvbnN1bWVycyB3aG8gaW1wb3J0IGZyb20gc3NnLXJlbmRlci50c1xuZXhwb3J0IHsgcmVzb2x2ZUR5bmFtaWNSb3V0ZVBhdGggfSBmcm9tICcuL3NzZy1oZWxwZXJzLmpzJztcbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Q0FjQyxHQUVELFNBQVMsSUFBSSxRQUFRLFlBQVk7QUFDakMsT0FBTyxhQUFhLGVBQWU7QUFDbkMsU0FBUyxVQUFVLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsYUFBYSxRQUFRLFVBQVU7QUFPbkYsU0FBUyxZQUFZLFFBQVEsMkJBQTJCO0FBQ3hELFNBQVMsbUJBQW1CLFFBQVEsbUJBQW1CO0FBQ3ZELFNBQVMsaUJBQWlCLFFBQVEsZ0JBQWdCO0FBQ2xELFNBQVMsaUJBQWlCLEVBQUUsY0FBYyxRQUFRLGtCQUFrQjtBQUNwRSxTQUFTLHVCQUF1QixF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