cdk-nextjs 0.7.12 → 0.7.14
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 +346 -100
- package/.jsii.tabl.json +1 -1
- package/API.md +208 -0
- package/README.md +115 -100
- package/docs/breaking-changes.md +4 -2
- package/docs/development-guide.md +10 -0
- package/docs/harness-coverage.md +16 -6
- package/docs/next-build-output-guide.md +1 -1
- package/lib/adapter/adapter.mjs +4 -1
- package/lib/generated-structs/OptionalNextjsApiProps.d.ts +6 -0
- package/lib/generated-structs/OptionalNextjsApiProps.js +1 -1
- package/lib/generated-structs/OptionalNextjsDistributionProps.d.ts +27 -0
- package/lib/generated-structs/OptionalNextjsDistributionProps.js +1 -1
- package/lib/nextjs-api.d.ts +11 -0
- package/lib/nextjs-api.js +63 -9
- package/lib/nextjs-api.test.js +127 -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 +92 -2
- package/lib/nextjs-distribution.js +223 -109
- package/lib/nextjs-distribution.test.js +492 -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.d.ts +7 -0
- package/lib/root-constructs/nextjs-base-construct.js +14 -2
- package/lib/root-constructs/nextjs-global-containers.d.ts +16 -1
- package/lib/root-constructs/nextjs-global-containers.js +5 -2
- package/lib/root-constructs/nextjs-global-functions.d.ts +16 -1
- package/lib/root-constructs/nextjs-global-functions.js +5 -2
- package/lib/root-constructs/nextjs-regional-containers.js +1 -1
- package/lib/root-constructs/nextjs-regional-functions.js +3 -2
- 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/README.md +10 -8
- package/scripts/e2e-harness/app.js +7 -2
- package/scripts/render-diagrams.mjs +121 -0
|
@@ -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.14",
|
|
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",
|
|
@@ -150,19 +150,21 @@ with a shared stack named `hrns-shard-<n>` (with the type's infix) and each
|
|
|
150
150
|
running its own slice of that type's file list.
|
|
151
151
|
|
|
152
152
|
A run of one type starts all its shards at once. A run of `all` (the schedule)
|
|
153
|
-
lists 45 jobs, Global Functions first, then Regional
|
|
154
|
-
Containers and
|
|
155
|
-
hours end to end (about
|
|
156
|
-
2.
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
lists 45 jobs, Global Functions first, then Regional Containers, Global
|
|
154
|
+
Containers and Regional Functions, and runs at most 10 at a time, which is about
|
|
155
|
+
9.6 hours end to end (about 90 shard-hours: the Containers types' shards take
|
|
156
|
+
about 2.3 hours each, Global Functions' about 1.7 and Regional Functions' about
|
|
157
|
+
1.5). Regional Functions goes last because its shards are the shortest, so they
|
|
158
|
+
fill the slots the others free at the end. Listing Global Containers last, as
|
|
159
|
+
the workflow once did, left its last five shards running with half the slots
|
|
160
|
+
idle, for about 10.2 hours. Ten is the peak a single-type run of the other three already reaches: no more CloudFront
|
|
159
161
|
distributions at once than a Global Functions run, under the CloudFront
|
|
160
162
|
response-headers-policy quota (3 per distribution), no more ECR pulls than a
|
|
161
163
|
Regional Containers run, and no more of the org's concurrent jobs than the
|
|
162
164
|
schedule has always taken. A slot goes to the next job as soon as one frees, so
|
|
163
165
|
types overlap where one ends and the next begins; that is safe, because no two
|
|
164
166
|
entries share a stack name. Results arrive in that order: a Global Functions
|
|
165
|
-
regression shows up first, about 2 hours in.
|
|
167
|
+
regression shows up first, about 2 hours in, and Regional Functions' last.
|
|
166
168
|
|
|
167
169
|
To check the workflow itself rather than compatibility, dispatch `all` with
|
|
168
170
|
`shard_total: 1` and `test_filters: ../test/harness-smoke-manifest.json`: one
|
|
@@ -205,7 +207,7 @@ cancels. It is one group for every type, not one per type: each run's
|
|
|
205
207
|
account-wide sweep could otherwise delete another type's idle-looking shard stack
|
|
206
208
|
as that run warms into it. GitHub keeps only one _pending_ run per group, so
|
|
207
209
|
dispatch a second type once the first has started, not while one is queued, or
|
|
208
|
-
dispatch `all`. A dispatch on a schedule day waits behind the schedule's ~
|
|
210
|
+
dispatch `all`. A dispatch on a schedule day waits behind the schedule's ~10
|
|
209
211
|
hours. A pending run that gets cancelled that way, the scheduled one included,
|
|
210
212
|
loses no sweep: every run sweeps, not just the scheduled one.
|
|
211
213
|
|
|
@@ -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
|
+
}
|