@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,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, '&quot;');
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+W10pIHtcbiAgICAgICAgICAvLyBPbmx5IGFkZCBleGNsdXNpb24gdG8gZG9jdW1lbnQgcnVsZXMgKHdoZXJlKSAtIGxpc3QgcnVsZXMgKHNvdXJjZSt1cmxzKSBkb24ndCBoYXZlIHdoZXJlXG4gICAgICAgICAgaWYgKHJ1bGUud2hlcmUgJiYgdHlwZW9mIHJ1bGUud2hlcmUgPT09ICdvYmplY3QnKSB7XG4gICAgICAgICAgICAocnVsZS53aGVyZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikubm90ID0geyBvcl9tYXRjaGVzOiBleGNsdWRlV2hlcmUgfTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICByZXR1cm4gSlNPTi5zdHJpbmdpZnkocnVsZXMsIG51bGwsIDIpO1xufVxuXG4vKipcbiAqIEluamVjdCBTcGVjdWxhdGlvbiBSdWxlcyBpbnRvIGFsbCBIVE1MIGZpbGVzLlxuICpcbiAqIFRoZSBTcGVjdWxhdGlvbiBSdWxlcyBBUEkgKENocm9tZSAxMjErKSBlbmFibGVzIHRoZSBicm93c2VyIHRvXG4gKiBwcmVmZXRjaCBvciBwcmVyZW5kZXIgcGFnZXMgYmVmb3JlIHRoZSB1c2VyIG5hdmlnYXRlcyB0byB0aGVtLlxuICogVGhpcyBtYWtlcyBuYXZpZ2F0aW9uIGZlZWwgaW5zdGFudCBmb3IgU1NHIHNpdGVzLlxuICpcbiAqIFNwZWN1bGF0aW9uIFJ1bGVzIGFyZSBkZWNsYXJhdGl2ZSBKU09OIGluIGEgPHNjcmlwdCB0eXBlPVwic3BlY3VsYXRpb25ydWxlc1wiPiB0YWcuXG4gKiBUaGV5IGhhdmUgemVybyBKYXZhU2NyaXB0IHJ1bnRpbWUgY29zdCAtIHRoZSBicm93c2VyIGhhbmRsZXMgZXZlcnl0aGluZyBuYXRpdmVseS5cbiAqXG4gKiBPbmx5IENocm9taXVtLWJhc2VkIGJyb3dzZXJzIChDaHJvbWUsIEVkZ2UpIHN1cHBvcnQgdGhpcyBhcyBvZiAyMDI2LlxuICogU2FmYXJpIGFuZCBGaXJlZm94IHNpbGVudGx5IGlnbm9yZSB0aGUgc2NyaXB0IHRhZyAoZ3JhY2VmdWwgZGVncmFkYXRpb24pLlxuICpcbiAqIEBwYXJhbSBkaXIgLSBPdXRwdXQgZGlyZWN0b3J5IGNvbnRhaW5pbmcgSFRNTCBmaWxlc1xuICogQHBhcmFtIHJ1bGVzSnNvbiAtIFByZS1idWlsdCBzcGVjdWxhdGlvbiBydWxlcyBKU09OIHN0cmluZ1xuICovXG5leHBvcnQgZnVuY3Rpb24gaW5qZWN0U3BlY3VsYXRpb25SdWxlcyhkaXI6IHN0cmluZywgcnVsZXNKc29uOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKCFydWxlc0pzb24udHJpbSgpKSByZXR1cm47XG5cbiAgY29uc3Qgc2NyaXB0VGFnID0gYCAgPHNjcmlwdCB0eXBlPVwic3BlY3VsYXRpb25ydWxlc1wiPlxcbiAgJHtydWxlc0pzb259XFxuICA8L3NjcmlwdD5gO1xuXG4gIHdhbGtIdG1sRmlsZXMoZGlyLCAoY29udGVudCkgPT4ge1xuICAgIGlmIChjb250ZW50LmluY2x1ZGVzKCc8c2NyaXB0IHR5cGU9XCJzcGVjdWxhdGlvbnJ1bGVzXCInKSkgcmV0dXJuIG51bGw7XG4gICAgcmV0dXJuIGluc2VydEFmdGVySGVhZChjb250ZW50LCBzY3JpcHRUYWcpO1xuICB9KTtcbn1cbiJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Q0FjQyxHQUVELFNBQVMsSUFBSSxFQUFFLE9BQU8sUUFBUSxZQUFZO0FBQzFDLFNBQVMsVUFBVSxFQUFFLFdBQVcsRUFBRSxZQUFZLEVBQUUsYUFBYSxRQUFRLFVBQVU7QUFDL0UsU0FBUyxZQUFZLFFBQVEsMkJBQTJCO0FBQ3hELFNBQVMsV0FBVyxRQUFRLDJCQUEyQjtBQUd2RCxNQUFNLE1BQU0sYUFBYTtBQUV6QiwwQkFBMEI7QUFFMUI7Ozs7Q0FJQyxHQUNELFNBQVMsY0FDUCxHQUFXLEVBQ1gsT0FBNkQ7RUFFN0QsTUFBTSxVQUFVLFlBQVksS0FBSztJQUFFLGVBQWU7RUFBSztFQUN2RCxLQUFLLE1BQU0sU0FBUyxRQUFTO0lBQzNCLE1BQU0sV0FBVyxLQUFLLEtBQUssTUFBTSxJQUFJO0lBQ3JDLElBQUksTUFBTSxXQUFXLElBQUk7TUFDdkIsY0FBYyxVQUFVO0lBQzFCLE9BQU8sSUFBSSxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVTtNQUN2QyxNQUFNLFVBQVUsYUFBYSxVQUFVO01BQ3ZDLE1BQU0sU0FBUyxRQUFRLFNBQVM7TUFDaEMsSUFBSSxXQUFXLE1BQU07UUFDbkIsY0FBYyxVQUFVLFFBQVE7TUFDbEM7SUFDRjtFQUNGO0FBQ0Y7QUFFQSwwRUFBMEU7QUFFMUUsNkVBQTZFLEdBQzdFLE9BQU8sU0FBUyxnQkFBZ0IsSUFBWSxFQUFFLE9BQWU7RUFDM0Qsa0VBQWtFO0VBQ2xFLE1BQU0sWUFBWSxLQUFLLEtBQUssQ0FBQztFQUM3QixJQUFJLENBQUMsV0FBVztJQUNkLE9BQU8sS0FBSyxVQUFVLENBQUMsU0FBUyxLQUFLLFVBQVUsQ0FBQyxXQUM1QyxLQUFLLE9BQU8sQ0FBQywrQkFBK0IsQ0FBQyxjQUFjLEVBQUUsUUFBUSxTQUFTLENBQUMsSUFDL0UsQ0FBQyxVQUFVLEVBQUUsUUFBUSxXQUFXLEVBQUUsTUFBTTtFQUM5QztFQUNBLElBQUksVUFBVSxLQUFLLEtBQUssV0FBVztJQUNqQyxNQUFNLElBQUksTUFBTTtFQUNsQjtFQUNBLE1BQU0sVUFBVSxVQUFVLEtBQUssR0FBRyxTQUFTLENBQUMsRUFBRSxDQUFDLE1BQU07RUFDckQsT0FBTyxLQUFLLEtBQUssQ0FBQyxHQUFHLFdBQVcsQ0FBQyxJQUFJLEVBQUUsU0FBUyxHQUFHLEtBQUssS0FBSyxDQUFDO0FBQ2hFO0FBRUEsMERBQTBELEdBQzFELFNBQVMsc0JBQXNCLElBQVksRUFBRSxPQUFlO0VBQzFELE1BQU0saUJBQWlCLEtBQUssS0FBSyxDQUFDO0VBQ2xDLElBQUksQ0FBQyxnQkFBZ0I7SUFDbkIsT0FBTyxPQUFPLENBQUMsRUFBRSxFQUFFLFFBQVEsRUFBRSxDQUFDO0VBQ2hDO0VBQ0EsSUFBSSxlQUFlLEtBQUssS0FBSyxXQUFXO0lBQ3RDLE1BQU0sSUFBSSxNQUFNO0VBQ2xCO0VBQ0EsTUFBTSxNQUFNLGVBQWUsS0FBSztFQUNoQyxPQUFPLEtBQUssS0FBSyxDQUFDLEdBQUcsT0FBTyxHQUFHLFFBQVEsRUFBRSxDQUFDLEdBQUcsS0FBSyxLQUFLLENBQUM7QUFDMUQ7QUFFQSwwRUFBMEU7QUFFMUU7OztDQUdDLEdBQ0QsT0FBTyxTQUFTLG9CQUNkLElBQVksRUFDWixNQUFjLEVBQ2QsT0FBaUIsRUFDakIsV0FBbUIsR0FBRztFQUV0QixNQUFNLFVBQVUsUUFBUSxNQUFNO0VBQzlCLE1BQU0sWUFBWSxRQUFRLFNBQVM7RUFDbkMsTUFBTSxpQkFBeUMsQ0FBQztFQUVoRCxJQUFJLENBQUMsV0FBVyxZQUFZLE9BQU87RUFFbkMsTUFBTSxlQUFlLEtBQUssV0FBVyxTQUFTO0VBQzlDLElBQUksQ0FBQyxXQUFXLGVBQWUsT0FBTztFQUV0QyxJQUFJO0lBQ0YsTUFBTSxjQUFjLGFBQWEsY0FBYztJQUMvQyxNQUFNLFdBQVcsS0FBSyxLQUFLLENBQUM7SUFFNUIsS0FBSyxNQUFNLENBQUMsVUFBVSxNQUFNLElBQUksT0FBTyxPQUFPLENBQUMsVUFBNEM7TUFDekYsSUFBSSxDQUFDLE1BQU0sSUFBSSxFQUFFO01BQ2pCLE1BQU0sYUFBYSxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUM7TUFDcEMsSUFBSSxjQUFjLFFBQVEsUUFBUSxDQUFDLFVBQVUsQ0FBQyxFQUFFLEdBQUc7UUFDakQsY0FBYyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsR0FBRyxHQUFHLFNBQVMsT0FBTyxFQUFFLE1BQU0sSUFBSSxFQUFFO01BQ25FO01BQ0EsSUFBSSxNQUFNLElBQUksS0FBSyxxQkFBcUI7UUFDdEMsS0FBSyxNQUFNLFdBQVcsUUFBUztVQUM3QixJQUFJLENBQUMsY0FBYyxDQUFDLFFBQVEsRUFBRTtZQUM1QixjQUFjLENBQUMsUUFBUSxHQUFHLEdBQUcsU0FBUyx3QkFBd0IsQ0FBQztVQUNqRTtRQUNGO01BQ0Y7SUFDRjtFQUNGLEVBQUUsT0FBTyxHQUFHO0lBQ1YsaURBQWlEO0lBQ2pELElBQUksSUFBSSxDQUNOLENBQUMsaUNBQWlDLEVBQUUsWUFBWSxJQUFJO0VBRXhEO0VBRUEsT0FBTztBQUNUO0FBRUE7O0NBRUMsR0FDRCxPQUFPLFNBQVMsbUJBQW1CLEdBQVcsRUFBRSxTQUFpQjtFQUMvRCxNQUFNLFlBQVksQ0FBQyw2QkFBNkIsRUFBRSxVQUFVLFdBQVcsQ0FBQztFQUN4RSxjQUFjLEtBQUssQ0FBQztJQUNsQixJQUFJLFFBQVEsUUFBUSxDQUFDLFlBQVksT0FBTztJQUN4QyxPQUFPLHNCQUFzQixTQUFTO0VBQ3hDO0FBQ0Y7QUFFQTs7Ozs7O0NBTUMsR0FDRCxPQUFPLFNBQVMsY0FDZCxHQUFXLEVBQ1gsU0FBaUIsRUFDakIsYUFBYSxLQUFLLEVBQ2xCLFFBQVEsS0FBSztFQUViLElBQUksT0FBTztJQUNULElBQUksSUFBSSxDQUNOLHVEQUNFLG1FQUNBO0VBRU47RUFFQSxNQUFNLGFBQWEsYUFBYSx3Q0FBd0M7RUFDeEUsTUFBTSxnQkFBZ0IsVUFBVSxPQUFPLENBQUMsTUFBTTtFQUM5QyxNQUFNLFVBQVUsQ0FBQyxvQkFBb0IsRUFBRSxXQUFXLFdBQVcsRUFBRSxjQUFjLEVBQUUsQ0FBQztFQUVoRixjQUFjLEtBQUssQ0FBQztJQUNsQixJQUFJLFFBQVEsUUFBUSxDQUFDLENBQUMsWUFBWSxFQUFFLFdBQVcsQ0FBQyxDQUFDLEdBQUcsT0FBTztJQUMzRCxPQUFPLGdCQUFnQixTQUFTO0VBQ2xDO0FBQ0Y7QUFFQTs7OztDQUlDLEdBQ0QsTUFBTSxlQUFlLENBQUM7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQW1EdEIsQ0FBQztBQUVEOzs7Q0FHQyxHQUNELE9BQU8sU0FBUyxrQkFBa0IsR0FBVztFQUMzQyxjQUFjLEtBQUssQ0FBQztJQUNsQixJQUFJLFFBQVEsUUFBUSxDQUFDLGlCQUFpQixPQUFPO0lBQzdDLE9BQU8sZ0JBQWdCLFNBQVM7RUFDbEM7QUFDRjtBQUVBLHlFQUF5RTtBQUV6RTs7Ozs7Ozs7Ozs7Ozs7O0NBZUMsR0FDRCxPQUFPLFNBQVMseUJBQXlCLEdBQVc7RUFDbEQsTUFBTSxVQUFVO0VBRWhCLGNBQWMsS0FBSyxDQUFDO0lBQ2xCLElBQUksUUFBUSxRQUFRLENBQUMsaUNBQWlDLE9BQU87SUFDN0QsT0FBTyxnQkFBZ0IsU0FBUztFQUNsQztBQUNGO0FBRUEseUVBQXlFO0FBRXpFOzs7Ozs7Ozs7Ozs7Ozs7OztDQWlCQyxHQUNELE9BQU8sU0FBUywwQkFDZCxPQUFnQyxFQUNoQyxNQUE4QztFQUU5Qyw0Q0FBNEM7RUFDNUMsSUFBSSxRQUFRLFNBQVMsRUFBRSxVQUFVLFFBQVEsUUFBUSxFQUFFLFFBQVE7SUFDekQsTUFBTSxRQUFtQyxDQUFDO0lBRTFDLElBQUksUUFBUSxTQUFTLElBQUksUUFBUSxTQUFTLENBQUMsTUFBTSxHQUFHLEdBQUc7TUFDckQsTUFBTSxTQUFTLEdBQUcsUUFBUSxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsVUFBWSxDQUFDO1VBQ3BELE9BQU87WUFBRSxjQUFjO1VBQVE7VUFDL0IsR0FBSSxRQUFRLFNBQVMsSUFBSSxRQUFRLFNBQVMsS0FBSyxhQUMzQztZQUFFLFdBQVcsUUFBUSxTQUFTO1VBQUMsSUFDL0IsQ0FBQyxDQUFDO1FBQ1IsQ0FBQztJQUNIO0lBRUEsSUFBSSxRQUFRLFFBQVEsSUFBSSxRQUFRLFFBQVEsQ0FBQyxNQUFNLEdBQUcsR0FBRztNQUNuRCxNQUFNLFFBQVEsR0FBRyxRQUFRLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxVQUFZLENBQUM7VUFDbEQsT0FBTztZQUFFLGNBQWM7VUFBUTtRQUNqQyxDQUFDO0lBQ0g7SUFFQSw0QkFBNEI7SUFDNUIsSUFBSSxRQUFRLE9BQU8sSUFBSSxRQUFRLE9BQU8sQ0FBQyxNQUFNLEdBQUcsR0FBRztNQUNqRCxNQUFNLGVBQWUsUUFBUSxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUMsVUFBWSxDQUFDO1VBQ3JELGNBQWM7UUFDaEIsQ0FBQztNQUNELGtEQUFrRDtNQUNsRCxLQUFLLE1BQU0sT0FBTztRQUFDO1FBQWE7T0FBVyxDQUFXO1FBQ3BELElBQUksS0FBSyxDQUFDLElBQUksRUFBRTtVQUNkLEtBQUssTUFBTSxRQUFRLEtBQUssQ0FBQyxJQUFJLENBQStCO1lBQ3pELEtBQUssS0FBSyxDQUE2QixHQUFHLEdBQUc7Y0FBRSxZQUFZO1lBQWE7VUFDM0U7UUFDRjtNQUNGO0lBQ0Y7SUFFQSxPQUFPLEtBQUssU0FBUyxDQUFDLE9BQU8sTUFBTTtFQUNyQztFQUVBLGlEQUFpRDtFQUNqRCxJQUFJLENBQUMsVUFBVSxPQUFPLE1BQU0sS0FBSyxHQUFHLE9BQU87RUFFM0MsTUFBTSxrQkFBa0IsT0FDckIsTUFBTSxDQUFDLENBQUMsSUFBTSxFQUFFLElBQUksS0FBSyxVQUFVLENBQUMsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQ3BELEdBQUcsQ0FBQyxDQUFDLElBQU0sRUFBRSxJQUFJO0VBRXBCLElBQUksZ0JBQWdCLE1BQU0sS0FBSyxHQUFHLE9BQU87RUFFekMscUJBQXFCO0VBQ3JCLDBFQUEwRTtFQUMxRSx1RUFBdUU7RUFDdkUsd0VBQXdFO0VBQ3hFLE1BQU0saUJBQTJCLEVBQUU7RUFDbkMsTUFBTSxnQkFBMEIsRUFBRTtFQUVsQyxLQUFLLE1BQU0sUUFBUSxnQkFBaUI7SUFDbEMsSUFBSSxTQUFTLEtBQUs7TUFDaEIseURBQXlEO01BQ3pELGVBQWUsSUFBSSxDQUFDO0lBQ3RCLE9BQU8sSUFBSSxLQUFLLEtBQUssQ0FBQyxLQUFLLE1BQU0sQ0FBQyxTQUFTLE1BQU0sSUFBSSxHQUFHO01BQ3RELG9FQUFvRTtNQUNwRSwyRUFBMkU7TUFDM0Usd0RBQXdEO01BQ3hELGVBQWUsSUFBSSxDQUFDO0lBQ3RCLE9BQU87TUFDTCx1REFBdUQ7TUFDdkQsc0ZBQXNGO01BQ3RGLGNBQWMsSUFBSSxDQUFDLEdBQUcsS0FBSyxFQUFFLENBQUM7SUFDaEM7RUFDRjtFQUVBLE1BQU0sUUFBbUMsQ0FBQztFQUUxQyxJQUFJLGVBQWUsTUFBTSxHQUFHLEdBQUc7SUFDN0IsTUFBTSxTQUFTLEdBQUcsZUFBZSxHQUFHLENBQUMsQ0FBQztNQUNwQyxxRUFBcUU7TUFDckUsSUFBSSxZQUFZLEtBQUs7UUFDbkIsT0FBTztVQUNMLFFBQVE7VUFDUixNQUFNO1lBQUM7V0FBSTtVQUNYLFdBQVc7UUFDYjtNQUNGO01BQ0EsMkVBQTJFO01BQzNFLE9BQU87UUFDTCxPQUFPO1VBQUUsY0FBYztRQUFRO1FBQy9CLFdBQVc7TUFDYjtJQUNGO0VBQ0Y7RUFFQSxJQUFJLGNBQWMsTUFBTSxHQUFHLEdBQUc7SUFDNUIsTUFBTSxRQUFRLEdBQUcsY0FBYyxHQUFHLENBQUMsQ0FBQyxVQUFZLENBQUM7UUFDL0MsT0FBTztVQUFFLGNBQWM7UUFBUTtNQUNqQyxDQUFDO0VBQ0g7RUFFQSxrQ0FBa0M7RUFDbEMsTUFBTSxXQUFXLE9BQ2QsTUFBTSxDQUFDLENBQUMsSUFBTSxFQUFFLElBQUksS0FBSyxPQUN6QixHQUFHLENBQUMsQ0FBQyxJQUFNLEdBQUcsRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDO0VBRTNCLElBQUksU0FBUyxNQUFNLEdBQUcsR0FBRztJQUN2QixNQUFNLGVBQWUsU0FBUyxHQUFHLENBQUMsQ0FBQyxJQUFNLENBQUM7UUFBRSxjQUFjO01BQUUsQ0FBQztJQUM3RCxLQUFLLE1BQU0sT0FBTztNQUFDO01BQWE7S0FBVyxDQUFXO01BQ3BELElBQUksS0FBSyxDQUFDLElBQUksRUFBRTtRQUNkLEtBQUssTUFBTSxRQUFRLEtBQUssQ0FBQyxJQUFJLENBQStCO1VBQzFELDJGQUEyRjtVQUMzRixJQUFJLEtBQUssS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLEtBQUssVUFBVTtZQUMvQyxLQUFLLEtBQUssQ0FBNkIsR0FBRyxHQUFHO2NBQUUsWUFBWTtZQUFhO1VBQzNFO1FBQ0Y7TUFDRjtJQUNGO0VBQ0Y7RUFFQSxPQUFPLEtBQUssU0FBUyxDQUFDLE9BQU8sTUFBTTtBQUNyQztBQUVBOzs7Ozs7Ozs7Ozs7Ozs7Q0FlQyxHQUNELE9BQU8sU0FBUyx1QkFBdUIsR0FBVyxFQUFFLFNBQWlCO0VBQ25FLElBQUksQ0FBQyxVQUFVLElBQUksSUFBSTtFQUV2QixNQUFNLFlBQVksQ0FBQyxzQ0FBc0MsRUFBRSxVQUFVLGFBQWEsQ0FBQztFQUVuRixjQUFjLEtBQUssQ0FBQztJQUNsQixJQUFJLFFBQVEsUUFBUSxDQUFDLG9DQUFvQyxPQUFPO0lBQ2hFLE9BQU8sZ0JBQWdCLFNBQVM7RUFDbEM7QUFDRiJ9
@@ -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=