@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.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/package.json +30 -0
- package/src/build-postprocess.d.ts +24 -0
- package/src/build-postprocess.js +74 -0
- package/src/cem-compat.js +226 -0
- package/src/entry-generators.d.ts +3 -0
- package/src/entry-generators.js +126 -0
- package/src/entry-render-helpers.js +307 -0
- package/src/entry-render-runtime.js +94 -0
- package/src/entry-render-ssg.js +169 -0
- package/src/entry-renderer.d.ts +87 -0
- package/src/entry-renderer.js +555 -0
- package/src/external-resolver.d.ts +62 -0
- package/src/external-resolver.js +285 -0
- package/src/index.d.ts +31 -0
- package/src/index.js +28 -0
- package/src/island-manifest.d.ts +27 -0
- package/src/island-manifest.js +78 -0
- package/src/postprocess.d.ts +73 -0
- package/src/postprocess.js +376 -0
- package/src/route-scanner-fs.js +29 -0
- package/src/route-scanner.d.ts +132 -0
- package/src/route-scanner.js +497 -0
- package/src/route-type-generator.d.ts +29 -0
- package/src/route-type-generator.js +99 -0
- package/src/ssg-dynamic.js +66 -0
- package/src/ssg-helpers.d.ts +8 -0
- package/src/ssg-helpers.js +96 -0
- package/src/ssg-i18n.js +78 -0
- package/src/ssg-render.d.ts +3 -0
- package/src/ssg-render.js +162 -0
- package/src/ssg-report.js +172 -0
- package/src/ssr-polyfills.d.ts +15 -0
- package/src/ssr-polyfills.js +26 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { SpeculationRulesOptions } from '@openelement/protocol/ssg';
|
|
2
|
+
/** Insert content immediately after <head> opening tag (handles attributes) */ export declare function insertAfterHead(html: string, content: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* Scan client build output to build tagName -> chunk path mapping.
|
|
5
|
+
* Reads Rollup manifest JSON (v0.3.0+ deterministic approach).
|
|
6
|
+
*/ export declare function buildIslandChunkMap(root: string, outDir: string, islands: string[], basePath?: string): Record<string, string>;
|
|
7
|
+
/**
|
|
8
|
+
* Inject client script tag into all HTML files.
|
|
9
|
+
*/ export declare function injectClientScript(dir: string, scriptSrc: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Inject CSP <meta> tag into all HTML files (SSG-only).
|
|
12
|
+
*
|
|
13
|
+
* For static sites, CSP is enforced via <meta http-equiv="Content-Security-Policy">
|
|
14
|
+
* rather than HTTP headers. Nonce-based CSP is NOT supported for SSG
|
|
15
|
+
* (nonces must be per-request and unpredictable - impossible in static files).
|
|
16
|
+
*/ export declare function injectCspMeta(dir: string, cspPolicy: string, reportOnly?: boolean, nonce?: boolean): void;
|
|
17
|
+
/**
|
|
18
|
+
* Inject DSD polyfill into all HTML files.
|
|
19
|
+
* Handles browsers that don't natively support Declarative Shadow DOM.
|
|
20
|
+
*/ export declare function injectDsdPolyfill(dir: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Inject View Transitions meta tag into all HTML files.
|
|
23
|
+
*
|
|
24
|
+
* The View Transitions API (Chrome 111+, Safari 18+, Firefox 129+) enables
|
|
25
|
+
* smooth cross-page animations for MPA (Multi-Page App) navigation.
|
|
26
|
+
* For SSG sites, this is a single meta tag - zero JavaScript required.
|
|
27
|
+
*
|
|
28
|
+
* When a user clicks a link, the browser automatically creates a cross-fade
|
|
29
|
+
* transition between the old and new page. No SPA routing needed.
|
|
30
|
+
*
|
|
31
|
+
* Supported browsers: Chrome 111+, Edge 111+, Safari 18+, Firefox 129+.
|
|
32
|
+
* Unsupported browsers silently ignore the meta tag (graceful degradation).
|
|
33
|
+
*
|
|
34
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
|
|
35
|
+
* @see https://chromestatus.com/feature/5190686707568640
|
|
36
|
+
*/ export declare function injectViewTransitionMeta(dir: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Build Speculation Rules JSON from configuration and known routes.
|
|
39
|
+
*
|
|
40
|
+
* If user-provided rules exist, they are used directly.
|
|
41
|
+
* Otherwise, heuristics are applied based on the route list:
|
|
42
|
+
* - Home page (/) -> prerender (moderate)
|
|
43
|
+
* - Top-level static pages (1 level deep) -> prerender (conservative)
|
|
44
|
+
* - Nested static pages -> prefetch
|
|
45
|
+
* - Dynamic routes (containing :) -> excluded (content depends on params)
|
|
46
|
+
* - API routes -> excluded
|
|
47
|
+
*
|
|
48
|
+
* This two-tier strategy balances instant navigation for high-probability
|
|
49
|
+
* targets (prerender) with bandwidth-conscious loading for deeper pages (prefetch).
|
|
50
|
+
*
|
|
51
|
+
* @param options - User-provided speculation rules configuration
|
|
52
|
+
* @param routes - Known route entries from route scanner (for heuristic rules)
|
|
53
|
+
* @returns Speculation Rules JSON string, or empty string if no rules apply
|
|
54
|
+
*/ export declare function buildSpeculationRulesJson(options: SpeculationRulesOptions, routes?: Array<{
|
|
55
|
+
path: string;
|
|
56
|
+
type: string;
|
|
57
|
+
}>): string;
|
|
58
|
+
/**
|
|
59
|
+
* Inject Speculation Rules into all HTML files.
|
|
60
|
+
*
|
|
61
|
+
* The Speculation Rules API (Chrome 121+) enables the browser to
|
|
62
|
+
* prefetch or prerender pages before the user navigates to them.
|
|
63
|
+
* This makes navigation feel instant for SSG sites.
|
|
64
|
+
*
|
|
65
|
+
* Speculation Rules are declarative JSON in a <script type="speculationrules"> tag.
|
|
66
|
+
* They have zero JavaScript runtime cost - the browser handles everything natively.
|
|
67
|
+
*
|
|
68
|
+
* Only Chromium-based browsers (Chrome, Edge) support this as of 2026.
|
|
69
|
+
* Safari and Firefox silently ignore the script tag (graceful degradation).
|
|
70
|
+
*
|
|
71
|
+
* @param dir - Output directory containing HTML files
|
|
72
|
+
* @param rulesJson - Pre-built speculation rules JSON string
|
|
73
|
+
*/ export declare function injectSpeculationRules(dir: string, rulesJson: string): void;
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapter-vite internal SSG post-processing.
|
|
3
|
+
*
|
|
4
|
+
* Pure Node.js fs operations for SSG output post-processing.
|
|
5
|
+
* No Vite dependency - these functions only read/write files.
|
|
6
|
+
*
|
|
7
|
+
* URLPattern is used for route matching per WHATWG section7.2.
|
|
8
|
+
*
|
|
9
|
+
* Post-processing pipeline (called after SSG rendering):
|
|
10
|
+
* 1. injectClientScript() - add island client entry
|
|
11
|
+
* 2. injectViewTransitionMeta() - enable cross-page View Transitions
|
|
12
|
+
* 3. injectSpeculationRules() - prefetch/prerender for navigation performance
|
|
13
|
+
* 4. injectCspMeta() - Content-Security-Policy meta tag
|
|
14
|
+
* 5. injectDsdPolyfill() - DSD polyfill for Firefox
|
|
15
|
+
*/ import { join, resolve } from 'node:path';
|
|
16
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { createLogger } from '@openelement/core/logger';
|
|
18
|
+
import { formatError } from '@openelement/core/errors';
|
|
19
|
+
const log = createLogger('core');
|
|
20
|
+
// Shared directory walker
|
|
21
|
+
/**
|
|
22
|
+
* Walk a directory tree and apply a visitor to each HTML file.
|
|
23
|
+
* If the visitor returns a string, the file is overwritten with that content.
|
|
24
|
+
* If it returns null, the file is left unchanged.
|
|
25
|
+
*/ function walkHtmlFiles(dir, visitor) {
|
|
26
|
+
const entries = readdirSync(dir, {
|
|
27
|
+
withFileTypes: true
|
|
28
|
+
});
|
|
29
|
+
for (const entry of entries){
|
|
30
|
+
const fullPath = join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
walkHtmlFiles(fullPath, visitor);
|
|
33
|
+
} else if (entry.name.endsWith('.html')) {
|
|
34
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
35
|
+
const result = visitor(content, fullPath);
|
|
36
|
+
if (result !== null) {
|
|
37
|
+
writeFileSync(fullPath, result, 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// ─── HTML Insertion Helpers ────────────────────────────────────────────
|
|
43
|
+
/** Insert content immediately after <head> opening tag (handles attributes) */ export function insertAfterHead(html, content) {
|
|
44
|
+
// M-11 fix: Use [^>]* instead of [\s\S]*? to prevent backtracking
|
|
45
|
+
const headMatch = html.match(/<head(\s[^>]*)?>/i);
|
|
46
|
+
if (!headMatch) {
|
|
47
|
+
return html.startsWith('<!') || html.startsWith('<html') ? html.replace(/(<(?:!DOCTYPE|html)[^>]*>)/i, `$1\n<head>\n ${content}\n</head>`) : `<head>\n ${content}\n</head>\n${html}`;
|
|
48
|
+
}
|
|
49
|
+
if (headMatch.index === undefined) {
|
|
50
|
+
throw new Error('insertAfterHead: matched <head> but index is undefined');
|
|
51
|
+
}
|
|
52
|
+
const headEnd = headMatch.index + headMatch[0].length;
|
|
53
|
+
return html.slice(0, headEnd) + `\n ${content}` + html.slice(headEnd);
|
|
54
|
+
}
|
|
55
|
+
/** Insert content immediately before </body> closing tag */ function insertBeforeBodyClose(html, content) {
|
|
56
|
+
const bodyCloseMatch = html.match(/<\/body\s*>/i);
|
|
57
|
+
if (!bodyCloseMatch) {
|
|
58
|
+
return html + `\n${content}\n`;
|
|
59
|
+
}
|
|
60
|
+
if (bodyCloseMatch.index === undefined) {
|
|
61
|
+
throw new Error('insertBeforeBodyClose: matched </body> but index is undefined');
|
|
62
|
+
}
|
|
63
|
+
const idx = bodyCloseMatch.index;
|
|
64
|
+
return html.slice(0, idx) + `${content}\n` + html.slice(idx);
|
|
65
|
+
}
|
|
66
|
+
// ─── Public API ────────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Scan client build output to build tagName -> chunk path mapping.
|
|
69
|
+
* Reads Rollup manifest JSON (v0.3.0+ deterministic approach).
|
|
70
|
+
*/ export function buildIslandChunkMap(root, outDir, islands, basePath = '/') {
|
|
71
|
+
const distDir = resolve(root, outDir);
|
|
72
|
+
const clientDir = resolve(distDir, 'client');
|
|
73
|
+
const islandChunkMap = {};
|
|
74
|
+
if (!existsSync(clientDir)) return islandChunkMap;
|
|
75
|
+
const manifestPath = join(clientDir, '.vite', 'manifest.json');
|
|
76
|
+
if (!existsSync(manifestPath)) return islandChunkMap;
|
|
77
|
+
try {
|
|
78
|
+
const manifestRaw = readFileSync(manifestPath, 'utf-8');
|
|
79
|
+
const manifest = JSON.parse(manifestRaw);
|
|
80
|
+
for (const [_srcPath, entry] of Object.entries(manifest)){
|
|
81
|
+
if (!entry.file) continue;
|
|
82
|
+
const chunkMatch = entry.file.match(/^islands\/island-(.+?)-[A-Za-z0-9]+\.js$/);
|
|
83
|
+
if (chunkMatch && islands.includes(chunkMatch[1])) {
|
|
84
|
+
islandChunkMap[chunkMatch[1]] = `${basePath}client/${entry.file}`;
|
|
85
|
+
}
|
|
86
|
+
if (entry.file === 'islands/client.js') {
|
|
87
|
+
for (const tagName of islands){
|
|
88
|
+
if (!islandChunkMap[tagName]) {
|
|
89
|
+
islandChunkMap[tagName] = `${basePath}client/islands/client.js`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// Malformed manifest - warn and return empty map
|
|
96
|
+
log.warn(`Could not parse client manifest: ${formatError(e)}`);
|
|
97
|
+
}
|
|
98
|
+
return islandChunkMap;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Inject client script tag into all HTML files.
|
|
102
|
+
*/ export function injectClientScript(dir, scriptSrc) {
|
|
103
|
+
const scriptTag = ` <script type="module" src="${scriptSrc}"></script>`;
|
|
104
|
+
walkHtmlFiles(dir, (content)=>{
|
|
105
|
+
if (content.includes(scriptSrc)) return null;
|
|
106
|
+
return insertBeforeBodyClose(content, scriptTag);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Inject CSP <meta> tag into all HTML files (SSG-only).
|
|
111
|
+
*
|
|
112
|
+
* For static sites, CSP is enforced via <meta http-equiv="Content-Security-Policy">
|
|
113
|
+
* rather than HTTP headers. Nonce-based CSP is NOT supported for SSG
|
|
114
|
+
* (nonces must be per-request and unpredictable - impossible in static files).
|
|
115
|
+
*/ export function injectCspMeta(dir, cspPolicy, reportOnly = false, nonce = false) {
|
|
116
|
+
if (nonce) {
|
|
117
|
+
log.warn('CSP nonce is not supported for SSG static output. ' + 'Falling back to policy-only Content-Security-Policy meta tag. ' + 'For per-request nonces, use a server-side middleware instead.');
|
|
118
|
+
}
|
|
119
|
+
const headerName = reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy';
|
|
120
|
+
const escapedPolicy = cspPolicy.replace(/"/g, '"');
|
|
121
|
+
const metaTag = ` <meta http-equiv="${headerName}" content="${escapedPolicy}">`;
|
|
122
|
+
walkHtmlFiles(dir, (content)=>{
|
|
123
|
+
if (content.includes(`http-equiv="${headerName}"`)) return null;
|
|
124
|
+
return insertAfterHead(content, metaTag);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* DSD polyfill for browsers that don't support Declarative Shadow DOM.
|
|
129
|
+
* Firefox does NOT support shadowrootmode as of 2025.
|
|
130
|
+
* This polyfill attaches Shadow Roots manually via attachShadow().
|
|
131
|
+
*/ const DSD_POLYFILL = `
|
|
132
|
+
<style>html{visibility:visible!important}</style>
|
|
133
|
+
<script>
|
|
134
|
+
// DSD Polyfill (Firefox, older browsers)
|
|
135
|
+
(function() {
|
|
136
|
+
try {
|
|
137
|
+
const t = document.createElement('template');
|
|
138
|
+
t.setAttribute('shadowrootmode', 'open');
|
|
139
|
+
if ('shadowRootMode' in t) return; // Native support
|
|
140
|
+
} catch { /* native detection failed - fallback to polyfill */ }
|
|
141
|
+
|
|
142
|
+
const attachDSD = (root) => {
|
|
143
|
+
root.querySelectorAll('template[shadowrootmode]').forEach(tpl => {
|
|
144
|
+
const parent = tpl.parentNode;
|
|
145
|
+
if (!parent || parent.shadowRoot) return;
|
|
146
|
+
try {
|
|
147
|
+
const mode = tpl.getAttribute('shadowrootmode');
|
|
148
|
+
const opts = { mode: mode === 'open' ? 'open' : 'closed' };
|
|
149
|
+
if (tpl.hasAttribute('shadowrootdelegatesfocus')) opts.delegatesFocus = true;
|
|
150
|
+
const sr = parent.attachShadow(opts);
|
|
151
|
+
sr.innerHTML = tpl.innerHTML;
|
|
152
|
+
tpl.remove();
|
|
153
|
+
attachDSD(sr); // Handle nested DSD
|
|
154
|
+
} catch { /* non-fatal: skip malformed DSD templates */ }
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (document.readyState === 'loading') {
|
|
159
|
+
document.addEventListener('DOMContentLoaded', () => attachDSD(document));
|
|
160
|
+
} else {
|
|
161
|
+
attachDSD(document);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Fallback: if after polyfill some templates remain unattached, inline them
|
|
165
|
+
// into light DOM so content is still visible even if attachShadow failed.
|
|
166
|
+
const fallbackRender = () => {
|
|
167
|
+
document.querySelectorAll('template[shadowrootmode]').forEach((tpl) => {
|
|
168
|
+
const parent = tpl.parentNode;
|
|
169
|
+
if (!parent || parent.shadowRoot) return;
|
|
170
|
+
const frag = tpl.content.cloneNode(true);
|
|
171
|
+
parent.appendChild(frag);
|
|
172
|
+
tpl.remove();
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
if (document.readyState === 'complete') {
|
|
176
|
+
fallbackRender();
|
|
177
|
+
} else {
|
|
178
|
+
window.addEventListener('load', fallbackRender, { once: true });
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
</script>
|
|
182
|
+
`;
|
|
183
|
+
/**
|
|
184
|
+
* Inject DSD polyfill into all HTML files.
|
|
185
|
+
* Handles browsers that don't natively support Declarative Shadow DOM.
|
|
186
|
+
*/ export function injectDsdPolyfill(dir) {
|
|
187
|
+
walkHtmlFiles(dir, (content)=>{
|
|
188
|
+
if (content.includes('DSD Polyfill')) return null;
|
|
189
|
+
return insertAfterHead(content, DSD_POLYFILL);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// ─── View Transitions API ─────────────────────────────────────────────
|
|
193
|
+
/**
|
|
194
|
+
* Inject View Transitions meta tag into all HTML files.
|
|
195
|
+
*
|
|
196
|
+
* The View Transitions API (Chrome 111+, Safari 18+, Firefox 129+) enables
|
|
197
|
+
* smooth cross-page animations for MPA (Multi-Page App) navigation.
|
|
198
|
+
* For SSG sites, this is a single meta tag - zero JavaScript required.
|
|
199
|
+
*
|
|
200
|
+
* When a user clicks a link, the browser automatically creates a cross-fade
|
|
201
|
+
* transition between the old and new page. No SPA routing needed.
|
|
202
|
+
*
|
|
203
|
+
* Supported browsers: Chrome 111+, Edge 111+, Safari 18+, Firefox 129+.
|
|
204
|
+
* Unsupported browsers silently ignore the meta tag (graceful degradation).
|
|
205
|
+
*
|
|
206
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
|
|
207
|
+
* @see https://chromestatus.com/feature/5190686707568640
|
|
208
|
+
*/ export function injectViewTransitionMeta(dir) {
|
|
209
|
+
const metaTag = ' <meta name="view-transition" content="same-origin">';
|
|
210
|
+
walkHtmlFiles(dir, (content)=>{
|
|
211
|
+
if (content.includes('<meta name="view-transition"')) return null;
|
|
212
|
+
return insertAfterHead(content, metaTag);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
// ─── Speculation Rules API ────────────────────────────────────────────
|
|
216
|
+
/**
|
|
217
|
+
* Build Speculation Rules JSON from configuration and known routes.
|
|
218
|
+
*
|
|
219
|
+
* If user-provided rules exist, they are used directly.
|
|
220
|
+
* Otherwise, heuristics are applied based on the route list:
|
|
221
|
+
* - Home page (/) -> prerender (moderate)
|
|
222
|
+
* - Top-level static pages (1 level deep) -> prerender (conservative)
|
|
223
|
+
* - Nested static pages -> prefetch
|
|
224
|
+
* - Dynamic routes (containing :) -> excluded (content depends on params)
|
|
225
|
+
* - API routes -> excluded
|
|
226
|
+
*
|
|
227
|
+
* This two-tier strategy balances instant navigation for high-probability
|
|
228
|
+
* targets (prerender) with bandwidth-conscious loading for deeper pages (prefetch).
|
|
229
|
+
*
|
|
230
|
+
* @param options - User-provided speculation rules configuration
|
|
231
|
+
* @param routes - Known route entries from route scanner (for heuristic rules)
|
|
232
|
+
* @returns Speculation Rules JSON string, or empty string if no rules apply
|
|
233
|
+
*/ export function buildSpeculationRulesJson(options, routes) {
|
|
234
|
+
// If user provided explicit rules, use them
|
|
235
|
+
if (options.prerender?.length || options.prefetch?.length) {
|
|
236
|
+
const rules = {};
|
|
237
|
+
if (options.prerender && options.prerender.length > 0) {
|
|
238
|
+
rules.prerender = options.prerender.map((pattern)=>({
|
|
239
|
+
where: {
|
|
240
|
+
href_matches: pattern
|
|
241
|
+
},
|
|
242
|
+
...options.eagerness && options.eagerness !== 'moderate' ? {
|
|
243
|
+
eagerness: options.eagerness
|
|
244
|
+
} : {}
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
if (options.prefetch && options.prefetch.length > 0) {
|
|
248
|
+
rules.prefetch = options.prefetch.map((pattern)=>({
|
|
249
|
+
where: {
|
|
250
|
+
href_matches: pattern
|
|
251
|
+
}
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
// Add exclusion if provided
|
|
255
|
+
if (options.exclude && options.exclude.length > 0) {
|
|
256
|
+
const excludeWhere = options.exclude.map((pattern)=>({
|
|
257
|
+
href_matches: pattern
|
|
258
|
+
}));
|
|
259
|
+
// Apply exclusions to both prerender and prefetch
|
|
260
|
+
for (const key of [
|
|
261
|
+
'prerender',
|
|
262
|
+
'prefetch'
|
|
263
|
+
]){
|
|
264
|
+
if (rules[key]) {
|
|
265
|
+
for (const rule of rules[key]){
|
|
266
|
+
rule.where.not = {
|
|
267
|
+
or_matches: excludeWhere
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return JSON.stringify(rules, null, 2);
|
|
274
|
+
}
|
|
275
|
+
// Heuristic mode: generate rules from route list
|
|
276
|
+
if (!routes || routes.length === 0) return '';
|
|
277
|
+
const staticPagePaths = routes.filter((r)=>r.type === 'page' && !r.path.includes(':')).map((r)=>r.path);
|
|
278
|
+
if (staticPagePaths.length === 0) return '';
|
|
279
|
+
// Two-tier strategy:
|
|
280
|
+
// - Home page is the highest-probability target -> prerender (moderate)
|
|
281
|
+
// - Top-level pages (e.g. /about, /blog) -> prerender (conservative)
|
|
282
|
+
// - Nested pages (e.g. /blog/post-slug, /docs/guide) -> prefetch only
|
|
283
|
+
const prerenderPaths = [];
|
|
284
|
+
const prefetchPaths = [];
|
|
285
|
+
for (const path of staticPagePaths){
|
|
286
|
+
if (path === '/') {
|
|
287
|
+
// Highest probability: prerender with moderate eagerness
|
|
288
|
+
prerenderPaths.push(path);
|
|
289
|
+
} else if (path.split('/').filter(Boolean).length <= 1) {
|
|
290
|
+
// v0.14.3: Top-level pages get exact-match patterns, NOT wildcards.
|
|
291
|
+
// Adding /about/* would waste bandwidth prefetching all /about/* sub-paths
|
|
292
|
+
// that may not exist. Use exact match for single pages.
|
|
293
|
+
prerenderPaths.push(path);
|
|
294
|
+
} else {
|
|
295
|
+
// Deeper pages: prefetch only (lighter than prerender)
|
|
296
|
+
// Use wildcard for nested sections (e.g., /blog/* matches /blog/post-1, /blog/post-2)
|
|
297
|
+
prefetchPaths.push(`${path}/*`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const rules = {};
|
|
301
|
+
if (prerenderPaths.length > 0) {
|
|
302
|
+
rules.prerender = prerenderPaths.map((pattern)=>{
|
|
303
|
+
// Home page (/): use list rule (source + urls) - no document matcher
|
|
304
|
+
if (pattern === '/') {
|
|
305
|
+
return {
|
|
306
|
+
source: 'list',
|
|
307
|
+
urls: [
|
|
308
|
+
'/'
|
|
309
|
+
],
|
|
310
|
+
eagerness: 'moderate'
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// Top-level pages (/about, /blog): use document rule (where: href_matches)
|
|
314
|
+
return {
|
|
315
|
+
where: {
|
|
316
|
+
href_matches: pattern
|
|
317
|
+
},
|
|
318
|
+
eagerness: 'conservative'
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
if (prefetchPaths.length > 0) {
|
|
323
|
+
rules.prefetch = prefetchPaths.map((pattern)=>({
|
|
324
|
+
where: {
|
|
325
|
+
href_matches: pattern
|
|
326
|
+
}
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
// Exclude API routes if any exist
|
|
330
|
+
const apiPaths = routes.filter((r)=>r.type === 'api').map((r)=>`${r.path}/*`);
|
|
331
|
+
if (apiPaths.length > 0) {
|
|
332
|
+
const excludeWhere = apiPaths.map((p)=>({
|
|
333
|
+
href_matches: p
|
|
334
|
+
}));
|
|
335
|
+
for (const key of [
|
|
336
|
+
'prerender',
|
|
337
|
+
'prefetch'
|
|
338
|
+
]){
|
|
339
|
+
if (rules[key]) {
|
|
340
|
+
for (const rule of rules[key]){
|
|
341
|
+
// Only add exclusion to document rules (where) - list rules (source+urls) don't have where
|
|
342
|
+
if (rule.where && typeof rule.where === 'object') {
|
|
343
|
+
rule.where.not = {
|
|
344
|
+
or_matches: excludeWhere
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return JSON.stringify(rules, null, 2);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Inject Speculation Rules into all HTML files.
|
|
355
|
+
*
|
|
356
|
+
* The Speculation Rules API (Chrome 121+) enables the browser to
|
|
357
|
+
* prefetch or prerender pages before the user navigates to them.
|
|
358
|
+
* This makes navigation feel instant for SSG sites.
|
|
359
|
+
*
|
|
360
|
+
* Speculation Rules are declarative JSON in a <script type="speculationrules"> tag.
|
|
361
|
+
* They have zero JavaScript runtime cost - the browser handles everything natively.
|
|
362
|
+
*
|
|
363
|
+
* Only Chromium-based browsers (Chrome, Edge) support this as of 2026.
|
|
364
|
+
* Safari and Firefox silently ignore the script tag (graceful degradation).
|
|
365
|
+
*
|
|
366
|
+
* @param dir - Output directory containing HTML files
|
|
367
|
+
* @param rulesJson - Pre-built speculation rules JSON string
|
|
368
|
+
*/ export function injectSpeculationRules(dir, rulesJson) {
|
|
369
|
+
if (!rulesJson.trim()) return;
|
|
370
|
+
const scriptTag = ` <script type="speculationrules">\n ${rulesJson}\n </script>`;
|
|
371
|
+
walkHtmlFiles(dir, (content)=>{
|
|
372
|
+
if (content.includes('<script type="speculationrules"')) return null;
|
|
373
|
+
return insertAfterHead(content, scriptTag);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3Bvc3Rwcm9jZXNzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogYWRhcHRlci12aXRlIGludGVybmFsIFNTRyBwb3N0LXByb2Nlc3NpbmcuXG4gKlxuICogUHVyZSBOb2RlLmpzIGZzIG9wZXJhdGlvbnMgZm9yIFNTRyBvdXRwdXQgcG9zdC1wcm9jZXNzaW5nLlxuICogTm8gVml0ZSBkZXBlbmRlbmN5IC0gdGhlc2UgZnVuY3Rpb25zIG9ubHkgcmVhZC93cml0ZSBmaWxlcy5cbiAqXG4gKiBVUkxQYXR0ZXJuIGlzIHVzZWQgZm9yIHJvdXRlIG1hdGNoaW5nIHBlciBXSEFUV0cgc2VjdGlvbjcuMi5cbiAqXG4gKiBQb3N0LXByb2Nlc3NpbmcgcGlwZWxpbmUgKGNhbGxlZCBhZnRlciBTU0cgcmVuZGVyaW5nKTpcbiAqIDEuIGluamVjdENsaWVudFNjcmlwdCgpIC0gYWRkIGlzbGFuZCBjbGllbnQgZW50cnlcbiAqIDIuIGluamVjdFZpZXdUcmFuc2l0aW9uTWV0YSgpIC0gZW5hYmxlIGNyb3NzLXBhZ2UgVmlldyBUcmFuc2l0aW9uc1xuICogMy4gaW5qZWN0U3BlY3VsYXRpb25SdWxlcygpIC0gcHJlZmV0Y2gvcHJlcmVuZGVyIGZvciBuYXZpZ2F0aW9uIHBlcmZvcm1hbmNlXG4gKiA0LiBpbmplY3RDc3BNZXRhKCkgLSBDb250ZW50LVNlY3VyaXR5LVBvbGljeSBtZXRhIHRhZ1xuICogNS4gaW5qZWN0RHNkUG9seWZpbGwoKSAtIERTRCBwb2x5ZmlsbCBmb3IgRmlyZWZveFxuICovXG5cbmltcG9ydCB7IGpvaW4sIHJlc29sdmUgfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHsgZXhpc3RzU3luYywgcmVhZGRpclN5bmMsIHJlYWRGaWxlU3luYywgd3JpdGVGaWxlU3luYyB9IGZyb20gJ25vZGU6ZnMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnQG9wZW5lbGVtZW50L2NvcmUvbG9nZ2VyJztcbmltcG9ydCB7IGZvcm1hdEVycm9yIH0gZnJvbSAnQG9wZW5lbGVtZW50L2NvcmUvZXJyb3JzJztcbmltcG9ydCB0eXBlIHsgU3BlY3VsYXRpb25SdWxlc09wdGlvbnMgfSBmcm9tICdAb3BlbmVsZW1lbnQvcHJvdG9jb2wvc3NnJztcblxuY29uc3QgbG9nID0gY3JlYXRlTG9nZ2VyKCdjb3JlJyk7XG5cbi8vIFNoYXJlZCBkaXJlY3Rvcnkgd2Fsa2VyXG5cbi8qKlxuICogV2FsayBhIGRpcmVjdG9yeSB0cmVlIGFuZCBhcHBseSBhIHZpc2l0b3IgdG8gZWFjaCBIVE1MIGZpbGUuXG4gKiBJZiB0aGUgdmlzaXRvciByZXR1cm5zIGEgc3RyaW5nLCB0aGUgZmlsZSBpcyBvdmVyd3JpdHRlbiB3aXRoIHRoYXQgY29udGVudC5cbiAqIElmIGl0IHJldHVybnMgbnVsbCwgdGhlIGZpbGUgaXMgbGVmdCB1bmNoYW5nZWQuXG4gKi9cbmZ1bmN0aW9uIHdhbGtIdG1sRmlsZXMoXG4gIGRpcjogc3RyaW5nLFxuICB2aXNpdG9yOiAoY29udGVudDogc3RyaW5nLCBmdWxsUGF0aDogc3RyaW5nKSA9PiBzdHJpbmcgfCBudWxsLFxuKTogdm9pZCB7XG4gIGNvbnN0IGVudHJpZXMgPSByZWFkZGlyU3luYyhkaXIsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KTtcbiAgZm9yIChjb25zdCBlbnRyeSBvZiBlbnRyaWVzKSB7XG4gICAgY29uc3QgZnVsbFBhdGggPSBqb2luKGRpciwgZW50cnkubmFtZSk7XG4gICAgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KCkpIHtcbiAgICAgIHdhbGtIdG1sRmlsZXMoZnVsbFBhdGgsIHZpc2l0b3IpO1xuICAgIH0gZWxzZSBpZiAoZW50cnkubmFtZS5lbmRzV2l0aCgnLmh0bWwnKSkge1xuICAgICAgY29uc3QgY29udGVudCA9IHJlYWRGaWxlU3luYyhmdWxsUGF0aCwgJ3V0Zi04Jyk7XG4gICAgICBjb25zdCByZXN1bHQgPSB2aXNpdG9yKGNvbnRlbnQsIGZ1bGxQYXRoKTtcbiAgICAgIGlmIChyZXN1bHQgIT09IG51bGwpIHtcbiAgICAgICAgd3JpdGVGaWxlU3luYyhmdWxsUGF0aCwgcmVzdWx0LCAndXRmLTgnKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbn1cblxuLy8g4pSA4pSA4pSAIEhUTUwgSW5zZXJ0aW9uIEhlbHBlcnMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbi8qKiBJbnNlcnQgY29udGVudCBpbW1lZGlhdGVseSBhZnRlciA8aGVhZD4gb3BlbmluZyB0YWcgKGhhbmRsZXMgYXR0cmlidXRlcykgKi9cbmV4cG9ydCBmdW5jdGlvbiBpbnNlcnRBZnRlckhlYWQoaHRtbDogc3RyaW5nLCBjb250ZW50OiBzdHJpbmcpOiBzdHJpbmcge1xuICAvLyBNLTExIGZpeDogVXNlIFtePl0qIGluc3RlYWQgb2YgW1xcc1xcU10qPyB0byBwcmV2ZW50IGJhY2t0cmFja2luZ1xuICBjb25zdCBoZWFkTWF0Y2ggPSBodG1sLm1hdGNoKC88aGVhZChcXHNbXj5dKik/Pi9pKTtcbiAgaWYgKCFoZWFkTWF0Y2gpIHtcbiAgICByZXR1cm4gaHRtbC5zdGFydHNXaXRoKCc8IScpIHx8IGh0bWwuc3RhcnRzV2l0aCgnPGh0bWwnKVxuICAgICAgPyBodG1sLnJlcGxhY2UoLyg8KD86IURPQ1RZUEV8aHRtbClbXj5dKj4pL2ksIGAkMVxcbjxoZWFkPlxcbiAgJHtjb250ZW50fVxcbjwvaGVhZD5gKVxuICAgICAgOiBgPGhlYWQ+XFxuICAke2NvbnRlbnR9XFxuPC9oZWFkPlxcbiR7aHRtbH1gO1xuICB9XG4gIGlmIChoZWFkTWF0Y2guaW5kZXggPT09IHVuZGVmaW5lZCkge1xuICAgIHRocm93IG5ldyBFcnJvcignaW5zZXJ0QWZ0ZXJIZWFkOiBtYXRjaGVkIDxoZWFkPiBidXQgaW5kZXggaXMgdW5kZWZpbmVkJyk7XG4gIH1cbiAgY29uc3QgaGVhZEVuZCA9IGhlYWRNYXRjaC5pbmRleCArIGhlYWRNYXRjaFswXS5sZW5ndGg7XG4gIHJldHVybiBodG1sLnNsaWNlKDAsIGhlYWRFbmQpICsgYFxcbiAgJHtjb250ZW50fWAgKyBodG1sLnNsaWNlKGhlYWRFbmQpO1xufVxuXG4vKiogSW5zZXJ0IGNvbnRlbnQgaW1tZWRpYXRlbHkgYmVmb3JlIDwvYm9keT4gY2xvc2luZyB0YWcgKi9cbmZ1bmN0aW9uIGluc2VydEJlZm9yZUJvZHlDbG9zZShodG1sOiBzdHJpbmcsIGNvbnRlbnQ6IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IGJvZHlDbG9zZU1hdGNoID0gaHRtbC5tYXRjaCgvPFxcL2JvZHlcXHMqPi9pKTtcbiAgaWYgKCFib2R5Q2xvc2VNYXRjaCkge1xuICAgIHJldHVybiBodG1sICsgYFxcbiR7Y29udGVudH1cXG5gO1xuICB9XG4gIGlmIChib2R5Q2xvc2VNYXRjaC5pbmRleCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKCdpbnNlcnRCZWZvcmVCb2R5Q2xvc2U6IG1hdGNoZWQgPC9ib2R5PiBidXQgaW5kZXggaXMgdW5kZWZpbmVkJyk7XG4gIH1cbiAgY29uc3QgaWR4ID0gYm9keUNsb3NlTWF0Y2guaW5kZXg7XG4gIHJldHVybiBodG1sLnNsaWNlKDAsIGlkeCkgKyBgJHtjb250ZW50fVxcbmAgKyBodG1sLnNsaWNlKGlkeCk7XG59XG5cbi8vIOKUgOKUgOKUgCBQdWJsaWMgQVBJIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4vKipcbiAqIFNjYW4gY2xpZW50IGJ1aWxkIG91dHB1dCB0byBidWlsZCB0YWdOYW1lIC0+IGNodW5rIHBhdGggbWFwcGluZy5cbiAqIFJlYWRzIFJvbGx1cCBtYW5pZmVzdCBKU09OICh2MC4zLjArIGRldGVybWluaXN0aWMgYXBwcm9hY2gpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRJc2xhbmRDaHVua01hcChcbiAgcm9vdDogc3RyaW5nLFxuICBvdXREaXI6IHN0cmluZyxcbiAgaXNsYW5kczogc3RyaW5nW10sXG4gIGJhc2VQYXRoOiBzdHJpbmcgPSAnLycsXG4pOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+IHtcbiAgY29uc3QgZGlzdERpciA9IHJlc29sdmUocm9vdCwgb3V0RGlyKTtcbiAgY29uc3QgY2xpZW50RGlyID0gcmVzb2x2ZShkaXN0RGlyLCAnY2xpZW50Jyk7XG4gIGNvbnN0IGlzbGFuZENodW5rTWFwOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cbiAgaWYgKCFleGlzdHNTeW5jKGNsaWVudERpcikpIHJldHVybiBpc2xhbmRDaHVua01hcDtcblxuICBjb25zdCBtYW5pZmVzdFBhdGggPSBqb2luKGNsaWVudERpciwgJy52aXRlJywgJ21hbmlmZXN0Lmpzb24nKTtcbiAgaWYgKCFleGlzdHNTeW5jKG1hbmlmZXN0UGF0aCkpIHJldHVybiBpc2xhbmRDaHVua01hcDtcblxuICB0cnkge1xuICAgIGNvbnN0IG1hbmlmZXN0UmF3ID0gcmVhZEZpbGVTeW5jKG1hbmlmZXN0UGF0aCwgJ3V0Zi04Jyk7XG4gICAgY29uc3QgbWFuaWZlc3QgPSBKU09OLnBhcnNlKG1hbmlmZXN0UmF3KTtcblxuICAgIGZvciAoY29uc3QgW19zcmNQYXRoLCBlbnRyeV0gb2YgT2JqZWN0LmVudHJpZXMobWFuaWZlc3QpIGFzIFtzdHJpbmcsIHsgZmlsZT86IHN0cmluZyB9XVtdKSB7XG4gICAgICBpZiAoIWVudHJ5LmZpbGUpIGNvbnRpbnVlO1xuICAgICAgY29uc3QgY2h1bmtNYXRjaCA9IGVudHJ5LmZpbGUubWF0Y2goL15pc2xhbmRzXFwvaXNsYW5kLSguKz8pLVtBLVphLXowLTldK1xcLmpzJC8pO1xuICAgICAgaWYgKGNodW5rTWF0Y2ggJiYgaXNsYW5kcy5pbmNsdWRlcyhjaHVua01hdGNoWzFdKSkge1xuICAgICAgICBpc2xhbmRDaHVua01hcFtjaHVua01hdGNoWzFdXSA9IGAke2Jhc2VQYXRofWNsaWVudC8ke2VudHJ5LmZpbGV9YDtcbiAgICAgIH1cbiAgICAgIGlmIChlbnRyeS5maWxlID09PSAnaXNsYW5kcy9jbGllbnQuanMnKSB7XG4gICAgICAgIGZvciAoY29uc3QgdGFnTmFtZSBvZiBpc2xhbmRzKSB7XG4gICAgICAgICAgaWYgKCFpc2xhbmRDaHVua01hcFt0YWdOYW1lXSkge1xuICAgICAgICAgICAgaXNsYW5kQ2h1bmtNYXBbdGFnTmFtZV0gPSBgJHtiYXNlUGF0aH1jbGllbnQvaXNsYW5kcy9jbGllbnQuanNgO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgfSBjYXRjaCAoZSkge1xuICAgIC8vIE1hbGZvcm1lZCBtYW5pZmVzdCAtIHdhcm4gYW5kIHJldHVybiBlbXB0eSBtYXBcbiAgICBsb2cud2FybihcbiAgICAgIGBDb3VsZCBub3QgcGFyc2UgY2xpZW50IG1hbmlmZXN0OiAke2Zvcm1hdEVycm9yKGUpfWAsXG4gICAgKTtcbiAgfVxuXG4gIHJldHVybiBpc2xhbmRDaHVua01hcDtcbn1cblxuLyoqXG4gKiBJbmplY3QgY2xpZW50IHNjcmlwdCB0YWcgaW50byBhbGwgSFRNTCBmaWxlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluamVjdENsaWVudFNjcmlwdChkaXI6IHN0cmluZywgc2NyaXB0U3JjOiBzdHJpbmcpOiB2b2lkIHtcbiAgY29uc3Qgc2NyaXB0VGFnID0gYCAgPHNjcmlwdCB0eXBlPVwibW9kdWxlXCIgc3JjPVwiJHtzY3JpcHRTcmN9XCI+PC9zY3JpcHQ+YDtcbiAgd2Fsa0h0bWxGaWxlcyhkaXIsIChjb250ZW50KSA9PiB7XG4gICAgaWYgKGNvbnRlbnQuaW5jbHVkZXMoc2NyaXB0U3JjKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEJlZm9yZUJvZHlDbG9zZShjb250ZW50LCBzY3JpcHRUYWcpO1xuICB9KTtcbn1cblxuLyoqXG4gKiBJbmplY3QgQ1NQIDxtZXRhPiB0YWcgaW50byBhbGwgSFRNTCBmaWxlcyAoU1NHLW9ubHkpLlxuICpcbiAqIEZvciBzdGF0aWMgc2l0ZXMsIENTUCBpcyBlbmZvcmNlZCB2aWEgPG1ldGEgaHR0cC1lcXVpdj1cIkNvbnRlbnQtU2VjdXJpdHktUG9saWN5XCI+XG4gKiByYXRoZXIgdGhhbiBIVFRQIGhlYWRlcnMuIE5vbmNlLWJhc2VkIENTUCBpcyBOT1Qgc3VwcG9ydGVkIGZvciBTU0dcbiAqIChub25jZXMgbXVzdCBiZSBwZXItcmVxdWVzdCBhbmQgdW5wcmVkaWN0YWJsZSAtIGltcG9zc2libGUgaW4gc3RhdGljIGZpbGVzKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluamVjdENzcE1ldGEoXG4gIGRpcjogc3RyaW5nLFxuICBjc3BQb2xpY3k6IHN0cmluZyxcbiAgcmVwb3J0T25seSA9IGZhbHNlLFxuICBub25jZSA9IGZhbHNlLFxuKTogdm9pZCB7XG4gIGlmIChub25jZSkge1xuICAgIGxvZy53YXJuKFxuICAgICAgJ0NTUCBub25jZSBpcyBub3Qgc3VwcG9ydGVkIGZvciBTU0cgc3RhdGljIG91dHB1dC4gJyArXG4gICAgICAgICdGYWxsaW5nIGJhY2sgdG8gcG9saWN5LW9ubHkgQ29udGVudC1TZWN1cml0eS1Qb2xpY3kgbWV0YSB0YWcuICcgK1xuICAgICAgICAnRm9yIHBlci1yZXF1ZXN0IG5vbmNlcywgdXNlIGEgc2VydmVyLXNpZGUgbWlkZGxld2FyZSBpbnN0ZWFkLicsXG4gICAgKTtcbiAgfVxuXG4gIGNvbnN0IGhlYWRlck5hbWUgPSByZXBvcnRPbmx5ID8gJ0NvbnRlbnQtU2VjdXJpdHktUG9saWN5LVJlcG9ydC1Pbmx5JyA6ICdDb250ZW50LVNlY3VyaXR5LVBvbGljeSc7XG4gIGNvbnN0IGVzY2FwZWRQb2xpY3kgPSBjc3BQb2xpY3kucmVwbGFjZSgvXCIvZywgJyZxdW90OycpO1xuICBjb25zdCBtZXRhVGFnID0gYCAgPG1ldGEgaHR0cC1lcXVpdj1cIiR7aGVhZGVyTmFtZX1cIiBjb250ZW50PVwiJHtlc2NhcGVkUG9saWN5fVwiPmA7XG5cbiAgd2Fsa0h0bWxGaWxlcyhkaXIsIChjb250ZW50KSA9PiB7XG4gICAgaWYgKGNvbnRlbnQuaW5jbHVkZXMoYGh0dHAtZXF1aXY9XCIke2hlYWRlck5hbWV9XCJgKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEFmdGVySGVhZChjb250ZW50LCBtZXRhVGFnKTtcbiAgfSk7XG59XG5cbi8qKlxuICogRFNEIHBvbHlmaWxsIGZvciBicm93c2VycyB0aGF0IGRvbid0IHN1cHBvcnQgRGVjbGFyYXRpdmUgU2hhZG93IERPTS5cbiAqIEZpcmVmb3ggZG9lcyBOT1Qgc3VwcG9ydCBzaGFkb3dyb290bW9kZSBhcyBvZiAyMDI1LlxuICogVGhpcyBwb2x5ZmlsbCBhdHRhY2hlcyBTaGFkb3cgUm9vdHMgbWFudWFsbHkgdmlhIGF0dGFjaFNoYWRvdygpLlxuICovXG5jb25zdCBEU0RfUE9MWUZJTEwgPSBgXG48c3R5bGU+aHRtbHt2aXNpYmlsaXR5OnZpc2libGUhaW1wb3J0YW50fTwvc3R5bGU+XG48c2NyaXB0PlxuLy8gRFNEIFBvbHlmaWxsIChGaXJlZm94LCBvbGRlciBicm93c2VycylcbihmdW5jdGlvbigpIHtcbiAgdHJ5IHtcbiAgICBjb25zdCB0ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndGVtcGxhdGUnKTtcbiAgICB0LnNldEF0dHJpYnV0ZSgnc2hhZG93cm9vdG1vZGUnLCAnb3BlbicpO1xuICAgIGlmICgnc2hhZG93Um9vdE1vZGUnIGluIHQpIHJldHVybjsgLy8gTmF0aXZlIHN1cHBvcnRcbiAgfSBjYXRjaCB7IC8qIG5hdGl2ZSBkZXRlY3Rpb24gZmFpbGVkIC0gZmFsbGJhY2sgdG8gcG9seWZpbGwgKi8gfVxuICBcbiAgY29uc3QgYXR0YWNoRFNEID0gKHJvb3QpID0+IHtcbiAgICByb290LnF1ZXJ5U2VsZWN0b3JBbGwoJ3RlbXBsYXRlW3NoYWRvd3Jvb3Rtb2RlXScpLmZvckVhY2godHBsID0+IHtcbiAgICAgIGNvbnN0IHBhcmVudCA9IHRwbC5wYXJlbnROb2RlO1xuICAgICAgaWYgKCFwYXJlbnQgfHwgcGFyZW50LnNoYWRvd1Jvb3QpIHJldHVybjtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IG1vZGUgPSB0cGwuZ2V0QXR0cmlidXRlKCdzaGFkb3dyb290bW9kZScpO1xuICAgICAgICBjb25zdCBvcHRzID0geyBtb2RlOiBtb2RlID09PSAnb3BlbicgPyAnb3BlbicgOiAnY2xvc2VkJyB9O1xuICAgICAgICBpZiAodHBsLmhhc0F0dHJpYnV0ZSgnc2hhZG93cm9vdGRlbGVnYXRlc2ZvY3VzJykpIG9wdHMuZGVsZWdhdGVzRm9jdXMgPSB0cnVlO1xuICAgICAgICBjb25zdCBzciA9IHBhcmVudC5hdHRhY2hTaGFkb3cob3B0cyk7XG4gICAgICAgIHNyLmlubmVySFRNTCA9IHRwbC5pbm5lckhUTUw7XG4gICAgICAgIHRwbC5yZW1vdmUoKTtcbiAgICAgICAgYXR0YWNoRFNEKHNyKTsgLy8gSGFuZGxlIG5lc3RlZCBEU0RcbiAgICAgIH0gY2F0Y2ggeyAvKiBub24tZmF0YWw6IHNraXAgbWFsZm9ybWVkIERTRCB0ZW1wbGF0ZXMgKi8gfVxuICAgIH0pO1xuICB9O1xuICBcbiAgaWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJykge1xuICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiBhdHRhY2hEU0QoZG9jdW1lbnQpKTtcbiAgfSBlbHNlIHtcbiAgICBhdHRhY2hEU0QoZG9jdW1lbnQpO1xuICB9XG5cbiAgLy8gRmFsbGJhY2s6IGlmIGFmdGVyIHBvbHlmaWxsIHNvbWUgdGVtcGxhdGVzIHJlbWFpbiB1bmF0dGFjaGVkLCBpbmxpbmUgdGhlbVxuICAvLyBpbnRvIGxpZ2h0IERPTSBzbyBjb250ZW50IGlzIHN0aWxsIHZpc2libGUgZXZlbiBpZiBhdHRhY2hTaGFkb3cgZmFpbGVkLlxuICBjb25zdCBmYWxsYmFja1JlbmRlciA9ICgpID0+IHtcbiAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCd0ZW1wbGF0ZVtzaGFkb3dyb290bW9kZV0nKS5mb3JFYWNoKCh0cGwpID0+IHtcbiAgICAgIGNvbnN0IHBhcmVudCA9IHRwbC5wYXJlbnROb2RlO1xuICAgICAgaWYgKCFwYXJlbnQgfHwgcGFyZW50LnNoYWRvd1Jvb3QpIHJldHVybjtcbiAgICAgIGNvbnN0IGZyYWcgPSB0cGwuY29udGVudC5jbG9uZU5vZGUodHJ1ZSk7XG4gICAgICBwYXJlbnQuYXBwZW5kQ2hpbGQoZnJhZyk7XG4gICAgICB0cGwucmVtb3ZlKCk7XG4gICAgfSk7XG4gIH07XG4gIGlmIChkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnY29tcGxldGUnKSB7XG4gICAgZmFsbGJhY2tSZW5kZXIoKTtcbiAgfSBlbHNlIHtcbiAgICB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsIGZhbGxiYWNrUmVuZGVyLCB7IG9uY2U6IHRydWUgfSk7XG4gIH1cbn0pKCk7XG48L3NjcmlwdD5cbmA7XG5cbi8qKlxuICogSW5qZWN0IERTRCBwb2x5ZmlsbCBpbnRvIGFsbCBIVE1MIGZpbGVzLlxuICogSGFuZGxlcyBicm93c2VycyB0aGF0IGRvbid0IG5hdGl2ZWx5IHN1cHBvcnQgRGVjbGFyYXRpdmUgU2hhZG93IERPTS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluamVjdERzZFBvbHlmaWxsKGRpcjogc3RyaW5nKTogdm9pZCB7XG4gIHdhbGtIdG1sRmlsZXMoZGlyLCAoY29udGVudCkgPT4ge1xuICAgIGlmIChjb250ZW50LmluY2x1ZGVzKCdEU0QgUG9seWZpbGwnKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEFmdGVySGVhZChjb250ZW50LCBEU0RfUE9MWUZJTEwpO1xuICB9KTtcbn1cblxuLy8g4pSA4pSA4pSAIFZpZXcgVHJhbnNpdGlvbnMgQVBJIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4vKipcbiAqIEluamVjdCBWaWV3IFRyYW5zaXRpb25zIG1ldGEgdGFnIGludG8gYWxsIEhUTUwgZmlsZXMuXG4gKlxuICogVGhlIFZpZXcgVHJhbnNpdGlvbnMgQVBJIChDaHJvbWUgMTExKywgU2FmYXJpIDE4KywgRmlyZWZveCAxMjkrKSBlbmFibGVzXG4gKiBzbW9vdGggY3Jvc3MtcGFnZSBhbmltYXRpb25zIGZvciBNUEEgKE11bHRpLVBhZ2UgQXBwKSBuYXZpZ2F0aW9uLlxuICogRm9yIFNTRyBzaXRlcywgdGhpcyBpcyBhIHNpbmdsZSBtZXRhIHRhZyAtIHplcm8gSmF2YVNjcmlwdCByZXF1aXJlZC5cbiAqXG4gKiBXaGVuIGEgdXNlciBjbGlja3MgYSBsaW5rLCB0aGUgYnJvd3NlciBhdXRvbWF0aWNhbGx5IGNyZWF0ZXMgYSBjcm9zcy1mYWRlXG4gKiB0cmFuc2l0aW9uIGJldHdlZW4gdGhlIG9sZCBhbmQgbmV3IHBhZ2UuIE5vIFNQQSByb3V0aW5nIG5lZWRlZC5cbiAqXG4gKiBTdXBwb3J0ZWQgYnJvd3NlcnM6IENocm9tZSAxMTErLCBFZGdlIDExMSssIFNhZmFyaSAxOCssIEZpcmVmb3ggMTI5Ky5cbiAqIFVuc3VwcG9ydGVkIGJyb3dzZXJzIHNpbGVudGx5IGlnbm9yZSB0aGUgbWV0YSB0YWcgKGdyYWNlZnVsIGRlZ3JhZGF0aW9uKS5cbiAqXG4gKiBAc2VlIGh0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTL2RvY3MvV2ViL0FQSS9WaWV3X1RyYW5zaXRpb25zX0FQSVxuICogQHNlZSBodHRwczovL2Nocm9tZXN0YXR1cy5jb20vZmVhdHVyZS81MTkwNjg2NzA3NTY4NjQwXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbmplY3RWaWV3VHJhbnNpdGlvbk1ldGEoZGlyOiBzdHJpbmcpOiB2b2lkIHtcbiAgY29uc3QgbWV0YVRhZyA9ICcgIDxtZXRhIG5hbWU9XCJ2aWV3LXRyYW5zaXRpb25cIiBjb250ZW50PVwic2FtZS1vcmlnaW5cIj4nO1xuXG4gIHdhbGtIdG1sRmlsZXMoZGlyLCAoY29udGVudCkgPT4ge1xuICAgIGlmIChjb250ZW50LmluY2x1ZGVzKCc8bWV0YSBuYW1lPVwidmlldy10cmFuc2l0aW9uXCInKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEFmdGVySGVhZChjb250ZW50LCBtZXRhVGFnKTtcbiAgfSk7XG59XG5cbi8vIOKUgOKUgOKUgCBTcGVjdWxhdGlvbiBSdWxlcyBBUEkg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG5cbi8qKlxuICogQnVpbGQgU3BlY3VsYXRpb24gUnVsZXMgSlNPTiBmcm9tIGNvbmZpZ3VyYXRpb24gYW5kIGtub3duIHJvdXRlcy5cbiAqXG4gKiBJZiB1c2VyLXByb3ZpZGVkIHJ1bGVzIGV4aXN0LCB0aGV5IGFyZSB1c2VkIGRpcmVjdGx5LlxuICogT3RoZXJ3aXNlLCBoZXVyaXN0aWNzIGFyZSBhcHBsaWVkIGJhc2VkIG9uIHRoZSByb3V0ZSBsaXN0OlxuICogLSBIb21lIHBhZ2UgKC8pIC0+IHByZXJlbmRlciAobW9kZXJhdGUpXG4gKiAtIFRvcC1sZXZlbCBzdGF0aWMgcGFnZXMgKDEgbGV2ZWwgZGVlcCkgLT4gcHJlcmVuZGVyIChjb25zZXJ2YXRpdmUpXG4gKiAtIE5lc3RlZCBzdGF0aWMgcGFnZXMgLT4gcHJlZmV0Y2hcbiAqIC0gRHluYW1pYyByb3V0ZXMgKGNvbnRhaW5pbmcgOikgLT4gZXhjbHVkZWQgKGNvbnRlbnQgZGVwZW5kcyBvbiBwYXJhbXMpXG4gKiAtIEFQSSByb3V0ZXMgLT4gZXhjbHVkZWRcbiAqXG4gKiBUaGlzIHR3by10aWVyIHN0cmF0ZWd5IGJhbGFuY2VzIGluc3RhbnQgbmF2aWdhdGlvbiBmb3IgaGlnaC1wcm9iYWJpbGl0eVxuICogdGFyZ2V0cyAocHJlcmVuZGVyKSB3aXRoIGJhbmR3aWR0aC1jb25zY2lvdXMgbG9hZGluZyBmb3IgZGVlcGVyIHBhZ2VzIChwcmVmZXRjaCkuXG4gKlxuICogQHBhcmFtIG9wdGlvbnMgLSBVc2VyLXByb3ZpZGVkIHNwZWN1bGF0aW9uIHJ1bGVzIGNvbmZpZ3VyYXRpb25cbiAqIEBwYXJhbSByb3V0ZXMgLSBLbm93biByb3V0ZSBlbnRyaWVzIGZyb20gcm91dGUgc2Nhbm5lciAoZm9yIGhldXJpc3RpYyBydWxlcylcbiAqIEByZXR1cm5zIFNwZWN1bGF0aW9uIFJ1bGVzIEpTT04gc3RyaW5nLCBvciBlbXB0eSBzdHJpbmcgaWYgbm8gcnVsZXMgYXBwbHlcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkU3BlY3VsYXRpb25SdWxlc0pzb24oXG4gIG9wdGlvbnM6IFNwZWN1bGF0aW9uUnVsZXNPcHRpb25zLFxuICByb3V0ZXM/OiBBcnJheTx7IHBhdGg6IHN0cmluZzsgdHlwZTogc3RyaW5nIH0+LFxuKTogc3RyaW5nIHtcbiAgLy8gSWYgdXNlciBwcm92aWRlZCBleHBsaWNpdCBydWxlcywgdXNlIHRoZW1cbiAgaWYgKG9wdGlvbnMucHJlcmVuZGVyPy5sZW5ndGggfHwgb3B0aW9ucy5wcmVmZXRjaD8ubGVuZ3RoKSB7XG4gICAgY29uc3QgcnVsZXM6IFJlY29yZDxzdHJpbmcsIHVua25vd25bXT4gPSB7fTtcblxuICAgIGlmIChvcHRpb25zLnByZXJlbmRlciAmJiBvcHRpb25zLnByZXJlbmRlci5sZW5ndGggPiAwKSB7XG4gICAgICBydWxlcy5wcmVyZW5kZXIgPSBvcHRpb25zLnByZXJlbmRlci5tYXAoKHBhdHRlcm4pID0+ICh7XG4gICAgICAgIHdoZXJlOiB7IGhyZWZfbWF0Y2hlczogcGF0dGVybiB9LFxuICAgICAgICAuLi4ob3B0aW9ucy5lYWdlcm5lc3MgJiYgb3B0aW9ucy5lYWdlcm5lc3MgIT09ICdtb2RlcmF0ZSdcbiAgICAgICAgICA/IHsgZWFnZXJuZXNzOiBvcHRpb25zLmVhZ2VybmVzcyB9XG4gICAgICAgICAgOiB7fSksXG4gICAgICB9KSk7XG4gICAgfVxuXG4gICAgaWYgKG9wdGlvbnMucHJlZmV0Y2ggJiYgb3B0aW9ucy5wcmVmZXRjaC5sZW5ndGggPiAwKSB7XG4gICAgICBydWxlcy5wcmVmZXRjaCA9IG9wdGlvbnMucHJlZmV0Y2gubWFwKChwYXR0ZXJuKSA9PiAoe1xuICAgICAgICB3aGVyZTogeyBocmVmX21hdGNoZXM6IHBhdHRlcm4gfSxcbiAgICAgIH0pKTtcbiAgICB9XG5cbiAgICAvLyBBZGQgZXhjbHVzaW9uIGlmIHByb3ZpZGVkXG4gICAgaWYgKG9wdGlvbnMuZXhjbHVkZSAmJiBvcHRpb25zLmV4Y2x1ZGUubGVuZ3RoID4gMCkge1xuICAgICAgY29uc3QgZXhjbHVkZVdoZXJlID0gb3B0aW9ucy5leGNsdWRlLm1hcCgocGF0dGVybikgPT4gKHtcbiAgICAgICAgaHJlZl9tYXRjaGVzOiBwYXR0ZXJuLFxuICAgICAgfSkpO1xuICAgICAgLy8gQXBwbHkgZXhjbHVzaW9ucyB0byBib3RoIHByZXJlbmRlciBhbmQgcHJlZmV0Y2hcbiAgICAgIGZvciAoY29uc3Qga2V5IG9mIFsncHJlcmVuZGVyJywgJ3ByZWZldGNoJ10gYXMgY29uc3QpIHtcbiAgICAgICAgaWYgKHJ1bGVzW2tleV0pIHtcbiAgICAgICAgICBmb3IgKGNvbnN0IHJ1bGUgb2YgcnVsZXNba2V5XSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPltdKSB7XG4gICAgICAgICAgICAocnVsZS53aGVyZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikubm90ID0geyBvcl9tYXRjaGVzOiBleGNsdWRlV2hlcmUgfTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkocnVsZXMsIG51bGwsIDIpO1xuICB9XG5cbiAgLy8gSGV1cmlzdGljIG1vZGU6IGdlbmVyYXRlIHJ1bGVzIGZyb20gcm91dGUgbGlzdFxuICBpZiAoIXJvdXRlcyB8fCByb3V0ZXMubGVuZ3RoID09PSAwKSByZXR1cm4gJyc7XG5cbiAgY29uc3Qgc3RhdGljUGFnZVBhdGhzID0gcm91dGVzXG4gICAgLmZpbHRlcigocikgPT4gci50eXBlID09PSAncGFnZScgJiYgIXIucGF0aC5pbmNsdWRlcygnOicpKVxuICAgIC5tYXAoKHIpID0+IHIucGF0aCk7XG5cbiAgaWYgKHN0YXRpY1BhZ2VQYXRocy5sZW5ndGggPT09IDApIHJldHVybiAnJztcblxuICAvLyBUd28tdGllciBzdHJhdGVneTpcbiAgLy8gICAtIEhvbWUgcGFnZSBpcyB0aGUgaGlnaGVzdC1wcm9iYWJpbGl0eSB0YXJnZXQgLT4gcHJlcmVuZGVyIChtb2RlcmF0ZSlcbiAgLy8gICAtIFRvcC1sZXZlbCBwYWdlcyAoZS5nLiAvYWJvdXQsIC9ibG9nKSAtPiBwcmVyZW5kZXIgKGNvbnNlcnZhdGl2ZSlcbiAgLy8gICAtIE5lc3RlZCBwYWdlcyAoZS5nLiAvYmxvZy9wb3N0LXNsdWcsIC9kb2NzL2d1aWRlKSAtPiBwcmVmZXRjaCBvbmx5XG4gIGNvbnN0IHByZXJlbmRlclBhdGhzOiBzdHJpbmdbXSA9IFtdO1xuICBjb25zdCBwcmVmZXRjaFBhdGhzOiBzdHJpbmdbXSA9IFtdO1xuXG4gIGZvciAoY29uc3QgcGF0aCBvZiBzdGF0aWNQYWdlUGF0aHMpIHtcbiAgICBpZiAocGF0aCA9PT0gJy8nKSB7XG4gICAgICAvLyBIaWdoZXN0IHByb2JhYmlsaXR5OiBwcmVyZW5kZXIgd2l0aCBtb2RlcmF0ZSBlYWdlcm5lc3NcbiAgICAgIHByZXJlbmRlclBhdGhzLnB1c2gocGF0aCk7XG4gICAgfSBlbHNlIGlmIChwYXRoLnNwbGl0KCcvJykuZmlsdGVyKEJvb2xlYW4pLmxlbmd0aCA8PSAxKSB7XG4gICAgICAvLyB2MC4xNC4zOiBUb3AtbGV2ZWwgcGFnZXMgZ2V0IGV4YWN0LW1hdGNoIHBhdHRlcm5zLCBOT1Qgd2lsZGNhcmRzLlxuICAgICAgLy8gQWRkaW5nIC9hYm91dC8qIHdvdWxkIHdhc3RlIGJhbmR3aWR0aCBwcmVmZXRjaGluZyBhbGwgL2Fib3V0Lyogc3ViLXBhdGhzXG4gICAgICAvLyB0aGF0IG1heSBub3QgZXhpc3QuIFVzZSBleGFjdCBtYXRjaCBmb3Igc2luZ2xlIHBhZ2VzLlxuICAgICAgcHJlcmVuZGVyUGF0aHMucHVzaChwYXRoKTtcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gRGVlcGVyIHBhZ2VzOiBwcmVmZXRjaCBvbmx5IChsaWdodGVyIHRoYW4gcHJlcmVuZGVyKVxuICAgICAgLy8gVXNlIHdpbGRjYXJkIGZvciBuZXN0ZWQgc2VjdGlvbnMgKGUuZy4sIC9ibG9nLyogbWF0Y2hlcyAvYmxvZy9wb3N0LTEsIC9ibG9nL3Bvc3QtMilcbiAgICAgIHByZWZldGNoUGF0aHMucHVzaChgJHtwYXRofS8qYCk7XG4gICAgfVxuICB9XG5cbiAgY29uc3QgcnVsZXM6IFJlY29yZDxzdHJpbmcsIHVua25vd25bXT4gPSB7fTtcblxuICBpZiAocHJlcmVuZGVyUGF0aHMubGVuZ3RoID4gMCkge1xuICAgIHJ1bGVzLnByZXJlbmRlciA9IHByZXJlbmRlclBhdGhzLm1hcCgocGF0dGVybikgPT4ge1xuICAgICAgLy8gSG9tZSBwYWdlICgvKTogdXNlIGxpc3QgcnVsZSAoc291cmNlICsgdXJscykgLSBubyBkb2N1bWVudCBtYXRjaGVyXG4gICAgICBpZiAocGF0dGVybiA9PT0gJy8nKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc291cmNlOiAnbGlzdCcsXG4gICAgICAgICAgdXJsczogWycvJ10sXG4gICAgICAgICAgZWFnZXJuZXNzOiAnbW9kZXJhdGUnLFxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgLy8gVG9wLWxldmVsIHBhZ2VzICgvYWJvdXQsIC9ibG9nKTogdXNlIGRvY3VtZW50IHJ1bGUgKHdoZXJlOiBocmVmX21hdGNoZXMpXG4gICAgICByZXR1cm4ge1xuICAgICAgICB3aGVyZTogeyBocmVmX21hdGNoZXM6IHBhdHRlcm4gfSxcbiAgICAgICAgZWFnZXJuZXNzOiAnY29uc2VydmF0aXZlJyxcbiAgICAgIH07XG4gICAgfSk7XG4gIH1cblxuICBpZiAocHJlZmV0Y2hQYXRocy5sZW5ndGggPiAwKSB7XG4gICAgcnVsZXMucHJlZmV0Y2ggPSBwcmVmZXRjaFBhdGhzLm1hcCgocGF0dGVybikgPT4gKHtcbiAgICAgIHdoZXJlOiB7IGhyZWZfbWF0Y2hlczogcGF0dGVybiB9LFxuICAgIH0pKTtcbiAgfVxuXG4gIC8vIEV4Y2x1ZGUgQVBJIHJvdXRlcyBpZiBhbnkgZXhpc3RcbiAgY29uc3QgYXBpUGF0aHMgPSByb3V0ZXNcbiAgICAuZmlsdGVyKChyKSA9PiByLnR5cGUgPT09ICdhcGknKVxuICAgIC5tYXAoKHIpID0+IGAke3IucGF0aH0vKmApO1xuXG4gIGlmIChhcGlQYXRocy5sZW5ndGggPiAwKSB7XG4gICAgY29uc3QgZXhjbHVkZVdoZXJlID0gYXBpUGF0aHMubWFwKChwKSA9PiAoeyBocmVmX21hdGNoZXM6IHAgfSkpO1xuICAgIGZvciAoY29uc3Qga2V5IG9mIFsncHJlcmVuZGVyJywgJ3ByZWZldGNoJ10gYXMgY29uc3QpIHtcbiAgICAgIGlmIChydWxlc1trZXldKSB7XG4gICAgICAgIGZvciAoY29uc3QgcnVsZSBvZiBydWxlc1trZXldIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+W10pIHtcbiAgICAgICAgICAvLyBPbmx5IGFkZCBleGNsdXNpb24gdG8gZG9jdW1lbnQgcnVsZXMgKHdoZXJlKSAtIGxpc3QgcnVsZXMgKHNvdXJjZSt1cmxzKSBkb24ndCBoYXZlIHdoZXJlXG4gICAgICAgICAgaWYgKHJ1bGUud2hlcmUgJiYgdHlwZW9mIHJ1bGUud2hlcmUgPT09ICdvYmplY3QnKSB7XG4gICAgICAgICAgICAocnVsZS53aGVyZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikubm90ID0geyBvcl9tYXRjaGVzOiBleGNsdWRlV2hlcmUgfTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gSlNPTi5zdHJpbmdpZnkocnVsZXMsIG51bGwsIDIpO1xufVxuXG4vKipcbiAqIEluamVjdCBTcGVjdWxhdGlvbiBSdWxlcyBpbnRvIGFsbCBIVE1MIGZpbGVzLlxuICpcbiAqIFRoZSBTcGVjdWxhdGlvbiBSdWxlcyBBUEkgKENocm9tZSAxMjErKSBlbmFibGVzIHRoZSBicm93c2VyIHRvXG4gKiBwcmVmZXRjaCBvciBwcmVyZW5kZXIgcGFnZXMgYmVmb3JlIHRoZSB1c2VyIG5hdmlnYXRlcyB0byB0aGVtLlxuICogVGhpcyBtYWtlcyBuYXZpZ2F0aW9uIGZlZWwgaW5zdGFudCBmb3IgU1NHIHNpdGVzLlxuICpcbiAqIFNwZWN1bGF0aW9uIFJ1bGVzIGFyZSBkZWNsYXJhdGl2ZSBKU09OIGluIGEgPHNjcmlwdCB0eXBlPVwic3BlY3VsYXRpb25ydWxlc1wiPiB0YWcuXG4gKiBUaGV5IGhhdmUgemVybyBKYXZhU2NyaXB0IHJ1bnRpbWUgY29zdCAtIHRoZSBicm93c2VyIGhhbmRsZXMgZXZlcnl0aGluZyBuYXRpdmVseS5cbiAqXG4gKiBPbmx5IENocm9taXVtLWJhc2VkIGJyb3dzZXJzIChDaHJvbWUsIEVkZ2UpIHN1cHBvcnQgdGhpcyBhcyBvZiAyMDI2LlxuICogU2FmYXJpIGFuZCBGaXJlZm94IHNpbGVudGx5IGlnbm9yZSB0aGUgc2NyaXB0IHRhZyAoZ3JhY2VmdWwgZGVncmFkYXRpb24pLlxuICpcbiAqIEBwYXJhbSBkaXIgLSBPdXRwdXQgZGlyZWN0b3J5IGNvbnRhaW5pbmcgSFRNTCBmaWxlc1xuICogQHBhcmFtIHJ1bGVzSnNvbiAtIFByZS1idWlsdCBzcGVjdWxhdGlvbiBydWxlcyBKU09OIHN0cmluZ1xuICovXG5leHBvcnQgZnVuY3Rpb24gaW5qZWN0U3BlY3VsYXRpb25SdWxlcyhkaXI6IHN0cmluZywgcnVsZXNKc29uOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKCFydWxlc0pzb24udHJpbSgpKSByZXR1cm47XG5cbiAgY29uc3Qgc2NyaXB0VGFnID0gYCAgPHNjcmlwdCB0eXBlPVwic3BlY3VsYXRpb25ydWxlc1wiPlxcbiAgJHtydWxlc0pzb259XFxuICA8L3NjcmlwdD5gO1xuXG4gIHdhbGtIdG1sRmlsZXMoZGlyLCAoY29udGVudCkgPT4ge1xuICAgIGlmIChjb250ZW50LmluY2x1ZGVzKCc8c2NyaXB0IHR5cGU9XCJzcGVjdWxhdGlvbnJ1bGVzXCInKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEFmdGVySGVhZChjb250ZW50LCBzY3JpcHRUYWcpO1xuICB9KTtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Q0FjQyxHQUVELFNBQVMsSUFBSSxFQUFFLE9BQU8sUUFBUSxZQUFZO0FBQzFDLFNBQVMsVUFBVSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsYUFBYSxRQUFRLFVBQVU7QUFDL0UsU0FBUyxZQUFZLFFBQVEsMkJBQTJCO0FBQ3hELFNBQVMsV0FBVyxRQUFRLDJCQUEyQjtBQUd2RCxNQUFNLE1BQU0sYUFBYTtBQUV6QiwwQkFBMEI7QUFFMUI7Ozs7Q0FJQyxHQUNELFNBQVMsY0FDUCxHQUFXLEVBQ1gsT0FBNkQ7RUFFN0QsTUFBTSxVQUFVLFlBQVksS0FBSztJQUFFLGVBQWU7RUFBSztFQUN2RCxLQUFLLE1BQU0sU0FBUyxRQUFTO0lBQzNCLE1BQU0sV0FBVyxLQUFLLEtBQUssTUFBTSxJQUFJO0lBQ3JDLElBQUksTUFBTSxXQUFXLElBQUk7TUFDdkIsY0FBYyxVQUFVO0lBQzFCLE9BQU8sSUFBSSxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVTtNQUN2QyxNQUFNLFVB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
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @openelement/ssg - Route scanner file-system helpers
|
|
3
|
+
*
|
|
4
|
+
* Thin wrappers around Node.js fs promises that swallow errors and return
|
|
5
|
+
* `undefined` (or `null`) instead of throwing. Used by the route scanner to
|
|
6
|
+
* keep file-system concerns separate from AST extraction and orchestration.
|
|
7
|
+
*/ import { readdir, readFile, stat } from 'node:fs/promises';
|
|
8
|
+
/** Read a directory, returning `undefined` if it cannot be read. */ export async function safeReadDir(dirPath) {
|
|
9
|
+
try {
|
|
10
|
+
return await readdir(dirPath);
|
|
11
|
+
} catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Read a text file, returning `undefined` if it cannot be read. */ export async function safeReadFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return await readFile(filePath, 'utf-8');
|
|
18
|
+
} catch {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Stat a path, returning `undefined` if it cannot be stated. */ export async function safeStat(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
return await stat(filePath);
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImZpbGU6Ly8vaG9tZS9ydW5uZXIvd29yay9vcGVuZWxlbWVudC9vcGVuZWxlbWVudC9wYWNrYWdlcy9zc2cvc3JjL3JvdXRlLXNjYW5uZXItZnMudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBAb3BlbmVsZW1lbnQvc3NnIC0gUm91dGUgc2Nhbm5lciBmaWxlLXN5c3RlbSBoZWxwZXJzXG4gKlxuICogVGhpbiB3cmFwcGVycyBhcm91bmQgTm9kZS5qcyBmcyBwcm9taXNlcyB0aGF0IHN3YWxsb3cgZXJyb3JzIGFuZCByZXR1cm5cbiAqIGB1bmRlZmluZWRgIChvciBgbnVsbGApIGluc3RlYWQgb2YgdGhyb3dpbmcuIFVzZWQgYnkgdGhlIHJvdXRlIHNjYW5uZXIgdG9cbiAqIGtlZXAgZmlsZS1zeXN0ZW0gY29uY2VybnMgc2VwYXJhdGUgZnJvbSBBU1QgZXh0cmFjdGlvbiBhbmQgb3JjaGVzdHJhdGlvbi5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFN0YXRzIH0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgeyByZWFkZGlyLCByZWFkRmlsZSwgc3RhdCB9IGZyb20gJ25vZGU6ZnMvcHJvbWlzZXMnO1xuXG4vKiogUmVhZCBhIGRpcmVjdG9yeSwgcmV0dXJuaW5nIGB1bmRlZmluZWRgIGlmIGl0IGNhbm5vdCBiZSByZWFkLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNhZmVSZWFkRGlyKGRpclBhdGg6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nW10gfCB1bmRlZmluZWQ+IHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gYXdhaXQgcmVhZGRpcihkaXJQYXRoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxufVxuXG4vKiogUmVhZCBhIHRleHQgZmlsZSwgcmV0dXJuaW5nIGB1bmRlZmluZWRgIGlmIGl0IGNhbm5vdCBiZSByZWFkLiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHNhZmVSZWFkRmlsZShmaWxlUGF0aDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+IHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gYXdhaXQgcmVhZEZpbGUoZmlsZVBhdGgsICd1dGYtOCcpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbi8qKiBTdGF0IGEgcGF0aCwgcmV0dXJuaW5nIGB1bmRlZmluZWRgIGlmIGl0IGNhbm5vdCBiZSBzdGF0ZWQuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2FmZVN0YXQoZmlsZVBhdGg6IHN0cmluZyk6IFByb21pc2U8U3RhdHMgfCB1bmRlZmluZWQ+IHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gYXdhaXQgc3RhdChmaWxlUGF0aCk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7O0NBTUMsR0FHRCxTQUFTLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxRQUFRLG1CQUFtQjtBQUUzRCxrRUFBa0UsR0FDbEUsT0FBTyxlQUFlLFlBQVksT0FBZTtFQUMvQyxJQUFJO0lBQ0YsT0FBTyxNQUFNLFFBQVE7RUFDdkIsRUFBRSxPQUFNO0lBQ04sT0FBTztFQUNUO0FBQ0Y7QUFFQSxrRUFBa0UsR0FDbEUsT0FBTyxlQUFlLGFBQWEsUUFBZ0I7RUFDakQsSUFBSTtJQUNGLE9BQU8sTUFBTSxTQUFTLFVBQVU7RUFDbEMsRUFBRSxPQUFNO0lBQ04sT0FBTztFQUNUO0FBQ0Y7QUFFQSwrREFBK0QsR0FDL0QsT0FBTyxlQUFlLFNBQVMsUUFBZ0I7RUFDN0MsSUFBSTtJQUNGLE9BQU8sTUFBTSxLQUFLO0VBQ3BCLEVBQUUsT0FBTTtJQUNOLE9BQU87RUFDVDtBQUNGIn0=
|