cdk-nextjs 0.7.12 → 0.7.13
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/.jsii +237 -81
- package/.jsii.tabl.json +1 -1
- package/API.md +152 -0
- package/README.md +115 -100
- package/docs/breaking-changes.md +4 -2
- package/docs/development-guide.md +10 -0
- package/lib/adapter/adapter.mjs +4 -1
- package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +21 -0
- package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
- package/lib/nextjs-api.js +1 -1
- package/lib/nextjs-build/nextjs-build.js +1 -1
- package/lib/nextjs-cache.js +1 -1
- package/lib/nextjs-compute/nextjs-containers.js +1 -1
- package/lib/nextjs-compute/nextjs-functions.js +1 -1
- package/lib/nextjs-distribution.d.ts +71 -2
- package/lib/nextjs-distribution.js +156 -107
- package/lib/nextjs-distribution.test.js +361 -12
- package/lib/nextjs-post-deploy.js +1 -1
- package/lib/nextjs-static-assets.js +1 -1
- package/lib/nextjs-static-assets.test.js +19 -7
- package/lib/root-constructs/nextjs-base-construct.js +1 -1
- package/lib/root-constructs/nextjs-global-containers.d.ts +16 -1
- package/lib/root-constructs/nextjs-global-containers.js +4 -2
- package/lib/root-constructs/nextjs-global-functions.d.ts +16 -1
- package/lib/root-constructs/nextjs-global-functions.js +4 -2
- package/lib/root-constructs/nextjs-regional-containers.js +1 -1
- package/lib/root-constructs/nextjs-regional-functions.js +1 -1
- package/lib/runtime/lambda.mjs +4 -1
- package/lib/runtime/server.mjs +4 -1
- package/lib/utils/viewer-request-functions.d.ts +53 -0
- package/lib/utils/viewer-request-functions.js +172 -0
- package/lib/utils/viewer-request-functions.test.d.ts +1 -0
- package/lib/utils/viewer-request-functions.test.js +43 -0
- package/package.json +4 -1
- package/scripts/e2e-harness/app.js +7 -2
- package/scripts/render-diagrams.mjs +121 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code of cdk-nextjs's CloudFront Functions, and of `viewerRequestFunctionCode`
|
|
3
|
+
* spliced into them. Plain strings: `NextjsDistribution` creates the functions.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Where a viewer-request handler calls `viewerRequestFunctionCode`, before any
|
|
7
|
+
* logic of its own. Replaced at synth, never deployed.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VIEWER_REQUEST_HOOK = "/*@viewerRequest@*/";
|
|
10
|
+
/**
|
|
11
|
+
* CloudFront's maximum CloudFront Function size.
|
|
12
|
+
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-functions
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_FUNCTION_CODE_BYTES: number;
|
|
15
|
+
/**
|
|
16
|
+
* NextjsGlobalFunctions' viewer-request handler: redirects repeated slashes,
|
|
17
|
+
* and overwrites `x-forwarded-host` with the viewer's `Host` so Next.js
|
|
18
|
+
* `request.url` is the site's domain rather than the Function URL's.
|
|
19
|
+
* @see https://open-next.js.org/advanced/workaround#workaround-set-x-forwarded-host-header-aws-specific
|
|
20
|
+
*/
|
|
21
|
+
export declare const X_FORWARDED_HOST_HANDLER = "\n async function handler(event) {/*@viewerRequest@*/\n const request = event.request;\n const uri = request.uri;\n\n if (/\\\\|\\/\\//.test(uri)) {\n let location = uri.replace(/\\\\/g, \"/\").replace(/\\/\\/+/g, \"/\");\n const qs = [];\n Object.keys(request.querystring).forEach((name) => {\n const q = request.querystring[name];\n (q.multiValue || [q]).forEach((entry) => {\n qs.push(entry.value === \"\" ? name : name + \"=\" + entry.value);\n });\n });\n if (qs.length) {\n location += \"?\" + qs.join(\"&\");\n }\n return {\n statusCode: 308,\n statusDescription: \"Permanent Redirect\",\n headers: { location: { value: location } },\n };\n }\n\n request.headers[\"x-forwarded-host\"] = request.headers.host;\n return request;\n }\n ";
|
|
22
|
+
/**
|
|
23
|
+
* The handler of a behavior with no viewer-request logic of its own, which
|
|
24
|
+
* only runs `viewerRequestFunctionCode`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const PASS_THROUGH_HANDLER = "\n async function handler(event) {/*@viewerRequest@*/\n return event.request;\n }\n ";
|
|
27
|
+
/**
|
|
28
|
+
* The `assetPrefix` behavior's handler: an S3 origin keys on the request URI
|
|
29
|
+
* and `originPath` can only prepend, so the prefix is swapped for the
|
|
30
|
+
* `basePath` here. Both are synth-time literals.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assetPrefixRewriteHandler(basePath: string, assetPrefix: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Checks `viewerRequestFunctionCode` defines `viewerRequest` and doesn't use
|
|
35
|
+
* the name `handler`.
|
|
36
|
+
*
|
|
37
|
+
* Any use of `handler` is refused, not just a declaration: an assignment
|
|
38
|
+
* (`var handler =`, destructuring, `globalThis["handler"] =`) would replace
|
|
39
|
+
* cdk-nextjs's handler, and with it the `x-forwarded-host` overwrite, and a
|
|
40
|
+
* `let`/`const`/`class` would make the file a syntax error. Telling these
|
|
41
|
+
* apart from a local of the same name takes a parser, so a local, or the
|
|
42
|
+
* word in a comment or string, is refused too; that errs towards refusing
|
|
43
|
+
* code that would have worked.
|
|
44
|
+
*/
|
|
45
|
+
export declare function checkViewerRequestCode(userCode: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* The deployed code of a cdk-nextjs handler: with `userCode` first in the file
|
|
48
|
+
* (where CloudFront wants `import cf from "cloudfront"`) and awaited where the
|
|
49
|
+
* handler says {@link VIEWER_REQUEST_HOOK}, or with the marker dropped when
|
|
50
|
+
* there is none. Indentation is dropped too, since it counts toward the
|
|
51
|
+
* 10 KB; none of this code has a multi-line string for that to change.
|
|
52
|
+
*/
|
|
53
|
+
export declare function spliceViewerRequest(handler: string, userCode: string | undefined): string;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PASS_THROUGH_HANDLER = exports.X_FORWARDED_HOST_HANDLER = exports.MAX_FUNCTION_CODE_BYTES = exports.VIEWER_REQUEST_HOOK = void 0;
|
|
4
|
+
exports.assetPrefixRewriteHandler = assetPrefixRewriteHandler;
|
|
5
|
+
exports.checkViewerRequestCode = checkViewerRequestCode;
|
|
6
|
+
exports.spliceViewerRequest = spliceViewerRequest;
|
|
7
|
+
const constants_1 = require("../constants");
|
|
8
|
+
/**
|
|
9
|
+
* The code of cdk-nextjs's CloudFront Functions, and of `viewerRequestFunctionCode`
|
|
10
|
+
* spliced into them. Plain strings: `NextjsDistribution` creates the functions.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Where a viewer-request handler calls `viewerRequestFunctionCode`, before any
|
|
14
|
+
* logic of its own. Replaced at synth, never deployed.
|
|
15
|
+
*/
|
|
16
|
+
exports.VIEWER_REQUEST_HOOK = "/*@viewerRequest@*/";
|
|
17
|
+
/**
|
|
18
|
+
* CloudFront's maximum CloudFront Function size.
|
|
19
|
+
* @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-functions
|
|
20
|
+
*/
|
|
21
|
+
exports.MAX_FUNCTION_CODE_BYTES = 10 * 1024;
|
|
22
|
+
// Next.js answers any path containing a backslash or a repeated
|
|
23
|
+
// slash with a 308 to the collapsed path
|
|
24
|
+
// (`normalizeRepeatedSlashes`, called from `base-server.ts`), and so
|
|
25
|
+
// does this runtime. Behind a Lambda Function URL the origin never
|
|
26
|
+
// gets the chance: Origin Access Control signs the raw path while the
|
|
27
|
+
// Function URL canonicalizes it before verifying, so the origin
|
|
28
|
+
// answers 403 SignatureDoesNotMatch. Doing the redirect here is what
|
|
29
|
+
// makes `/basepath//to-sv` behave as it does under `next start`.
|
|
30
|
+
//
|
|
31
|
+
// A bare `//` reaches here too, and is answered the same way -
|
|
32
|
+
// measured, because `//` at the start of a request target is also the
|
|
33
|
+
// authority form and it was not obvious CloudFront would route it to a
|
|
34
|
+
// function at all. It does: `GET //` answers 308 to `/` with
|
|
35
|
+
// `x-cache: FunctionGeneratedResponse`, over both HTTP/1.1 and HTTP/2,
|
|
36
|
+
// which is what `test/e2e/hydration` asks for.
|
|
37
|
+
//
|
|
38
|
+
// Two things Next.js does that this does not:
|
|
39
|
+
//
|
|
40
|
+
// - the body it sends with its own redirect (the destination, as
|
|
41
|
+
// text). A generated response can carry one on
|
|
42
|
+
// cloudfront-js-2.0, but none is sent yet; no known client reads
|
|
43
|
+
// it.
|
|
44
|
+
// - the original order of the query string. The event exposes
|
|
45
|
+
// `querystring` as an object, never as the raw string, so
|
|
46
|
+
// `/x//y?a=1&b=2` redirects to `/x/y?b=2&a=1`. The pairs all
|
|
47
|
+
// survive; only their order is CloudFront's rather than the
|
|
48
|
+
// client's, and a redirect target is not order-sensitive.
|
|
49
|
+
//
|
|
50
|
+
// A repeated key's `multiValue` already holds its first value, which
|
|
51
|
+
// `value` repeats, so it is one or the other, never both. Names and
|
|
52
|
+
// values arrive as the client sent them, still percent-encoded
|
|
53
|
+
// (measured: `?a=%20b%26c&p=1+2` reads back unchanged), so they are
|
|
54
|
+
// joined as-is; encoding them again would turn `%20` into `%2520`.
|
|
55
|
+
//
|
|
56
|
+
// These notes live out here rather than in the code because
|
|
57
|
+
// CloudFront counts comments toward the function's 10 KB, which
|
|
58
|
+
// `viewerRequestFunctionCode` shares.
|
|
59
|
+
//
|
|
60
|
+
// Only the ES6+ that cloudfront-js-2.0 documents: `const`/`let`, arrow
|
|
61
|
+
// functions, `Object.keys`; no destructuring, spread or `for...of`.
|
|
62
|
+
// Strings are concatenated rather than templated, since a template
|
|
63
|
+
// literal in here would need every backtick and `${` escaped.
|
|
64
|
+
// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/functions-javascript-runtime-20.html
|
|
65
|
+
/**
|
|
66
|
+
* NextjsGlobalFunctions' viewer-request handler: redirects repeated slashes,
|
|
67
|
+
* and overwrites `x-forwarded-host` with the viewer's `Host` so Next.js
|
|
68
|
+
* `request.url` is the site's domain rather than the Function URL's.
|
|
69
|
+
* @see https://open-next.js.org/advanced/workaround#workaround-set-x-forwarded-host-header-aws-specific
|
|
70
|
+
*/
|
|
71
|
+
exports.X_FORWARDED_HOST_HANDLER = `
|
|
72
|
+
async function handler(event) {${exports.VIEWER_REQUEST_HOOK}
|
|
73
|
+
const request = event.request;
|
|
74
|
+
const uri = request.uri;
|
|
75
|
+
|
|
76
|
+
if (/\\\\|\\/\\//.test(uri)) {
|
|
77
|
+
let location = uri.replace(/\\\\/g, "/").replace(/\\/\\/+/g, "/");
|
|
78
|
+
const qs = [];
|
|
79
|
+
Object.keys(request.querystring).forEach((name) => {
|
|
80
|
+
const q = request.querystring[name];
|
|
81
|
+
(q.multiValue || [q]).forEach((entry) => {
|
|
82
|
+
qs.push(entry.value === "" ? name : name + "=" + entry.value);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
if (qs.length) {
|
|
86
|
+
location += "?" + qs.join("&");
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
statusCode: 308,
|
|
90
|
+
statusDescription: "Permanent Redirect",
|
|
91
|
+
headers: { location: { value: location } },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
request.headers["x-forwarded-host"] = request.headers.host;
|
|
96
|
+
return request;
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
/**
|
|
100
|
+
* The handler of a behavior with no viewer-request logic of its own, which
|
|
101
|
+
* only runs `viewerRequestFunctionCode`.
|
|
102
|
+
*/
|
|
103
|
+
exports.PASS_THROUGH_HANDLER = `
|
|
104
|
+
async function handler(event) {${exports.VIEWER_REQUEST_HOOK}
|
|
105
|
+
return event.request;
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
/**
|
|
109
|
+
* The `assetPrefix` behavior's handler: an S3 origin keys on the request URI
|
|
110
|
+
* and `originPath` can only prepend, so the prefix is swapped for the
|
|
111
|
+
* `basePath` here. Both are synth-time literals.
|
|
112
|
+
*/
|
|
113
|
+
function assetPrefixRewriteHandler(basePath, assetPrefix) {
|
|
114
|
+
return `
|
|
115
|
+
async function handler(event) {${exports.VIEWER_REQUEST_HOOK}
|
|
116
|
+
const request = event.request;
|
|
117
|
+
request.uri = ${JSON.stringify(basePath ? `/${basePath}` : "")} + request.uri.slice(${assetPrefix.length});
|
|
118
|
+
return request;
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Checks `viewerRequestFunctionCode` defines `viewerRequest` and doesn't use
|
|
124
|
+
* the name `handler`.
|
|
125
|
+
*
|
|
126
|
+
* Any use of `handler` is refused, not just a declaration: an assignment
|
|
127
|
+
* (`var handler =`, destructuring, `globalThis["handler"] =`) would replace
|
|
128
|
+
* cdk-nextjs's handler, and with it the `x-forwarded-host` overwrite, and a
|
|
129
|
+
* `let`/`const`/`class` would make the file a syntax error. Telling these
|
|
130
|
+
* apart from a local of the same name takes a parser, so a local, or the
|
|
131
|
+
* word in a comment or string, is refused too; that errs towards refusing
|
|
132
|
+
* code that would have worked.
|
|
133
|
+
*/
|
|
134
|
+
function checkViewerRequestCode(userCode) {
|
|
135
|
+
if (!/\bfunction\s+viewerRequest\s*\(|(?:\b(?:var|let|const)\s+|,\s*)viewerRequest\s*=(?!=)/.test(userCode)) {
|
|
136
|
+
throw new Error(`${constants_1.LOG_PREFIX} \`viewerRequestFunctionCode\` must define ` +
|
|
137
|
+
`\`viewerRequest(event)\`, as a function or a \`var\`/\`let\`/` +
|
|
138
|
+
`\`const\`, which cdk-nextjs calls from its own viewer-request ` +
|
|
139
|
+
`handler.`);
|
|
140
|
+
}
|
|
141
|
+
if (/\bhandler\b/.test(userCode)) {
|
|
142
|
+
throw new Error(`${constants_1.LOG_PREFIX} \`viewerRequestFunctionCode\` uses the name ` +
|
|
143
|
+
`\`handler\`, which is cdk-nextjs's own function in the same file. ` +
|
|
144
|
+
`Rename it (a mention in a comment or string counts too); your entry ` +
|
|
145
|
+
`point is \`viewerRequest\`, called with the same event, and it may ` +
|
|
146
|
+
`return the same request or response.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The deployed code of a cdk-nextjs handler: with `userCode` first in the file
|
|
151
|
+
* (where CloudFront wants `import cf from "cloudfront"`) and awaited where the
|
|
152
|
+
* handler says {@link VIEWER_REQUEST_HOOK}, or with the marker dropped when
|
|
153
|
+
* there is none. Indentation is dropped too, since it counts toward the
|
|
154
|
+
* 10 KB; none of this code has a multi-line string for that to change.
|
|
155
|
+
*/
|
|
156
|
+
function spliceViewerRequest(handler, userCode) {
|
|
157
|
+
const dedent = (code) => code.replace(/^[ \t]+/gm, "");
|
|
158
|
+
if (userCode === undefined) {
|
|
159
|
+
return dedent(handler.replace(exports.VIEWER_REQUEST_HOOK, ""));
|
|
160
|
+
}
|
|
161
|
+
// A response has a status; anything else returned is the request to carry
|
|
162
|
+
// on with. `await` takes a synchronous `viewerRequest` as it is.
|
|
163
|
+
return `${userCode}\n${dedent(handler.replace(exports.VIEWER_REQUEST_HOOK, `
|
|
164
|
+
const viewerResult = await viewerRequest(event);
|
|
165
|
+
if (viewerResult && viewerResult.statusCode) {
|
|
166
|
+
return viewerResult;
|
|
167
|
+
}
|
|
168
|
+
if (viewerResult) {
|
|
169
|
+
event.request = viewerResult;
|
|
170
|
+
}`))}`;
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlld2VyLXJlcXVlc3QtZnVuY3Rpb25zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3ZpZXdlci1yZXF1ZXN0LWZ1bmN0aW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFpSEEsOERBYUM7QUFjRCx3REFzQkM7QUFTRCxrREF1QkM7QUFsTUQsNENBQTBDO0FBRTFDOzs7R0FHRztBQUVIOzs7R0FHRztBQUNVLFFBQUEsbUJBQW1CLEdBQUcscUJBQXFCLENBQUM7QUFFekQ7OztHQUdHO0FBQ1UsUUFBQSx1QkFBdUIsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDO0FBRWpELGdFQUFnRTtBQUNoRSx5Q0FBeUM7QUFDekMscUVBQXFFO0FBQ3JFLG1FQUFtRTtBQUNuRSxzRUFBc0U7QUFDdEUsZ0VBQWdFO0FBQ2hFLHFFQUFxRTtBQUNyRSxpRUFBaUU7QUFDakUsRUFBRTtBQUNGLCtEQUErRDtBQUMvRCxzRUFBc0U7QUFDdEUsdUVBQXVFO0FBQ3ZFLDZEQUE2RDtBQUM3RCx1RUFBdUU7QUFDdkUsK0NBQStDO0FBQy9DLEVBQUU7QUFDRiw4Q0FBOEM7QUFDOUMsRUFBRTtBQUNGLGlFQUFpRTtBQUNqRSxpREFBaUQ7QUFDakQsbUVBQW1FO0FBQ25FLFFBQVE7QUFDUiw4REFBOEQ7QUFDOUQsNERBQTREO0FBQzVELCtEQUErRDtBQUMvRCw4REFBOEQ7QUFDOUQsNERBQTREO0FBQzVELEVBQUU7QUFDRixxRUFBcUU7QUFDckUsb0VBQW9FO0FBQ3BFLCtEQUErRDtBQUMvRCxvRUFBb0U7QUFDcEUsbUVBQW1FO0FBQ25FLEVBQUU7QUFDRiw0REFBNEQ7QUFDNUQsZ0VBQWdFO0FBQ2hFLHNDQUFzQztBQUN0QyxFQUFFO0FBQ0YsdUVBQXVFO0FBQ3ZFLG9FQUFvRTtBQUNwRSxtRUFBbUU7QUFDbkUsOERBQThEO0FBQzlELDBHQUEwRztBQUUxRzs7Ozs7R0FLRztBQUNVLFFBQUEsd0JBQXdCLEdBQUc7bUNBQ0wsMkJBQW1COzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBCbkQsQ0FBQztBQUVKOzs7R0FHRztBQUNVLFFBQUEsb0JBQW9CLEdBQUc7bUNBQ0QsMkJBQW1COzs7R0FHbkQsQ0FBQztBQUVKOzs7O0dBSUc7QUFDSCxTQUFnQix5QkFBeUIsQ0FDdkMsUUFBZ0IsRUFDaEIsV0FBbUI7SUFFbkIsT0FBTzttQ0FDMEIsMkJBQW1COztvQkFFbEMsSUFBSSxDQUFDLFNBQVMsQ0FDNUIsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQy9CLHdCQUF3QixXQUFXLENBQUMsTUFBTTs7O0dBRzVDLENBQUM7QUFDSixDQUFDO0FBRUQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxTQUFnQixzQkFBc0IsQ0FBQyxRQUFnQjtJQUNyRCxJQUNFLENBQUMsdUZBQXVGLENBQUMsSUFBSSxDQUMzRixRQUFRLENBQ1QsRUFDRCxDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FDYixHQUFHLHNCQUFVLDZDQUE2QztZQUN4RCwrREFBK0Q7WUFDL0QsZ0VBQWdFO1lBQ2hFLFVBQVUsQ0FDYixDQUFDO0lBQ0osQ0FBQztJQUNELElBQUksYUFBYSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1FBQ2pDLE1BQU0sSUFBSSxLQUFLLENBQ2IsR0FBRyxzQkFBVSwrQ0FBK0M7WUFDMUQsb0VBQW9FO1lBQ3BFLHNFQUFzRTtZQUN0RSxxRUFBcUU7WUFDckUsc0NBQXNDLENBQ3pDLENBQUM7SUFDSixDQUFDO0FBQ0gsQ0FBQztBQUVEOzs7Ozs7R0FNRztBQUNILFNBQWdCLG1CQUFtQixDQUNqQyxPQUFlLEVBQ2YsUUFBNEI7SUFFNUIsTUFBTSxNQUFNLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0lBQy9ELElBQUksUUFBUSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQzNCLE9BQU8sTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsMkJBQW1CLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMxRCxDQUFDO0lBQ0QsMEVBQTBFO0lBQzFFLGlFQUFpRTtJQUNqRSxPQUFPLEdBQUcsUUFBUSxLQUFLLE1BQU0sQ0FDM0IsT0FBTyxDQUFDLE9BQU8sQ0FDYiwyQkFBbUIsRUFDbkI7Ozs7Ozs7UUFPRSxDQUNILENBQ0YsRUFBRSxDQUFDO0FBQ04sQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IExPR19QUkVGSVggfSBmcm9tIFwiLi4vY29uc3RhbnRzXCI7XG5cbi8qKlxuICogVGhlIGNvZGUgb2YgY2RrLW5leHRqcydzIENsb3VkRnJvbnQgRnVuY3Rpb25zLCBhbmQgb2YgYHZpZXdlclJlcXVlc3RGdW5jdGlvbkNvZGVgXG4gKiBzcGxpY2VkIGludG8gdGhlbS4gUGxhaW4gc3RyaW5nczogYE5leHRqc0Rpc3RyaWJ1dGlvbmAgY3JlYXRlcyB0aGUgZnVuY3Rpb25zLlxuICovXG5cbi8qKlxuICogV2hlcmUgYSB2aWV3ZXItcmVxdWVzdCBoYW5kbGVyIGNhbGxzIGB2aWV3ZXJSZXF1ZXN0RnVuY3Rpb25Db2RlYCwgYmVmb3JlIGFueVxuICogbG9naWMgb2YgaXRzIG93bi4gUmVwbGFjZWQgYXQgc3ludGgsIG5ldmVyIGRlcGxveWVkLlxuICovXG5leHBvcnQgY29uc3QgVklFV0VSX1JFUVVFU1RfSE9PSyA9IFwiLypAdmlld2VyUmVxdWVzdEAqL1wiO1xuXG4vKipcbiAqIENsb3VkRnJvbnQncyBtYXhpbXVtIENsb3VkRnJvbnQgRnVuY3Rpb24gc2l6ZS5cbiAqIEBzZWUgaHR0cHM6Ly9kb2NzLmF3cy5hbWF6b24uY29tL0FtYXpvbkNsb3VkRnJvbnQvbGF0ZXN0L0RldmVsb3Blckd1aWRlL2Nsb3VkZnJvbnQtbGltaXRzLmh0bWwjbGltaXRzLWZ1bmN0aW9uc1xuICovXG5leHBvcnQgY29uc3QgTUFYX0ZVTkNUSU9OX0NPREVfQllURVMgPSAxMCAqIDEwMjQ7XG5cbi8vIE5leHQuanMgYW5zd2VycyBhbnkgcGF0aCBjb250YWluaW5nIGEgYmFja3NsYXNoIG9yIGEgcmVwZWF0ZWRcbi8vIHNsYXNoIHdpdGggYSAzMDggdG8gdGhlIGNvbGxhcHNlZCBwYXRoXG4vLyAoYG5vcm1hbGl6ZVJlcGVhdGVkU2xhc2hlc2AsIGNhbGxlZCBmcm9tIGBiYXNlLXNlcnZlci50c2ApLCBhbmQgc29cbi8vIGRvZXMgdGhpcyBydW50aW1lLiBCZWhpbmQgYSBMYW1iZGEgRnVuY3Rpb24gVVJMIHRoZSBvcmlnaW4gbmV2ZXJcbi8vIGdldHMgdGhlIGNoYW5jZTogT3JpZ2luIEFjY2VzcyBDb250cm9sIHNpZ25zIHRoZSByYXcgcGF0aCB3aGlsZSB0aGVcbi8vIEZ1bmN0aW9uIFVSTCBjYW5vbmljYWxpemVzIGl0IGJlZm9yZSB2ZXJpZnlpbmcsIHNvIHRoZSBvcmlnaW5cbi8vIGFuc3dlcnMgNDAzIFNpZ25hdHVyZURvZXNOb3RNYXRjaC4gRG9pbmcgdGhlIHJlZGlyZWN0IGhlcmUgaXMgd2hhdFxuLy8gbWFrZXMgYC9iYXNlcGF0aC8vdG8tc3ZgIGJlaGF2ZSBhcyBpdCBkb2VzIHVuZGVyIGBuZXh0IHN0YXJ0YC5cbi8vXG4vLyBBIGJhcmUgYC8vYCByZWFjaGVzIGhlcmUgdG9vLCBhbmQgaXMgYW5zd2VyZWQgdGhlIHNhbWUgd2F5IC1cbi8vIG1lYXN1cmVkLCBiZWNhdXNlIGAvL2AgYXQgdGhlIHN0YXJ0IG9mIGEgcmVxdWVzdCB0YXJnZXQgaXMgYWxzbyB0aGVcbi8vIGF1dGhvcml0eSBmb3JtIGFuZCBpdCB3YXMgbm90IG9idmlvdXMgQ2xvdWRGcm9udCB3b3VsZCByb3V0ZSBpdCB0byBhXG4vLyBmdW5jdGlvbiBhdCBhbGwuIEl0IGRvZXM6IGBHRVQgLy9gIGFuc3dlcnMgMzA4IHRvIGAvYCB3aXRoXG4vLyBgeC1jYWNoZTogRnVuY3Rpb25HZW5lcmF0ZWRSZXNwb25zZWAsIG92ZXIgYm90aCBIVFRQLzEuMSBhbmQgSFRUUC8yLFxuLy8gd2hpY2ggaXMgd2hhdCBgdGVzdC9lMmUvaHlkcmF0aW9uYCBhc2tzIGZvci5cbi8vXG4vLyBUd28gdGhpbmdzIE5leHQuanMgZG9lcyB0aGF0IHRoaXMgZG9lcyBub3Q6XG4vL1xuLy8gLSB0aGUgYm9keSBpdCBzZW5kcyB3aXRoIGl0cyBvd24gcmVkaXJlY3QgKHRoZSBkZXN0aW5hdGlvbiwgYXNcbi8vICAgdGV4dCkuIEEgZ2VuZXJhdGVkIHJlc3BvbnNlIGNhbiBjYXJyeSBvbmUgb25cbi8vICAgY2xvdWRmcm9udC1qcy0yLjAsIGJ1dCBub25lIGlzIHNlbnQgeWV0OyBubyBrbm93biBjbGllbnQgcmVhZHNcbi8vICAgaXQuXG4vLyAtIHRoZSBvcmlnaW5hbCBvcmRlciBvZiB0aGUgcXVlcnkgc3RyaW5nLiBUaGUgZXZlbnQgZXhwb3Nlc1xuLy8gICBgcXVlcnlzdHJpbmdgIGFzIGFuIG9iamVjdCwgbmV2ZXIgYXMgdGhlIHJhdyBzdHJpbmcsIHNvXG4vLyAgIGAveC8veT9hPTEmYj0yYCByZWRpcmVjdHMgdG8gYC94L3k/Yj0yJmE9MWAuIFRoZSBwYWlycyBhbGxcbi8vICAgc3Vydml2ZTsgb25seSB0aGVpciBvcmRlciBpcyBDbG91ZEZyb250J3MgcmF0aGVyIHRoYW4gdGhlXG4vLyAgIGNsaWVudCdzLCBhbmQgYSByZWRpcmVjdCB0YXJnZXQgaXMgbm90IG9yZGVyLXNlbnNpdGl2ZS5cbi8vXG4vLyBBIHJlcGVhdGVkIGtleSdzIGBtdWx0aVZhbHVlYCBhbHJlYWR5IGhvbGRzIGl0cyBmaXJzdCB2YWx1ZSwgd2hpY2hcbi8vIGB2YWx1ZWAgcmVwZWF0cywgc28gaXQgaXMgb25lIG9yIHRoZSBvdGhlciwgbmV2ZXIgYm90aC4gTmFtZXMgYW5kXG4vLyB2YWx1ZXMgYXJyaXZlIGFzIHRoZSBjbGllbnQgc2VudCB0aGVtLCBzdGlsbCBwZXJjZW50LWVuY29kZWRcbi8vIChtZWFzdXJlZDogYD9hPSUyMGIlMjZjJnA9MSsyYCByZWFkcyBiYWNrIHVuY2hhbmdlZCksIHNvIHRoZXkgYXJlXG4vLyBqb2luZWQgYXMtaXM7IGVuY29kaW5nIHRoZW0gYWdhaW4gd291bGQgdHVybiBgJTIwYCBpbnRvIGAlMjUyMGAuXG4vL1xuLy8gVGhlc2Ugbm90ZXMgbGl2ZSBvdXQgaGVyZSByYXRoZXIgdGhhbiBpbiB0aGUgY29kZSBiZWNhdXNlXG4vLyBDbG91ZEZyb250IGNvdW50cyBjb21tZW50cyB0b3dhcmQgdGhlIGZ1bmN0aW9uJ3MgMTAgS0IsIHdoaWNoXG4vLyBgdmlld2VyUmVxdWVzdEZ1bmN0aW9uQ29kZWAgc2hhcmVzLlxuLy9cbi8vIE9ubHkgdGhlIEVTNisgdGhhdCBjbG91ZGZyb250LWpzLTIuMCBkb2N1bWVudHM6IGBjb25zdGAvYGxldGAsIGFycm93XG4vLyBmdW5jdGlvbnMsIGBPYmplY3Qua2V5c2A7IG5vIGRlc3RydWN0dXJpbmcsIHNwcmVhZCBvciBgZm9yLi4ub2ZgLlxuLy8gU3RyaW5ncyBhcmUgY29uY2F0ZW5hdGVkIHJhdGhlciB0aGFuIHRlbXBsYXRlZCwgc2luY2UgYSB0ZW1wbGF0ZVxuLy8gbGl0ZXJhbCBpbiBoZXJlIHdvdWxkIG5lZWQgZXZlcnkgYmFja3RpY2sgYW5kIGAke2AgZXNjYXBlZC5cbi8vIGh0dHBzOi8vZG9jcy5hd3MuYW1hem9uLmNvbS9BbWF6b25DbG91ZEZyb250L2xhdGVzdC9EZXZlbG9wZXJHdWlkZS9mdW5jdGlvbnMtamF2YXNjcmlwdC1ydW50aW1lLTIwLmh0bWxcblxuLyoqXG4gKiBOZXh0anNHbG9iYWxGdW5jdGlvbnMnIHZpZXdlci1yZXF1ZXN0IGhhbmRsZXI6IHJlZGlyZWN0cyByZXBlYXRlZCBzbGFzaGVzLFxuICogYW5kIG92ZXJ3cml0ZXMgYHgtZm9yd2FyZGVkLWhvc3RgIHdpdGggdGhlIHZpZXdlcidzIGBIb3N0YCBzbyBOZXh0LmpzXG4gKiBgcmVxdWVzdC51cmxgIGlzIHRoZSBzaXRlJ3MgZG9tYWluIHJhdGhlciB0aGFuIHRoZSBGdW5jdGlvbiBVUkwncy5cbiAqIEBzZWUgaHR0cHM6Ly9vcGVuLW5leHQuanMub3JnL2FkdmFuY2VkL3dvcmthcm91bmQjd29ya2Fyb3VuZC1zZXQteC1mb3J3YXJkZWQtaG9zdC1oZWFkZXItYXdzLXNwZWNpZmljXG4gKi9cbmV4cG9ydCBjb25zdCBYX0ZPUldBUkRFRF9IT1NUX0hBTkRMRVIgPSBgXG4gIGFzeW5jIGZ1bmN0aW9uIGhhbmRsZXIoZXZlbnQpIHske1ZJRVdFUl9SRVFVRVNUX0hPT0t9XG4gICAgY29uc3QgcmVxdWVzdCA9IGV2ZW50LnJlcXVlc3Q7XG4gICAgY29uc3QgdXJpID0gcmVxdWVzdC51cmk7XG5cbiAgICBpZiAoL1xcXFxcXFxcfFxcXFwvXFxcXC8vLnRlc3QodXJpKSkge1xuICAgICAgbGV0IGxvY2F0aW9uID0gdXJpLnJlcGxhY2UoL1xcXFxcXFxcL2csIFwiL1wiKS5yZXBsYWNlKC9cXFxcL1xcXFwvKy9nLCBcIi9cIik7XG4gICAgICBjb25zdCBxcyA9IFtdO1xuICAgICAgT2JqZWN0LmtleXMocmVxdWVzdC5xdWVyeXN0cmluZykuZm9yRWFjaCgobmFtZSkgPT4ge1xuICAgICAgICBjb25zdCBxID0gcmVxdWVzdC5xdWVyeXN0cmluZ1tuYW1lXTtcbiAgICAgICAgKHEubXVsdGlWYWx1ZSB8fCBbcV0pLmZvckVhY2goKGVudHJ5KSA9PiB7XG4gICAgICAgICAgcXMucHVzaChlbnRyeS52YWx1ZSA9PT0gXCJcIiA/IG5hbWUgOiBuYW1lICsgXCI9XCIgKyBlbnRyeS52YWx1ZSk7XG4gICAgICAgIH0pO1xuICAgICAgfSk7XG4gICAgICBpZiAocXMubGVuZ3RoKSB7XG4gICAgICAgIGxvY2F0aW9uICs9IFwiP1wiICsgcXMuam9pbihcIiZcIik7XG4gICAgICB9XG4gICAgICByZXR1cm4ge1xuICAgICAgICBzdGF0dXNDb2RlOiAzMDgsXG4gICAgICAgIHN0YXR1c0Rlc2NyaXB0aW9uOiBcIlBlcm1hbmVudCBSZWRpcmVjdFwiLFxuICAgICAgICBoZWFkZXJzOiB7IGxvY2F0aW9uOiB7IHZhbHVlOiBsb2NhdGlvbiB9IH0sXG4gICAgICB9O1xuICAgIH1cblxuICAgIHJlcXVlc3QuaGVhZGVyc1tcIngtZm9yd2FyZGVkLWhvc3RcIl0gPSByZXF1ZXN0LmhlYWRlcnMuaG9zdDtcbiAgICByZXR1cm4gcmVxdWVzdDtcbiAgfVxuICBgO1xuXG4vKipcbiAqIFRoZSBoYW5kbGVyIG9mIGEgYmVoYXZpb3Igd2l0aCBubyB2aWV3ZXItcmVxdWVzdCBsb2dpYyBvZiBpdHMgb3duLCB3aGljaFxuICogb25seSBydW5zIGB2aWV3ZXJSZXF1ZXN0RnVuY3Rpb25Db2RlYC5cbiAqL1xuZXhwb3J0IGNvbnN0IFBBU1NfVEhST1VHSF9IQU5ETEVSID0gYFxuICBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50KSB7JHtWSUVXRVJfUkVRVUVTVF9IT09LfVxuICAgIHJldHVybiBldmVudC5yZXF1ZXN0O1xuICB9XG4gIGA7XG5cbi8qKlxuICogVGhlIGBhc3NldFByZWZpeGAgYmVoYXZpb3IncyBoYW5kbGVyOiBhbiBTMyBvcmlnaW4ga2V5cyBvbiB0aGUgcmVxdWVzdCBVUklcbiAqIGFuZCBgb3JpZ2luUGF0aGAgY2FuIG9ubHkgcHJlcGVuZCwgc28gdGhlIHByZWZpeCBpcyBzd2FwcGVkIGZvciB0aGVcbiAqIGBiYXNlUGF0aGAgaGVyZS4gQm90aCBhcmUgc3ludGgtdGltZSBsaXRlcmFscy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFzc2V0UHJlZml4UmV3cml0ZUhhbmRsZXIoXG4gIGJhc2VQYXRoOiBzdHJpbmcsXG4gIGFzc2V0UHJlZml4OiBzdHJpbmcsXG4pOiBzdHJpbmcge1xuICByZXR1cm4gYFxuICBhc3luYyBmdW5jdGlvbiBoYW5kbGVyKGV2ZW50KSB7JHtWSUVXRVJfUkVRVUVTVF9IT09LfVxuICAgIGNvbnN0IHJlcXVlc3QgPSBldmVudC5yZXF1ZXN0O1xuICAgIHJlcXVlc3QudXJpID0gJHtKU09OLnN0cmluZ2lmeShcbiAgICAgIGJhc2VQYXRoID8gYC8ke2Jhc2VQYXRofWAgOiBcIlwiLFxuICAgICl9ICsgcmVxdWVzdC51cmkuc2xpY2UoJHthc3NldFByZWZpeC5sZW5ndGh9KTtcbiAgICByZXR1cm4gcmVxdWVzdDtcbiAgfVxuICBgO1xufVxuXG4vKipcbiAqIENoZWNrcyBgdmlld2VyUmVxdWVzdEZ1bmN0aW9uQ29kZWAgZGVmaW5lcyBgdmlld2VyUmVxdWVzdGAgYW5kIGRvZXNuJ3QgdXNlXG4gKiB0aGUgbmFtZSBgaGFuZGxlcmAuXG4gKlxuICogQW55IHVzZSBvZiBgaGFuZGxlcmAgaXMgcmVmdXNlZCwgbm90IGp1c3QgYSBkZWNsYXJhdGlvbjogYW4gYXNzaWdubWVudFxuICogKGB2YXIgaGFuZGxlciA9YCwgZGVzdHJ1Y3R1cmluZywgYGdsb2JhbFRoaXNbXCJoYW5kbGVyXCJdID1gKSB3b3VsZCByZXBsYWNlXG4gKiBjZGstbmV4dGpzJ3MgaGFuZGxlciwgYW5kIHdpdGggaXQgdGhlIGB4LWZvcndhcmRlZC1ob3N0YCBvdmVyd3JpdGUsIGFuZCBhXG4gKiBgbGV0YC9gY29uc3RgL2BjbGFzc2Agd291bGQgbWFrZSB0aGUgZmlsZSBhIHN5bnRheCBlcnJvci4gVGVsbGluZyB0aGVzZVxuICogYXBhcnQgZnJvbSBhIGxvY2FsIG9mIHRoZSBzYW1lIG5hbWUgdGFrZXMgYSBwYXJzZXIsIHNvIGEgbG9jYWwsIG9yIHRoZVxuICogd29yZCBpbiBhIGNvbW1lbnQgb3Igc3RyaW5nLCBpcyByZWZ1c2VkIHRvbzsgdGhhdCBlcnJzIHRvd2FyZHMgcmVmdXNpbmdcbiAqIGNvZGUgdGhhdCB3b3VsZCBoYXZlIHdvcmtlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNoZWNrVmlld2VyUmVxdWVzdENvZGUodXNlckNvZGU6IHN0cmluZyk6IHZvaWQge1xuICBpZiAoXG4gICAgIS9cXGJmdW5jdGlvblxccyt2aWV3ZXJSZXF1ZXN0XFxzKlxcKHwoPzpcXGIoPzp2YXJ8bGV0fGNvbnN0KVxccyt8LFxccyopdmlld2VyUmVxdWVzdFxccyo9KD8hPSkvLnRlc3QoXG4gICAgICB1c2VyQ29kZSxcbiAgICApXG4gICkge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIGAke0xPR19QUkVGSVh9IFxcYHZpZXdlclJlcXVlc3RGdW5jdGlvbkNvZGVcXGAgbXVzdCBkZWZpbmUgYCArXG4gICAgICAgIGBcXGB2aWV3ZXJSZXF1ZXN0KGV2ZW50KVxcYCwgYXMgYSBmdW5jdGlvbiBvciBhIFxcYHZhclxcYC9cXGBsZXRcXGAvYCArXG4gICAgICAgIGBcXGBjb25zdFxcYCwgd2hpY2ggY2RrLW5leHRqcyBjYWxscyBmcm9tIGl0cyBvd24gdmlld2VyLXJlcXVlc3QgYCArXG4gICAgICAgIGBoYW5kbGVyLmAsXG4gICAgKTtcbiAgfVxuICBpZiAoL1xcYmhhbmRsZXJcXGIvLnRlc3QodXNlckNvZGUpKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgYCR7TE9HX1BSRUZJWH0gXFxgdmlld2VyUmVxdWVzdEZ1bmN0aW9uQ29kZVxcYCB1c2VzIHRoZSBuYW1lIGAgK1xuICAgICAgICBgXFxgaGFuZGxlclxcYCwgd2hpY2ggaXMgY2RrLW5leHRqcydzIG93biBmdW5jdGlvbiBpbiB0aGUgc2FtZSBmaWxlLiBgICtcbiAgICAgICAgYFJlbmFtZSBpdCAoYSBtZW50aW9uIGluIGEgY29tbWVudCBvciBzdHJpbmcgY291bnRzIHRvbyk7IHlvdXIgZW50cnkgYCArXG4gICAgICAgIGBwb2ludCBpcyBcXGB2aWV3ZXJSZXF1ZXN0XFxgLCBjYWxsZWQgd2l0aCB0aGUgc2FtZSBldmVudCwgYW5kIGl0IG1heSBgICtcbiAgICAgICAgYHJldHVybiB0aGUgc2FtZSByZXF1ZXN0IG9yIHJlc3BvbnNlLmAsXG4gICAgKTtcbiAgfVxufVxuXG4vKipcbiAqIFRoZSBkZXBsb3llZCBjb2RlIG9mIGEgY2RrLW5leHRqcyBoYW5kbGVyOiB3aXRoIGB1c2VyQ29kZWAgZmlyc3QgaW4gdGhlIGZpbGVcbiAqICh3aGVyZSBDbG91ZEZyb250IHdhbnRzIGBpbXBvcnQgY2YgZnJvbSBcImNsb3VkZnJvbnRcImApIGFuZCBhd2FpdGVkIHdoZXJlIHRoZVxuICogaGFuZGxlciBzYXlzIHtAbGluayBWSUVXRVJfUkVRVUVTVF9IT09LfSwgb3Igd2l0aCB0aGUgbWFya2VyIGRyb3BwZWQgd2hlblxuICogdGhlcmUgaXMgbm9uZS4gSW5kZW50YXRpb24gaXMgZHJvcHBlZCB0b28sIHNpbmNlIGl0IGNvdW50cyB0b3dhcmQgdGhlXG4gKiAxMCBLQjsgbm9uZSBvZiB0aGlzIGNvZGUgaGFzIGEgbXVsdGktbGluZSBzdHJpbmcgZm9yIHRoYXQgdG8gY2hhbmdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc3BsaWNlVmlld2VyUmVxdWVzdChcbiAgaGFuZGxlcjogc3RyaW5nLFxuICB1c2VyQ29kZTogc3RyaW5nIHwgdW5kZWZpbmVkLFxuKTogc3RyaW5nIHtcbiAgY29uc3QgZGVkZW50ID0gKGNvZGU6IHN0cmluZykgPT4gY29kZS5yZXBsYWNlKC9eWyBcXHRdKy9nbSwgXCJcIik7XG4gIGlmICh1c2VyQ29kZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuIGRlZGVudChoYW5kbGVyLnJlcGxhY2UoVklFV0VSX1JFUVVFU1RfSE9PSywgXCJcIikpO1xuICB9XG4gIC8vIEEgcmVzcG9uc2UgaGFzIGEgc3RhdHVzOyBhbnl0aGluZyBlbHNlIHJldHVybmVkIGlzIHRoZSByZXF1ZXN0IHRvIGNhcnJ5XG4gIC8vIG9uIHdpdGguIGBhd2FpdGAgdGFrZXMgYSBzeW5jaHJvbm91cyBgdmlld2VyUmVxdWVzdGAgYXMgaXQgaXMuXG4gIHJldHVybiBgJHt1c2VyQ29kZX1cXG4ke2RlZGVudChcbiAgICBoYW5kbGVyLnJlcGxhY2UoXG4gICAgICBWSUVXRVJfUkVRVUVTVF9IT09LLFxuICAgICAgYFxuICAgICAgY29uc3Qgdmlld2VyUmVzdWx0ID0gYXdhaXQgdmlld2VyUmVxdWVzdChldmVudCk7XG4gICAgICBpZiAodmlld2VyUmVzdWx0ICYmIHZpZXdlclJlc3VsdC5zdGF0dXNDb2RlKSB7XG4gICAgICAgIHJldHVybiB2aWV3ZXJSZXN1bHQ7XG4gICAgICB9XG4gICAgICBpZiAodmlld2VyUmVzdWx0KSB7XG4gICAgICAgIGV2ZW50LnJlcXVlc3QgPSB2aWV3ZXJSZXN1bHQ7XG4gICAgICB9YCxcbiAgICApLFxuICApfWA7XG59XG4iXX0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const viewer_request_functions_1 = require("./viewer-request-functions");
|
|
4
|
+
describe("spliceViewerRequest", () => {
|
|
5
|
+
it.each([
|
|
6
|
+
["x-forwarded-host", viewer_request_functions_1.X_FORWARDED_HOST_HANDLER],
|
|
7
|
+
["pass-through", viewer_request_functions_1.PASS_THROUGH_HANDLER],
|
|
8
|
+
["assetPrefix rewrite", (0, viewer_request_functions_1.assetPrefixRewriteHandler)("base", "/cdn")],
|
|
9
|
+
])("drops the marker and indentation from the %s handler", (_, handler) => {
|
|
10
|
+
const code = (0, viewer_request_functions_1.spliceViewerRequest)(handler, undefined);
|
|
11
|
+
expect(code).not.toContain(viewer_request_functions_1.VIEWER_REQUEST_HOOK);
|
|
12
|
+
expect(code).not.toMatch(/^[ \t]/m);
|
|
13
|
+
expect(code).toMatch(/^async function handler\(event\) \{$/m);
|
|
14
|
+
});
|
|
15
|
+
it("puts the user's code first and awaits viewerRequest at the marker", () => {
|
|
16
|
+
const code = (0, viewer_request_functions_1.spliceViewerRequest)(viewer_request_functions_1.PASS_THROUGH_HANDLER, 'import cf from "cloudfront";\nfunction viewerRequest(event) {}');
|
|
17
|
+
expect(code.startsWith('import cf from "cloudfront";')).toBe(true);
|
|
18
|
+
expect(code).toMatch(/async function handler\(event\) \{\nconst viewerResult = await viewerRequest\(event\);/);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe("assetPrefixRewriteHandler", () => {
|
|
22
|
+
it("swaps the prefix for the basePath", () => {
|
|
23
|
+
expect((0, viewer_request_functions_1.assetPrefixRewriteHandler)("base", "/cdn")).toContain('request.uri = "/base" + request.uri.slice(4);');
|
|
24
|
+
expect((0, viewer_request_functions_1.assetPrefixRewriteHandler)("", "/cdn")).toContain('request.uri = "" + request.uri.slice(4);');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("checkViewerRequestCode", () => {
|
|
28
|
+
it.each([
|
|
29
|
+
"function viewerRequest(event) {}",
|
|
30
|
+
"async function viewerRequest(event) {}",
|
|
31
|
+
"const viewerRequest = (event) => {};",
|
|
32
|
+
"var x = 1, viewerRequest = (event) => {};",
|
|
33
|
+
])("accepts %s", (code) => {
|
|
34
|
+
expect(() => (0, viewer_request_functions_1.checkViewerRequestCode)(code)).not.toThrow();
|
|
35
|
+
});
|
|
36
|
+
it("refuses code that never defines viewerRequest", () => {
|
|
37
|
+
expect(() => (0, viewer_request_functions_1.checkViewerRequestCode)("const ok = viewerRequest === 1;")).toThrow(/must define `viewerRequest\(event\)`/);
|
|
38
|
+
});
|
|
39
|
+
it("refuses code that uses the name handler", () => {
|
|
40
|
+
expect(() => (0, viewer_request_functions_1.checkViewerRequestCode)("function viewerRequest(event) {} // handler")).toThrow(/uses the name `handler`/);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlld2VyLXJlcXVlc3QtZnVuY3Rpb25zLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvdmlld2VyLXJlcXVlc3QtZnVuY3Rpb25zLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSx5RUFPb0M7QUFFcEMsUUFBUSxDQUFDLHFCQUFxQixFQUFFLEdBQUcsRUFBRTtJQUNuQyxFQUFFLENBQUMsSUFBSSxDQUFDO1FBQ04sQ0FBQyxrQkFBa0IsRUFBRSxtREFBd0IsQ0FBQztRQUM5QyxDQUFDLGNBQWMsRUFBRSwrQ0FBb0IsQ0FBQztRQUN0QyxDQUFDLHFCQUFxQixFQUFFLElBQUEsb0RBQXlCLEVBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0tBQ25FLENBQUMsQ0FBQyxzREFBc0QsRUFBRSxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUN4RSxNQUFNLElBQUksR0FBRyxJQUFBLDhDQUFtQixFQUFDLE9BQU8sRUFBRSxTQUFTLENBQUMsQ0FBQztRQUNyRCxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyw4Q0FBbUIsQ0FBQyxDQUFDO1FBQ2hELE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3BDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsdUNBQXVDLENBQUMsQ0FBQztJQUNoRSxDQUFDLENBQUMsQ0FBQztJQUVILEVBQUUsQ0FBQyxtRUFBbUUsRUFBRSxHQUFHLEVBQUU7UUFDM0UsTUFBTSxJQUFJLEdBQUcsSUFBQSw4Q0FBbUIsRUFDOUIsK0NBQW9CLEVBQ3BCLGdFQUFnRSxDQUNqRSxDQUFDO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsOEJBQThCLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNuRSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxDQUNsQix3RkFBd0YsQ0FDekYsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQyxDQUFDLENBQUM7QUFFSCxRQUFRLENBQUMsMkJBQTJCLEVBQUUsR0FBRyxFQUFFO0lBQ3pDLEVBQUUsQ0FBQyxtQ0FBbUMsRUFBRSxHQUFHLEVBQUU7UUFDM0MsTUFBTSxDQUFDLElBQUEsb0RBQXlCLEVBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUN6RCwrQ0FBK0MsQ0FDaEQsQ0FBQztRQUNGLE1BQU0sQ0FBQyxJQUFBLG9EQUF5QixFQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FDckQsMENBQTBDLENBQzNDLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDO0FBRUgsUUFBUSxDQUFDLHdCQUF3QixFQUFFLEdBQUcsRUFBRTtJQUN0QyxFQUFFLENBQUMsSUFBSSxDQUFDO1FBQ04sa0NBQWtDO1FBQ2xDLHdDQUF3QztRQUN4QyxzQ0FBc0M7UUFDdEMsMkNBQTJDO0tBQzVDLENBQUMsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUN4QixNQUFNLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBQSxpREFBc0IsRUFBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzRCxDQUFDLENBQUMsQ0FBQztJQUVILEVBQUUsQ0FBQywrQ0FBK0MsRUFBRSxHQUFHLEVBQUU7UUFDdkQsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUNWLElBQUEsaURBQXNCLEVBQUMsaUNBQWlDLENBQUMsQ0FDMUQsQ0FBQyxPQUFPLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUNwRCxDQUFDLENBQUMsQ0FBQztJQUVILEVBQUUsQ0FBQyx5Q0FBeUMsRUFBRSxHQUFHLEVBQUU7UUFDakQsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUNWLElBQUEsaURBQXNCLEVBQUMsNkNBQTZDLENBQUMsQ0FDdEUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLENBQUMsQ0FBQztJQUN2QyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgYXNzZXRQcmVmaXhSZXdyaXRlSGFuZGxlcixcbiAgY2hlY2tWaWV3ZXJSZXF1ZXN0Q29kZSxcbiAgUEFTU19USFJPVUdIX0hBTkRMRVIsXG4gIHNwbGljZVZpZXdlclJlcXVlc3QsXG4gIFZJRVdFUl9SRVFVRVNUX0hPT0ssXG4gIFhfRk9SV0FSREVEX0hPU1RfSEFORExFUixcbn0gZnJvbSBcIi4vdmlld2VyLXJlcXVlc3QtZnVuY3Rpb25zXCI7XG5cbmRlc2NyaWJlKFwic3BsaWNlVmlld2VyUmVxdWVzdFwiLCAoKSA9PiB7XG4gIGl0LmVhY2goW1xuICAgIFtcIngtZm9yd2FyZGVkLWhvc3RcIiwgWF9GT1JXQVJERURfSE9TVF9IQU5ETEVSXSxcbiAgICBbXCJwYXNzLXRocm91Z2hcIiwgUEFTU19USFJPVUdIX0hBTkRMRVJdLFxuICAgIFtcImFzc2V0UHJlZml4IHJld3JpdGVcIiwgYXNzZXRQcmVmaXhSZXdyaXRlSGFuZGxlcihcImJhc2VcIiwgXCIvY2RuXCIpXSxcbiAgXSkoXCJkcm9wcyB0aGUgbWFya2VyIGFuZCBpbmRlbnRhdGlvbiBmcm9tIHRoZSAlcyBoYW5kbGVyXCIsIChfLCBoYW5kbGVyKSA9PiB7XG4gICAgY29uc3QgY29kZSA9IHNwbGljZVZpZXdlclJlcXVlc3QoaGFuZGxlciwgdW5kZWZpbmVkKTtcbiAgICBleHBlY3QoY29kZSkubm90LnRvQ29udGFpbihWSUVXRVJfUkVRVUVTVF9IT09LKTtcbiAgICBleHBlY3QoY29kZSkubm90LnRvTWF0Y2goL15bIFxcdF0vbSk7XG4gICAgZXhwZWN0KGNvZGUpLnRvTWF0Y2goL15hc3luYyBmdW5jdGlvbiBoYW5kbGVyXFwoZXZlbnRcXCkgXFx7JC9tKTtcbiAgfSk7XG5cbiAgaXQoXCJwdXRzIHRoZSB1c2VyJ3MgY29kZSBmaXJzdCBhbmQgYXdhaXRzIHZpZXdlclJlcXVlc3QgYXQgdGhlIG1hcmtlclwiLCAoKSA9PiB7XG4gICAgY29uc3QgY29kZSA9IHNwbGljZVZpZXdlclJlcXVlc3QoXG4gICAgICBQQVNTX1RIUk9VR0hfSEFORExFUixcbiAgICAgICdpbXBvcnQgY2YgZnJvbSBcImNsb3VkZnJvbnRcIjtcXG5mdW5jdGlvbiB2aWV3ZXJSZXF1ZXN0KGV2ZW50KSB7fScsXG4gICAgKTtcbiAgICBleHBlY3QoY29kZS5zdGFydHNXaXRoKCdpbXBvcnQgY2YgZnJvbSBcImNsb3VkZnJvbnRcIjsnKSkudG9CZSh0cnVlKTtcbiAgICBleHBlY3QoY29kZSkudG9NYXRjaChcbiAgICAgIC9hc3luYyBmdW5jdGlvbiBoYW5kbGVyXFwoZXZlbnRcXCkgXFx7XFxuY29uc3Qgdmlld2VyUmVzdWx0ID0gYXdhaXQgdmlld2VyUmVxdWVzdFxcKGV2ZW50XFwpOy8sXG4gICAgKTtcbiAgfSk7XG59KTtcblxuZGVzY3JpYmUoXCJhc3NldFByZWZpeFJld3JpdGVIYW5kbGVyXCIsICgpID0+IHtcbiAgaXQoXCJzd2FwcyB0aGUgcHJlZml4IGZvciB0aGUgYmFzZVBhdGhcIiwgKCkgPT4ge1xuICAgIGV4cGVjdChhc3NldFByZWZpeFJld3JpdGVIYW5kbGVyKFwiYmFzZVwiLCBcIi9jZG5cIikpLnRvQ29udGFpbihcbiAgICAgICdyZXF1ZXN0LnVyaSA9IFwiL2Jhc2VcIiArIHJlcXVlc3QudXJpLnNsaWNlKDQpOycsXG4gICAgKTtcbiAgICBleHBlY3QoYXNzZXRQcmVmaXhSZXdyaXRlSGFuZGxlcihcIlwiLCBcIi9jZG5cIikpLnRvQ29udGFpbihcbiAgICAgICdyZXF1ZXN0LnVyaSA9IFwiXCIgKyByZXF1ZXN0LnVyaS5zbGljZSg0KTsnLFxuICAgICk7XG4gIH0pO1xufSk7XG5cbmRlc2NyaWJlKFwiY2hlY2tWaWV3ZXJSZXF1ZXN0Q29kZVwiLCAoKSA9PiB7XG4gIGl0LmVhY2goW1xuICAgIFwiZnVuY3Rpb24gdmlld2VyUmVxdWVzdChldmVudCkge31cIixcbiAgICBcImFzeW5jIGZ1bmN0aW9uIHZpZXdlclJlcXVlc3QoZXZlbnQpIHt9XCIsXG4gICAgXCJjb25zdCB2aWV3ZXJSZXF1ZXN0ID0gKGV2ZW50KSA9PiB7fTtcIixcbiAgICBcInZhciB4ID0gMSwgdmlld2VyUmVxdWVzdCA9IChldmVudCkgPT4ge307XCIsXG4gIF0pKFwiYWNjZXB0cyAlc1wiLCAoY29kZSkgPT4ge1xuICAgIGV4cGVjdCgoKSA9PiBjaGVja1ZpZXdlclJlcXVlc3RDb2RlKGNvZGUpKS5ub3QudG9UaHJvdygpO1xuICB9KTtcblxuICBpdChcInJlZnVzZXMgY29kZSB0aGF0IG5ldmVyIGRlZmluZXMgdmlld2VyUmVxdWVzdFwiLCAoKSA9PiB7XG4gICAgZXhwZWN0KCgpID0+XG4gICAgICBjaGVja1ZpZXdlclJlcXVlc3RDb2RlKFwiY29uc3Qgb2sgPSB2aWV3ZXJSZXF1ZXN0ID09PSAxO1wiKSxcbiAgICApLnRvVGhyb3coL211c3QgZGVmaW5lIGB2aWV3ZXJSZXF1ZXN0XFwoZXZlbnRcXClgLyk7XG4gIH0pO1xuXG4gIGl0KFwicmVmdXNlcyBjb2RlIHRoYXQgdXNlcyB0aGUgbmFtZSBoYW5kbGVyXCIsICgpID0+IHtcbiAgICBleHBlY3QoKCkgPT5cbiAgICAgIGNoZWNrVmlld2VyUmVxdWVzdENvZGUoXCJmdW5jdGlvbiB2aWV3ZXJSZXF1ZXN0KGV2ZW50KSB7fSAvLyBoYW5kbGVyXCIpLFxuICAgICkudG9UaHJvdygvdXNlcyB0aGUgbmFtZSBgaGFuZGxlcmAvKTtcbiAgfSk7XG59KTtcbiJdfQ==
|
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"@aws-sdk/client-s3": "^3.1141.0",
|
|
21
21
|
"@aws-sdk/client-ssm": "^3.1141.0",
|
|
22
22
|
"@aws-sdk/lib-storage": "^3.1141.0",
|
|
23
|
+
"@iconify-json/logos": "1.2.16",
|
|
23
24
|
"@mrgrain/jsii-struct-builder": "^0.7.64",
|
|
24
25
|
"@next/routing": "16.4.0",
|
|
25
26
|
"@smithy/signature-v4": "^2.3.0",
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"publishConfig": {
|
|
85
86
|
"access": "public"
|
|
86
87
|
},
|
|
87
|
-
"version": "0.7.
|
|
88
|
+
"version": "0.7.13",
|
|
88
89
|
"jest": {
|
|
89
90
|
"coverageProvider": "v8",
|
|
90
91
|
"testPathIgnorePatterns": [
|
|
@@ -190,6 +191,8 @@
|
|
|
190
191
|
"compat": "pnpm dlx projen compat",
|
|
191
192
|
"compile": "pnpm dlx projen compile",
|
|
192
193
|
"default": "pnpm dlx projen default",
|
|
194
|
+
"diagrams": "pnpm dlx projen diagrams",
|
|
195
|
+
"diagrams:check": "pnpm dlx projen diagrams:check",
|
|
193
196
|
"docgen": "pnpm dlx projen docgen",
|
|
194
197
|
"eject": "pnpm dlx projen eject",
|
|
195
198
|
"eslint": "pnpm dlx projen eslint",
|
|
@@ -205,8 +205,13 @@ class HarnessStack extends Stack {
|
|
|
205
205
|
* `use-cache-metadata-route-handler`'s `/[slug]` page, which threw on it (a 500,
|
|
206
206
|
* past the ALB's 200-499 range, so the task under test kept being replaced);
|
|
207
207
|
* `/_next/__cdk-nextjs-harness-health` was taken by `sub-shell-generation`'s
|
|
208
|
-
* `/[lang]/[slug]` as `lang: "_next"
|
|
209
|
-
*
|
|
208
|
+
* `/[lang]/[slug]` as `lang: "_next"`. Its fallback shell revalidates 15 minutes
|
|
209
|
+
* after `next build` (`fallbackRevalidate: 900`, the `'use cache'` root layout's
|
|
210
|
+
* default lifetime), and the first request past that serves it stale and
|
|
211
|
+
* regenerates it at runtime, so `/es/*` then reports a runtime root layout -
|
|
212
|
+
* `next start` does the same. The health check was simply that first request,
|
|
213
|
+
* well before the test's own; the test still fails if it runs more than 15
|
|
214
|
+
* minutes after the build (#274).
|
|
210
215
|
*/
|
|
211
216
|
const HEALTH_CHECK_PATH = "/_next/static/__cdk-nextjs-harness-health";
|
|
212
217
|
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Render the README architecture diagrams with real AWS icons.
|
|
4
|
+
*
|
|
5
|
+
* pnpm diagrams # docs/diagrams/*.mmd -> docs/diagrams/*.svg
|
|
6
|
+
* pnpm diagrams:check # fail if an .svg is missing, orphaned or stale
|
|
7
|
+
*
|
|
8
|
+
* Why committed SVGs and not ```mermaid blocks. GitHub's Mermaid renderer can't
|
|
9
|
+
* register icon packs, so `architecture-beta` there is limited to its generic
|
|
10
|
+
* built-in icons; and npm and Construct Hub don't render Mermaid at all. An SVG
|
|
11
|
+
* renders everywhere the README is shown.
|
|
12
|
+
*
|
|
13
|
+
* Why the check compares a hash and not the rendered bytes. Chromium lays out
|
|
14
|
+
* labels with whatever fonts the machine has, so a render on macOS and one on an
|
|
15
|
+
* Ubuntu runner differ even from the same source. Each SVG instead carries a
|
|
16
|
+
* SHA-256 of the `.mmd` it was rendered from and the `mmdc` and icon pack
|
|
17
|
+
* versions, which the check (no Chrome needed) recomputes from what's on disk.
|
|
18
|
+
*
|
|
19
|
+
* `mmdc` runs through `npx`, not as a devDependency: it pulls in Puppeteer, whose
|
|
20
|
+
* Chrome download pnpm blocks as a build script, and every install would pay for
|
|
21
|
+
* it. The icon pack is an exact-pinned devDependency, which keeps it out of the
|
|
22
|
+
* automated `upgrade-dev-deps` PRs: bumping either version is a hand edit that
|
|
23
|
+
* `diagrams:check` then makes you re-render for.
|
|
24
|
+
*/
|
|
25
|
+
import { execFileSync } from "node:child_process";
|
|
26
|
+
import { createHash } from "node:crypto";
|
|
27
|
+
import {
|
|
28
|
+
existsSync,
|
|
29
|
+
mkdtempSync,
|
|
30
|
+
readFileSync,
|
|
31
|
+
readdirSync,
|
|
32
|
+
rmSync,
|
|
33
|
+
writeFileSync,
|
|
34
|
+
} from "node:fs";
|
|
35
|
+
import { tmpdir } from "node:os";
|
|
36
|
+
import { basename, join, resolve } from "node:path";
|
|
37
|
+
import { pathToFileURL } from "node:url";
|
|
38
|
+
|
|
39
|
+
const MERMAID_CLI = "@mermaid-js/mermaid-cli@12.0.0";
|
|
40
|
+
const DIAGRAMS_DIR = resolve("docs", "diagrams");
|
|
41
|
+
const ICON_PACK_DIR = resolve("node_modules", "@iconify-json", "logos");
|
|
42
|
+
const ICON_PACK = join(ICON_PACK_DIR, "icons.json");
|
|
43
|
+
const ICON_PACK_VERSION = JSON.parse(
|
|
44
|
+
readFileSync(join(ICON_PACK_DIR, "package.json"), "utf8"),
|
|
45
|
+
).version;
|
|
46
|
+
const STAMP = /^<!-- source-sha256: ([0-9a-f]{64}) -->\n/;
|
|
47
|
+
|
|
48
|
+
const files = readdirSync(DIAGRAMS_DIR);
|
|
49
|
+
const sources = files.filter((f) => f.endsWith(".mmd")).sort();
|
|
50
|
+
|
|
51
|
+
const stampOf = (source) =>
|
|
52
|
+
createHash("sha256")
|
|
53
|
+
.update(`${MERMAID_CLI}\n@iconify-json/logos@${ICON_PACK_VERSION}\n`)
|
|
54
|
+
.update(readFileSync(join(DIAGRAMS_DIR, source)))
|
|
55
|
+
.digest("hex");
|
|
56
|
+
|
|
57
|
+
if (process.argv.includes("--check")) {
|
|
58
|
+
const stale = sources.filter((source) => {
|
|
59
|
+
const svg = join(DIAGRAMS_DIR, source.replace(/\.mmd$/, ".svg"));
|
|
60
|
+
if (!existsSync(svg)) return true;
|
|
61
|
+
return STAMP.exec(readFileSync(svg, "utf8"))?.[1] !== stampOf(source);
|
|
62
|
+
});
|
|
63
|
+
const orphaned = files.filter(
|
|
64
|
+
(f) => f.endsWith(".svg") && !sources.includes(f.replace(/\.svg$/, ".mmd")),
|
|
65
|
+
);
|
|
66
|
+
if (stale.length) {
|
|
67
|
+
console.error(
|
|
68
|
+
`Stale diagrams (run \`pnpm diagrams\` and commit the SVGs): ${stale.join(", ")}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (orphaned.length) {
|
|
72
|
+
console.error(`SVGs with no .mmd source: ${orphaned.join(", ")}`);
|
|
73
|
+
}
|
|
74
|
+
process.exit(stale.length || orphaned.length ? 1 : 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Chrome's sandbox can't start on GitHub's Ubuntu runners or in most containers.
|
|
78
|
+
const work = mkdtempSync(join(tmpdir(), "render-diagrams-"));
|
|
79
|
+
const puppeteerConfig = join(work, "puppeteer.json");
|
|
80
|
+
writeFileSync(puppeteerConfig, JSON.stringify({ args: ["--no-sandbox"] }));
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
for (const source of sources) {
|
|
84
|
+
const name = basename(source, ".mmd");
|
|
85
|
+
const input = join(DIAGRAMS_DIR, source);
|
|
86
|
+
const output = join(DIAGRAMS_DIR, `${name}.svg`);
|
|
87
|
+
execFileSync(
|
|
88
|
+
"npx",
|
|
89
|
+
[
|
|
90
|
+
"--yes",
|
|
91
|
+
MERMAID_CLI,
|
|
92
|
+
"--quiet",
|
|
93
|
+
"--puppeteerConfigFile",
|
|
94
|
+
puppeteerConfig,
|
|
95
|
+
"--iconPacksNamesAndUrls",
|
|
96
|
+
`logos#${pathToFileURL(ICON_PACK)}`,
|
|
97
|
+
"--input",
|
|
98
|
+
input,
|
|
99
|
+
"--output",
|
|
100
|
+
output,
|
|
101
|
+
],
|
|
102
|
+
// `npx` is `npx.cmd` on Windows, which only a shell can run.
|
|
103
|
+
{ stdio: "inherit", shell: process.platform === "win32" },
|
|
104
|
+
);
|
|
105
|
+
// Iconify names gradient ids with a random prefix; renumber them so an
|
|
106
|
+
// unchanged source re-renders to the same bytes on the same machine.
|
|
107
|
+
const ids = new Map();
|
|
108
|
+
const svg = readFileSync(output, "utf8").replace(
|
|
109
|
+
/IconifyId[0-9a-f]+/g,
|
|
110
|
+
(id) => {
|
|
111
|
+
if (!ids.has(id)) ids.set(id, `${name}-icon-${ids.size}`);
|
|
112
|
+
return ids.get(id);
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
const stamp = `<!-- source-sha256: ${stampOf(source)} -->\n`;
|
|
116
|
+
writeFileSync(output, `${stamp}${svg}\n`);
|
|
117
|
+
console.log(`rendered ${output}`);
|
|
118
|
+
}
|
|
119
|
+
} finally {
|
|
120
|
+
rmSync(work, { recursive: true, force: true });
|
|
121
|
+
}
|