canopycms-cdk 0.0.66-int.81 → 0.0.66-int.83
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/dist/constructs/asset-support.d.ts +85 -13
- package/dist/constructs/asset-support.js +87 -4
- package/dist/constructs/cms-distribution.d.ts +34 -0
- package/dist/constructs/cms-distribution.js +107 -3
- package/dist/constructs/cms-service.d.ts +50 -1
- package/dist/constructs/cms-service.js +134 -4
- package/lambda/asset-transform/dist/handler.js +3 -0
- package/lambda/asset-transform/dist/node_modules/.package-lock.json +39 -39
- package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +1 -1
- package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/{libvips-cpp.so.8.18.3 → libvips-cpp.so.8.18.6} +0 -0
- package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/package.json +2 -2
- package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/versions.json +13 -13
- package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/index.cjs +1 -1
- package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/lib/{sharp-linux-arm64-0.35.3.node → sharp-linux-arm64-0.35.4.node} +0 -0
- package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/package.json +2 -2
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/index.cjs +1 -1
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.4.node.js +1 -0
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/{sharp-wasm32-0.35.3.node.wasm → sharp-wasm32-0.35.4.node.wasm} +0 -0
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/package.json +2 -2
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/versions.json +9 -9
- package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.cjs +6 -6
- package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.mjs +6 -6
- package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.cjs +1 -1
- package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.mjs +1 -1
- package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.cts +2 -2
- package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.mts +2 -2
- package/lambda/asset-transform/dist/node_modules/sharp/dist/input.cjs +20 -15
- package/lambda/asset-transform/dist/node_modules/sharp/dist/input.mjs +20 -15
- package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.cjs +4 -0
- package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.mjs +4 -0
- package/lambda/asset-transform/dist/node_modules/sharp/dist/output.cjs +4 -7
- package/lambda/asset-transform/dist/node_modules/sharp/dist/output.mjs +4 -7
- package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.cjs +8 -8
- package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.mjs +8 -8
- package/lambda/asset-transform/dist/node_modules/sharp/lib/index.d.ts +2 -2
- package/lambda/asset-transform/dist/node_modules/sharp/package.json +38 -38
- package/lambda/asset-transform/dist/node_modules/sharp/src/common.cc +7 -4
- package/lambda/asset-transform/dist/node_modules/sharp/src/common.h +2 -2
- package/package.json +4 -4
- package/worker/dist/index.js +4497 -2689
- package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.3.node.js +0 -1
|
@@ -107,16 +107,54 @@ export interface AssetSupportProps {
|
|
|
107
107
|
*/
|
|
108
108
|
readonly transformLogGroupName?: string;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* The two CloudFront path patterns the asset system's behaviors are keyed
|
|
112
|
+
* under, derived from `PREFIXES` above (the single source of truth for these
|
|
113
|
+
* prefixes in this file) rather than spelled out again. Exported so
|
|
114
|
+
* `cms-distribution.ts`'s synth-time ordering guard can recognize them
|
|
115
|
+
* without a second, driftable copy of the literal strings.
|
|
116
|
+
*/
|
|
117
|
+
export declare const ASSETS_PATH_PATTERN: string;
|
|
118
|
+
export declare const ASSETS_TRANSFORM_PATH_PATTERN: string;
|
|
119
|
+
/**
|
|
120
|
+
* The literal property names of `AssetCloudFrontBehaviors`
|
|
121
|
+
* (`assets`/`assetsTransform`). It is a natural but broken mistake to spread
|
|
122
|
+
* `assetBehaviors()`'s return value directly into `additionalBehaviors` (a
|
|
123
|
+
* `Record<pathPattern, BehaviorOptions>`) - that type-checks and deploys
|
|
124
|
+
* clean, but synthesizes two CloudFront behaviors matching the literal path
|
|
125
|
+
* patterns `assets` and `assetsTransform`, which nothing ever requests,
|
|
126
|
+
* instead of the real `/assets/*` and `/assets/t/*` patterns. Use
|
|
127
|
+
* `attachTo()` (below) or `CanopyCmsDistribution`'s `assetSupport` prop
|
|
128
|
+
* instead. Exported so `cms-distribution.ts`'s synth-time guard can
|
|
129
|
+
* recognize the mistake and name it in its error.
|
|
130
|
+
*/
|
|
131
|
+
export declare const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS: readonly ["assets", "assetsTransform"];
|
|
110
132
|
/**
|
|
111
133
|
* The two CloudFront behavior configs the asset system needs, keyed by the
|
|
112
134
|
* path pattern they belong under. Each value is a full `BehaviorOptions`
|
|
113
|
-
* (origin included)
|
|
135
|
+
* (origin included).
|
|
136
|
+
*
|
|
137
|
+
* Prefer `AssetSupport.attachTo(distribution)` or `CanopyCmsDistribution`'s
|
|
138
|
+
* `assetSupport` prop over consuming this directly - both encode the
|
|
139
|
+
* required attachment order in exactly one place instead of asking every
|
|
140
|
+
* caller to reproduce it correctly. This return value remains useful as an
|
|
141
|
+
* ESCAPE HATCH for a bespoke `new cloudfront.Distribution(...)` assembled
|
|
142
|
+
* entirely inline (its `additionalBehaviors` is fixed at construction, so
|
|
143
|
+
* there is no distribution yet to call `addBehavior` on) - but manual use
|
|
144
|
+
* must still preserve the ordering shown below, and
|
|
145
|
+
* `CanopyCmsDistribution`'s synth-time guard (`mergeBehaviors`) actively
|
|
146
|
+
* rejects three ways this goes wrong: `/assets/*` listed before
|
|
147
|
+
* `/assets/t/*`; the literal keys `assets`/`assetsTransform` (see
|
|
148
|
+
* `ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS`) from spreading this object directly
|
|
149
|
+
* into a `Record`; or an asset pattern listed here at all while
|
|
150
|
+
* `CanopyCmsDistribution`'s `assetSupport` prop is also passed. See
|
|
151
|
+
* `assertNoAssetBehaviorOrderingHazards`'s doc comment for the full list.
|
|
114
152
|
*
|
|
115
153
|
* ```ts
|
|
116
154
|
* const behaviors = assetSupport.assetBehaviors()
|
|
117
155
|
*
|
|
118
|
-
* // Building a new distribution. CloudFront matches path patterns in
|
|
119
|
-
* // order they're listed and stops at the first match, so the more
|
|
156
|
+
* // Building a new distribution inline. CloudFront matches path patterns in
|
|
157
|
+
* // the order they're listed and stops at the first match, so the more
|
|
120
158
|
* // specific '/assets/t/*' MUST come before '/assets/*' - otherwise the
|
|
121
159
|
* // broader S3-only pattern swallows transform requests first and they
|
|
122
160
|
* // 403 with no Lambda fallback.
|
|
@@ -127,12 +165,6 @@ export interface AssetSupportProps {
|
|
|
127
165
|
* '/assets/*': behaviors.assets,
|
|
128
166
|
* },
|
|
129
167
|
* })
|
|
130
|
-
*
|
|
131
|
-
* // Adding to an existing distribution: the same first-match-wins ordering
|
|
132
|
-
* // applies to the resulting CacheBehaviors list, so call addBehavior for
|
|
133
|
-
* // '/assets/t/*' before '/assets/*' here too.
|
|
134
|
-
* distribution.addBehavior('/assets/t/*', behaviors.assetsTransform.origin, behaviors.assetsTransform)
|
|
135
|
-
* distribution.addBehavior('/assets/*', behaviors.assets.origin, behaviors.assets)
|
|
136
168
|
* ```
|
|
137
169
|
*/
|
|
138
170
|
export interface AssetCloudFrontBehaviors {
|
|
@@ -165,9 +197,11 @@ export interface AssetCloudFrontBehaviors {
|
|
|
165
197
|
* (custom name/retention/removal policy instead of the
|
|
166
198
|
* CloudFormation-implicit `/aws/lambda/<function-name>` group), and its
|
|
167
199
|
* OAC-locked Function URL.
|
|
168
|
-
* - `
|
|
169
|
-
* distribution
|
|
170
|
-
*
|
|
200
|
+
* - `attachTo(distribution)`, which attaches the two CloudFront behaviors a
|
|
201
|
+
* consuming distribution needs in the only safe order (see its doc comment
|
|
202
|
+
* for why the order is the whole point); `assetBehaviors()` is the escape
|
|
203
|
+
* hatch for a distribution built entirely by hand (see
|
|
204
|
+
* `AssetCloudFrontBehaviors`'s doc comment).
|
|
171
205
|
* - `grantUploadAccess()`, the exact prefix-scoped grants the CMS/editor
|
|
172
206
|
* principal needs to run `S3AssetStore` (packages/canopycms/src/assets/store-s3.ts).
|
|
173
207
|
*/
|
|
@@ -183,10 +217,48 @@ export declare class AssetSupport extends Construct {
|
|
|
183
217
|
/** Effective advisory upload-size cap (see `AssetSupportProps.maxUploadBytes`). */
|
|
184
218
|
readonly maxUploadBytes: number;
|
|
185
219
|
private readonly behaviors;
|
|
220
|
+
/** Distributions `attachTo` has already wired -- see that method's duplicate check. */
|
|
221
|
+
private readonly attachedDistributions;
|
|
186
222
|
constructor(scope: Construct, id: string, props: AssetSupportProps);
|
|
187
223
|
private buildBehaviors;
|
|
188
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* The two CloudFront behavior configs this system needs.
|
|
226
|
+
*
|
|
227
|
+
* Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s
|
|
228
|
+
* `assetSupport` prop, which attach these to a distribution in the only
|
|
229
|
+
* safe order automatically. This method exists as the escape hatch for a
|
|
230
|
+
* distribution assembled entirely by hand - see `AssetCloudFrontBehaviors`'s
|
|
231
|
+
* doc comment for that shape and its ordering requirements.
|
|
232
|
+
*/
|
|
189
233
|
assetBehaviors(): AssetCloudFrontBehaviors;
|
|
234
|
+
/**
|
|
235
|
+
* Attach both asset behaviors to a concrete CloudFront distribution, in the
|
|
236
|
+
* only safe order.
|
|
237
|
+
*
|
|
238
|
+
* THIS METHOD EXISTS BECAUSE THE ORDER IS THE WHOLE POINT.
|
|
239
|
+
* `assetBehaviors()` returns `{ assets, assetsTransform }` with no path
|
|
240
|
+
* pattern attached at all (see that method's and `AssetCloudFrontBehaviors`'s
|
|
241
|
+
* doc comments) - so nothing stops a caller from attaching the two in
|
|
242
|
+
* either order. CloudFront matches path patterns in the order given and
|
|
243
|
+
* stops at the first match. `/assets/*` is a broader, S3-only pattern that
|
|
244
|
+
* also matches every `/assets/t/*` request; `/assets/t/*` is an origin
|
|
245
|
+
* group that fails over to the transform Lambda on a miss. Attach
|
|
246
|
+
* `/assets/*` first and every never-yet-computed transform gets a
|
|
247
|
+
* permanent 403 (an OAC-signed S3 miss reports 403) while already-computed
|
|
248
|
+
* transforms keep working - silent, launch-delayed, and permanent.
|
|
249
|
+
* `'/assets/*'` also sorts BEFORE `'/assets/t/*'` lexicographically (`*` =
|
|
250
|
+
* 0x2A, `t` = 0x74), so alphabetizing the keys reproduces exactly this
|
|
251
|
+
* failure, with no synth or deploy error to catch it.
|
|
252
|
+
*
|
|
253
|
+
* Needs a concrete `cloudfront.Distribution` - `addBehavior` is an instance
|
|
254
|
+
* method on that class, not on `IDistribution` (what an imported/looked-up
|
|
255
|
+
* distribution reference gives you). For a distribution built entirely
|
|
256
|
+
* inline (its `additionalBehaviors` fixed at construction, with no
|
|
257
|
+
* distribution yet to call `addBehavior` on), call `assetBehaviors()`
|
|
258
|
+
* directly instead and list `/assets/t/*` before `/assets/*` yourself - see
|
|
259
|
+
* `AssetCloudFrontBehaviors`'s doc comment.
|
|
260
|
+
*/
|
|
261
|
+
attachTo(distribution: cloudfront.Distribution): void;
|
|
190
262
|
/**
|
|
191
263
|
* Grant the CMS/editor principal (the CMS Lambda's role, typically) the
|
|
192
264
|
* exact permissions `S3AssetStore` (packages/canopycms/src/assets/store-s3.ts)
|
|
@@ -111,6 +111,28 @@ const TRANSFORM_OUTPUT_RETENTION = Duration.days(180);
|
|
|
111
111
|
const TRANSFORM_CACHE_MIN_TTL = Duration.seconds(0);
|
|
112
112
|
const TRANSFORM_CACHE_DEFAULT_TTL = Duration.days(1);
|
|
113
113
|
const TRANSFORM_CACHE_MAX_TTL = Duration.days(365);
|
|
114
|
+
/**
|
|
115
|
+
* The two CloudFront path patterns the asset system's behaviors are keyed
|
|
116
|
+
* under, derived from `PREFIXES` above (the single source of truth for these
|
|
117
|
+
* prefixes in this file) rather than spelled out again. Exported so
|
|
118
|
+
* `cms-distribution.ts`'s synth-time ordering guard can recognize them
|
|
119
|
+
* without a second, driftable copy of the literal strings.
|
|
120
|
+
*/
|
|
121
|
+
export const ASSETS_PATH_PATTERN = `/${PREFIXES.public}/*`;
|
|
122
|
+
export const ASSETS_TRANSFORM_PATH_PATTERN = `/${PREFIXES.transform}/*`;
|
|
123
|
+
/**
|
|
124
|
+
* The literal property names of `AssetCloudFrontBehaviors`
|
|
125
|
+
* (`assets`/`assetsTransform`). It is a natural but broken mistake to spread
|
|
126
|
+
* `assetBehaviors()`'s return value directly into `additionalBehaviors` (a
|
|
127
|
+
* `Record<pathPattern, BehaviorOptions>`) - that type-checks and deploys
|
|
128
|
+
* clean, but synthesizes two CloudFront behaviors matching the literal path
|
|
129
|
+
* patterns `assets` and `assetsTransform`, which nothing ever requests,
|
|
130
|
+
* instead of the real `/assets/*` and `/assets/t/*` patterns. Use
|
|
131
|
+
* `attachTo()` (below) or `CanopyCmsDistribution`'s `assetSupport` prop
|
|
132
|
+
* instead. Exported so `cms-distribution.ts`'s synth-time guard can
|
|
133
|
+
* recognize the mistake and name it in its error.
|
|
134
|
+
*/
|
|
135
|
+
export const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS = ['assets', 'assetsTransform'];
|
|
114
136
|
/**
|
|
115
137
|
* Per-site CDK construct for the asset/media delivery system (see
|
|
116
138
|
* `.claude/future-tasks/assets-media-system.md` for the full design record).
|
|
@@ -123,15 +145,19 @@ const TRANSFORM_CACHE_MAX_TTL = Duration.days(365);
|
|
|
123
145
|
* (custom name/retention/removal policy instead of the
|
|
124
146
|
* CloudFormation-implicit `/aws/lambda/<function-name>` group), and its
|
|
125
147
|
* OAC-locked Function URL.
|
|
126
|
-
* - `
|
|
127
|
-
* distribution
|
|
128
|
-
*
|
|
148
|
+
* - `attachTo(distribution)`, which attaches the two CloudFront behaviors a
|
|
149
|
+
* consuming distribution needs in the only safe order (see its doc comment
|
|
150
|
+
* for why the order is the whole point); `assetBehaviors()` is the escape
|
|
151
|
+
* hatch for a distribution built entirely by hand (see
|
|
152
|
+
* `AssetCloudFrontBehaviors`'s doc comment).
|
|
129
153
|
* - `grantUploadAccess()`, the exact prefix-scoped grants the CMS/editor
|
|
130
154
|
* principal needs to run `S3AssetStore` (packages/canopycms/src/assets/store-s3.ts).
|
|
131
155
|
*/
|
|
132
156
|
export class AssetSupport extends Construct {
|
|
133
157
|
constructor(scope, id, props) {
|
|
134
158
|
super(scope, id);
|
|
159
|
+
/** Distributions `attachTo` has already wired -- see that method's duplicate check. */
|
|
160
|
+
this.attachedDistributions = new WeakSet();
|
|
135
161
|
// Fail closed before anything else: refuse to build a stack around a
|
|
136
162
|
// transform Lambda whose code asset was never verified to contain the
|
|
137
163
|
// linux/arm64 sharp binary. Without this, `pnpm test` (whose
|
|
@@ -317,10 +343,67 @@ export class AssetSupport extends Construct {
|
|
|
317
343
|
};
|
|
318
344
|
return { assets, assetsTransform };
|
|
319
345
|
}
|
|
320
|
-
/**
|
|
346
|
+
/**
|
|
347
|
+
* The two CloudFront behavior configs this system needs.
|
|
348
|
+
*
|
|
349
|
+
* Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s
|
|
350
|
+
* `assetSupport` prop, which attach these to a distribution in the only
|
|
351
|
+
* safe order automatically. This method exists as the escape hatch for a
|
|
352
|
+
* distribution assembled entirely by hand - see `AssetCloudFrontBehaviors`'s
|
|
353
|
+
* doc comment for that shape and its ordering requirements.
|
|
354
|
+
*/
|
|
321
355
|
assetBehaviors() {
|
|
322
356
|
return this.behaviors;
|
|
323
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Attach both asset behaviors to a concrete CloudFront distribution, in the
|
|
360
|
+
* only safe order.
|
|
361
|
+
*
|
|
362
|
+
* THIS METHOD EXISTS BECAUSE THE ORDER IS THE WHOLE POINT.
|
|
363
|
+
* `assetBehaviors()` returns `{ assets, assetsTransform }` with no path
|
|
364
|
+
* pattern attached at all (see that method's and `AssetCloudFrontBehaviors`'s
|
|
365
|
+
* doc comments) - so nothing stops a caller from attaching the two in
|
|
366
|
+
* either order. CloudFront matches path patterns in the order given and
|
|
367
|
+
* stops at the first match. `/assets/*` is a broader, S3-only pattern that
|
|
368
|
+
* also matches every `/assets/t/*` request; `/assets/t/*` is an origin
|
|
369
|
+
* group that fails over to the transform Lambda on a miss. Attach
|
|
370
|
+
* `/assets/*` first and every never-yet-computed transform gets a
|
|
371
|
+
* permanent 403 (an OAC-signed S3 miss reports 403) while already-computed
|
|
372
|
+
* transforms keep working - silent, launch-delayed, and permanent.
|
|
373
|
+
* `'/assets/*'` also sorts BEFORE `'/assets/t/*'` lexicographically (`*` =
|
|
374
|
+
* 0x2A, `t` = 0x74), so alphabetizing the keys reproduces exactly this
|
|
375
|
+
* failure, with no synth or deploy error to catch it.
|
|
376
|
+
*
|
|
377
|
+
* Needs a concrete `cloudfront.Distribution` - `addBehavior` is an instance
|
|
378
|
+
* method on that class, not on `IDistribution` (what an imported/looked-up
|
|
379
|
+
* distribution reference gives you). For a distribution built entirely
|
|
380
|
+
* inline (its `additionalBehaviors` fixed at construction, with no
|
|
381
|
+
* distribution yet to call `addBehavior` on), call `assetBehaviors()`
|
|
382
|
+
* directly instead and list `/assets/t/*` before `/assets/*` yourself - see
|
|
383
|
+
* `AssetCloudFrontBehaviors`'s doc comment.
|
|
384
|
+
*/
|
|
385
|
+
attachTo(distribution) {
|
|
386
|
+
// `addBehavior` does not dedupe, and these calls bypass
|
|
387
|
+
// `CanopyCmsDistribution`'s own synth-time guard because they run after
|
|
388
|
+
// that distribution is constructed. So attaching twice -- passing the
|
|
389
|
+
// `assetSupport` prop AND calling this yourself, most likely, since the
|
|
390
|
+
// prop's doc comment describes them as equivalent -- synthesizes
|
|
391
|
+
// ['/assets/t/*','/assets/*','/assets/t/*','/assets/*'] and fails at
|
|
392
|
+
// deploy time when CloudFront rejects the duplicate patterns. Refuse it
|
|
393
|
+
// here instead, where the message can say which two routes collided.
|
|
394
|
+
if (this.attachedDistributions.has(distribution)) {
|
|
395
|
+
throw new Error(`AssetSupport: attachTo() was already called for this distribution. Each pattern ` +
|
|
396
|
+
`would be attached twice and CloudFront rejects duplicate path patterns at deploy ` +
|
|
397
|
+
`time. This usually means the distribution was given CanopyCmsDistribution's ` +
|
|
398
|
+
`\`assetSupport\` prop (which calls attachTo for you) as well as an explicit ` +
|
|
399
|
+
`attachTo() call -- keep one.`);
|
|
400
|
+
}
|
|
401
|
+
this.attachedDistributions.add(distribution);
|
|
402
|
+
const { origin: transformOrigin, ...transformRest } = this.behaviors.assetsTransform;
|
|
403
|
+
const { origin: assetsOrigin, ...assetsRest } = this.behaviors.assets;
|
|
404
|
+
distribution.addBehavior(ASSETS_TRANSFORM_PATH_PATTERN, transformOrigin, transformRest);
|
|
405
|
+
distribution.addBehavior(ASSETS_PATH_PATTERN, assetsOrigin, assetsRest);
|
|
406
|
+
}
|
|
324
407
|
/**
|
|
325
408
|
* Grant the CMS/editor principal (the CMS Lambda's role, typically) the
|
|
326
409
|
* exact permissions `S3AssetStore` (packages/canopycms/src/assets/store-s3.ts)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Construct } from 'constructs';
|
|
2
2
|
import { Duration, aws_cloudfront as cloudfront, aws_certificatemanager as acm, aws_route53 as route53, aws_lambda as lambda } from 'aws-cdk-lib';
|
|
3
|
+
import type { AssetSupport } from './asset-support.js';
|
|
3
4
|
export interface CanopyCmsDistributionProps {
|
|
4
5
|
/** Lambda Function URL from CanopyCmsService */
|
|
5
6
|
functionUrl: lambda.FunctionUrl;
|
|
@@ -49,8 +50,39 @@ export interface CanopyCmsDistributionProps {
|
|
|
49
50
|
* overridden key takes YOUR position in this object, not the position the
|
|
50
51
|
* default held. (A plain object spread would do the opposite; see
|
|
51
52
|
* `mergeBehaviors`.)
|
|
53
|
+
*
|
|
54
|
+
* Prefer the `assetSupport` prop over wiring `AssetSupport`'s behaviors in
|
|
55
|
+
* here by hand — but if you do it by hand anyway, `mergeBehaviors` throws
|
|
56
|
+
* at synth if it sees any of the three AssetSupport footguns: `/assets/*`
|
|
57
|
+
* listed before `/assets/t/*`; the literal keys `assets`/`assetsTransform`
|
|
58
|
+
* (from spreading `assetBehaviors()`'s return value directly into this
|
|
59
|
+
* object instead of keying it by path pattern); or an asset pattern listed
|
|
60
|
+
* here at all while the `assetSupport` prop is also passed (see that
|
|
61
|
+
* prop's own doc comment). See `assertNoAssetBehaviorOrderingHazards`'s
|
|
62
|
+
* doc comment for the full list.
|
|
52
63
|
*/
|
|
53
64
|
additionalBehaviors?: Record<string, cloudfront.BehaviorOptions>;
|
|
65
|
+
/**
|
|
66
|
+
* Attach `AssetSupport`'s CloudFront behaviors (`/assets/*` and
|
|
67
|
+
* `/assets/t/*`) to the distribution this construct builds, in the only
|
|
68
|
+
* safe order - see `AssetSupport.attachTo()`'s doc comment for why the
|
|
69
|
+
* order matters. Equivalent to calling
|
|
70
|
+
* `assetSupport.attachTo(distribution)` yourself right after construction,
|
|
71
|
+
* but with nothing to forget.
|
|
72
|
+
*
|
|
73
|
+
* Prefer this over passing `assetSupport.assetBehaviors()` through
|
|
74
|
+
* `additionalBehaviors` by hand - that stays available as an escape hatch
|
|
75
|
+
* (e.g. for behaviors that are not from `AssetSupport` at all), and this
|
|
76
|
+
* construct's synth-time guard (`mergeBehaviors`) still checks it for the
|
|
77
|
+
* three ways that manual wiring is known to go wrong: see
|
|
78
|
+
* `additionalBehaviors`'s own doc comment.
|
|
79
|
+
*
|
|
80
|
+
* No ordering logic lives in this construct beyond calling this method -
|
|
81
|
+
* ordering is encoded exactly once, in `AssetSupport.attachTo()` itself.
|
|
82
|
+
*
|
|
83
|
+
* @default - no asset behaviors are attached
|
|
84
|
+
*/
|
|
85
|
+
assetSupport?: AssetSupport;
|
|
54
86
|
}
|
|
55
87
|
/**
|
|
56
88
|
* Optional CDK construct for CanopyCMS CloudFront distribution.
|
|
@@ -66,6 +98,8 @@ export interface CanopyCmsDistributionProps {
|
|
|
66
98
|
* - Cache policies: no-cache for /api/* and /edit*, cache /_next/static/*
|
|
67
99
|
* - Viewer-request CloudFront Function setting x-forwarded-host (redirect-URL
|
|
68
100
|
* derivation behind the Host-stripping OAC origin)
|
|
101
|
+
* - `AssetSupport`'s `/assets/*` and `/assets/t/*` behaviors, in the required
|
|
102
|
+
* order, when the `assetSupport` prop is passed
|
|
69
103
|
*/
|
|
70
104
|
export declare class CanopyCmsDistribution extends Construct {
|
|
71
105
|
/** The CloudFront distribution */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Construct } from 'constructs';
|
|
2
2
|
import { Duration, Stack, Token, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_certificatemanager as acm, aws_route53 as route53, aws_route53_targets as targets, } from 'aws-cdk-lib';
|
|
3
3
|
import { DEFAULT_CMS_LAMBDA_TIMEOUT, MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT } from './cms-service.js';
|
|
4
|
+
import { ASSETS_PATH_PATTERN, ASSETS_TRANSFORM_PATH_PATTERN, ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS, } from './asset-support.js';
|
|
4
5
|
/**
|
|
5
6
|
* Merge caller behaviors over this construct's own, preserving THE CALLER'S
|
|
6
7
|
* ordering for every key they supply.
|
|
@@ -16,11 +17,105 @@ import { DEFAULT_CMS_LAMBDA_TIMEOUT, MAX_CLOUDFRONT_ORIGIN_READ_TIMEOUT } from '
|
|
|
16
17
|
* Dropping collided defaults FIRST means the caller's own object contributes
|
|
17
18
|
* its keys in its own order.
|
|
18
19
|
*/
|
|
19
|
-
function mergeBehaviors(defaults, caller) {
|
|
20
|
+
function mergeBehaviors(defaults, caller, attachingAssetSupport) {
|
|
20
21
|
if (!caller)
|
|
21
22
|
return defaults;
|
|
22
23
|
const uncollided = Object.fromEntries(Object.entries(defaults).filter(([pattern]) => !(pattern in caller)));
|
|
23
|
-
|
|
24
|
+
const merged = { ...uncollided, ...caller };
|
|
25
|
+
assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport);
|
|
26
|
+
return merged;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Guards against the three AssetSupport CloudFront-behavior hazards a manually
|
|
30
|
+
* assembled `additionalBehaviors` can introduce (see
|
|
31
|
+
* `AssetSupport.attachTo()`'s doc comment for the full mechanism):
|
|
32
|
+
*
|
|
33
|
+
* 1. The literal keys `assets`/`assetsTransform` present in `merged` mean
|
|
34
|
+
* `assetSupport.assetBehaviors()`'s return value was spread directly into
|
|
35
|
+
* `additionalBehaviors` (a `Record<pathPattern, BehaviorOptions>`) instead
|
|
36
|
+
* of being attached via `assetSupport.attachTo(distribution)` or this
|
|
37
|
+
* construct's `assetSupport` prop. This type-checks and deploys clean,
|
|
38
|
+
* synthesizing two behaviors matching the literal path patterns `assets`
|
|
39
|
+
* and `assetsTransform`, which nothing ever requests.
|
|
40
|
+
* 2. `/assets/*` listed before `/assets/t/*` means CloudFront's first-match-
|
|
41
|
+
* wins ordering serves every transform request off the broader, S3-only
|
|
42
|
+
* `/assets/*` behavior, permanently 403ing any derivative that has not
|
|
43
|
+
* already been computed (an OAC-signed S3 miss reports 403).
|
|
44
|
+
* 3. Either asset pattern present in `merged` WHILE the `assetSupport` prop is
|
|
45
|
+
* also passed means both wiring routes are active at once, so each pattern
|
|
46
|
+
* is attached twice. This is the migration mistake specifically - keeping a
|
|
47
|
+
* hand-wired block while adopting the prop - and hazard 2 cannot catch it,
|
|
48
|
+
* because the hand-written order is usually the correct one.
|
|
49
|
+
*
|
|
50
|
+
* `Object.keys` insertion order is spec-guaranteed for string keys like
|
|
51
|
+
* these (none are integer-like array-index strings), so comparing index
|
|
52
|
+
* order here is sound.
|
|
53
|
+
*
|
|
54
|
+
* SCOPE LIMIT: this protects only callers going through
|
|
55
|
+
* `CanopyCmsDistribution`'s own `additionalBehaviors` merge. It cannot help a
|
|
56
|
+
* bespoke `new cloudfront.Distribution(...)` assembled elsewhere -
|
|
57
|
+
* `AssetSupport.attachTo()` is the answer there.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* CloudFront treats the leading `/` on a path pattern as optional -- `assets/*`
|
|
61
|
+
* and `/assets/*` match exactly the same requests, and AWS's own console and
|
|
62
|
+
* docs frequently show the slash-less spelling. The checks below therefore
|
|
63
|
+
* compare NORMALIZED keys: without this, writing `{ 'assets/*': ..., 'assets/t/*': ... }`
|
|
64
|
+
* walked past all three hazards and synthesized the broad-pattern-first order
|
|
65
|
+
* this guard exists to refuse -- silently, which is the worst of the failure
|
|
66
|
+
* modes here.
|
|
67
|
+
*/
|
|
68
|
+
function normalizePathPattern(pattern) {
|
|
69
|
+
return pattern.startsWith('/') ? pattern.slice(1) : pattern;
|
|
70
|
+
}
|
|
71
|
+
function assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport) {
|
|
72
|
+
const keys = Object.keys(merged).map(normalizePathPattern);
|
|
73
|
+
const assetsPattern = normalizePathPattern(ASSETS_PATH_PATTERN);
|
|
74
|
+
const transformPattern = normalizePathPattern(ASSETS_TRANSFORM_PATH_PATTERN);
|
|
75
|
+
const spreadMistakeKeys = ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS.filter((key) => key in merged);
|
|
76
|
+
if (spreadMistakeKeys.length > 0) {
|
|
77
|
+
throw new Error(`CanopyCmsDistribution: additionalBehaviors has literal key(s) ` +
|
|
78
|
+
`${spreadMistakeKeys.map((k) => JSON.stringify(k)).join(', ')}, which is not a ` +
|
|
79
|
+
`CloudFront path pattern. This usually means assetSupport.assetBehaviors()'s return ` +
|
|
80
|
+
`value was spread directly into additionalBehaviors instead of attached via ` +
|
|
81
|
+
`assetSupport.attachTo(distribution) or this construct's \`assetSupport\` prop - either ` +
|
|
82
|
+
`of which attaches the real '${ASSETS_PATH_PATTERN}' and '${ASSETS_TRANSFORM_PATH_PATTERN}' ` +
|
|
83
|
+
`path patterns in the required order.`);
|
|
84
|
+
}
|
|
85
|
+
// Both halves wired at once. `attachTo` runs AFTER the distribution is
|
|
86
|
+
// constructed, so its `addBehavior` calls never pass through this function -
|
|
87
|
+
// meaning a caller who keeps a hand-wired asset block AND adopts the
|
|
88
|
+
// `assetSupport` prop gets each pattern attached twice. Measured: CDK does
|
|
89
|
+
// not object, and synthesizes CacheBehaviors
|
|
90
|
+
// ['/assets/t/*','/assets/*','/assets/t/*','/assets/*'], which CloudFront
|
|
91
|
+
// then rejects at deploy time for the duplicate path patterns. That is a
|
|
92
|
+
// late failure of exactly the kind this guard exists to convert into a synth
|
|
93
|
+
// one, and it is the most likely mistake during migration: the correct
|
|
94
|
+
// hand-written order does NOT trip the check below, so nothing else would
|
|
95
|
+
// catch it.
|
|
96
|
+
if (attachingAssetSupport) {
|
|
97
|
+
const alsoWiredByHand = [ASSETS_PATH_PATTERN, ASSETS_TRANSFORM_PATH_PATTERN].filter((pattern) => keys.includes(normalizePathPattern(pattern)));
|
|
98
|
+
if (alsoWiredByHand.length > 0) {
|
|
99
|
+
throw new Error(`CanopyCmsDistribution: the \`assetSupport\` prop attaches ` +
|
|
100
|
+
`'${ASSETS_TRANSFORM_PATH_PATTERN}' and '${ASSETS_PATH_PATTERN}' itself, but ` +
|
|
101
|
+
`additionalBehaviors already lists ${alsoWiredByHand.map((p) => `'${p}'`).join(' and ')}. ` +
|
|
102
|
+
`Each pattern would be attached twice and CloudFront rejects duplicate path patterns at ` +
|
|
103
|
+
`deploy time. Remove the asset entries from additionalBehaviors and keep the ` +
|
|
104
|
+
`\`assetSupport\` prop, which also guarantees the required order.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const assetsIndex = keys.indexOf(assetsPattern);
|
|
108
|
+
const transformIndex = keys.indexOf(transformPattern);
|
|
109
|
+
if (assetsIndex !== -1 && transformIndex !== -1 && assetsIndex < transformIndex) {
|
|
110
|
+
throw new Error(`CanopyCmsDistribution: additionalBehaviors lists '${ASSETS_PATH_PATTERN}' before ` +
|
|
111
|
+
`'${ASSETS_TRANSFORM_PATH_PATTERN}'. CloudFront matches path patterns in the order given ` +
|
|
112
|
+
`and stops at the first match, so every '${ASSETS_TRANSFORM_PATH_PATTERN}' request would ` +
|
|
113
|
+
`be served by the broader, S3-only '${ASSETS_PATH_PATTERN}' behavior and never fail over ` +
|
|
114
|
+
`to the transform Lambda - a permanent 403 on any derivative that has not already been ` +
|
|
115
|
+
`computed. List '${ASSETS_TRANSFORM_PATH_PATTERN}' first, or use ` +
|
|
116
|
+
`assetSupport.attachTo(distribution) / this construct's \`assetSupport\` prop, which get ` +
|
|
117
|
+
`the order right automatically.`);
|
|
118
|
+
}
|
|
24
119
|
}
|
|
25
120
|
/**
|
|
26
121
|
* Optional CDK construct for CanopyCMS CloudFront distribution.
|
|
@@ -36,6 +131,8 @@ function mergeBehaviors(defaults, caller) {
|
|
|
36
131
|
* - Cache policies: no-cache for /api/* and /edit*, cache /_next/static/*
|
|
37
132
|
* - Viewer-request CloudFront Function setting x-forwarded-host (redirect-URL
|
|
38
133
|
* derivation behind the Host-stripping OAC origin)
|
|
134
|
+
* - `AssetSupport`'s `/assets/*` and `/assets/t/*` behaviors, in the required
|
|
135
|
+
* order, when the `assetSupport` prop is passed
|
|
39
136
|
*/
|
|
40
137
|
export class CanopyCmsDistribution extends Construct {
|
|
41
138
|
constructor(scope, id, props) {
|
|
@@ -160,8 +257,15 @@ export class CanopyCmsDistribution extends Construct {
|
|
|
160
257
|
cachePolicy: staticCachePolicy,
|
|
161
258
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
162
259
|
},
|
|
163
|
-
}, props.additionalBehaviors),
|
|
260
|
+
}, props.additionalBehaviors, props.assetSupport !== undefined),
|
|
164
261
|
});
|
|
262
|
+
// Attach AssetSupport's behaviors (if provided), in the only safe order -
|
|
263
|
+
// see AssetSupport.attachTo()'s doc comment. This is the ONLY place this
|
|
264
|
+
// construct deals with the asset behaviors' ordering; the actual ordering
|
|
265
|
+
// logic lives exactly once, inside attachTo() itself. Callers who instead
|
|
266
|
+
// pass `assetSupport.assetBehaviors()` through `additionalBehaviors` by
|
|
267
|
+
// hand are covered by `mergeBehaviors`'s synth-time guard above instead.
|
|
268
|
+
props.assetSupport?.attachTo(this.distribution);
|
|
165
269
|
// ========================================================================
|
|
166
270
|
// DNS Records
|
|
167
271
|
// ========================================================================
|
|
@@ -72,8 +72,57 @@ export interface CanopyCmsServiceProps {
|
|
|
72
72
|
githubTokenSecretArn?: string;
|
|
73
73
|
/** Secrets Manager ARN for the Clerk secret key */
|
|
74
74
|
clerkSecretKeySecretArn?: string;
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* The GitHub repository's default branch name (default: 'main').
|
|
77
|
+
*
|
|
78
|
+
* Interpolated straight into a git ref (`refs/heads/{baseBranch}`) and into
|
|
79
|
+
* the worker's `.env` heredoc, so an invalid value is rejected at synth (see
|
|
80
|
+
* `assertValidGitBranchName` above) instead of deploying an instance that
|
|
81
|
+
* boots successfully and then fails. And fail it will, permanently:
|
|
82
|
+
* `verifyBaseBranchExists`
|
|
83
|
+
* (packages/canopycms/src/worker/cms-worker.ts) throws when the named branch
|
|
84
|
+
* does not exist in the cloned `remote.git`, `start()`'s catch records the
|
|
85
|
+
* fatal error, `worker/index.ts` exits 1, and systemd's `Restart=always`
|
|
86
|
+
* repeats that forever — there is no working worker at all until the value
|
|
87
|
+
* is fixed and the instance replaced, and `rebaseActiveBranches` fetches and
|
|
88
|
+
* rebases against the wrong lineage in the meantime.
|
|
89
|
+
*
|
|
90
|
+
* MUST match the shared repo's `canopycms.config.ts`'s `defaultBaseBranch`
|
|
91
|
+
* (both default to 'main' when unset) — the two are resolved by different
|
|
92
|
+
* processes (this stamps the worker's `.env`; the Lambda reads
|
|
93
|
+
* `config.defaultBaseBranch` at request time) with no automatic
|
|
94
|
+
* reconciliation between them. `infrastructure/lib/cms-stack.ts`, as
|
|
95
|
+
* scaffolded by `canopycms init-deploy aws`, derives this prop FROM that same
|
|
96
|
+
* config file at synth time so the two cannot drift for adopters who deploy
|
|
97
|
+
* through the generated stack; a hand-rolled stack must set this explicitly
|
|
98
|
+
* whenever `defaultBaseBranch` is anything other than 'main'.
|
|
99
|
+
*/
|
|
76
100
|
baseBranch?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Explicit settings-branch name, stamped into the worker's
|
|
103
|
+
* `CANOPYCMS_SETTINGS_BRANCH` environment variable (default: unset, in which
|
|
104
|
+
* case the worker falls through to the same computed name the Lambda uses,
|
|
105
|
+
* `canopycms-settings-<deploymentName>` — see `deploymentName` below).
|
|
106
|
+
*
|
|
107
|
+
* Only set this to mirror an adopter-configured `config.settingsBranch` in
|
|
108
|
+
* `canopycms.config.ts`. Leaving both unset (the default) is safe: Lambda and
|
|
109
|
+
* worker then resolve the SAME computed name independently, with nothing to
|
|
110
|
+
* keep in sync. But if the shared config sets `settingsBranch` explicitly
|
|
111
|
+
* and this prop is left unset, the worker keeps resolving the computed name
|
|
112
|
+
* while the Lambda's `getSettingsBranchName` short-circuits on
|
|
113
|
+
* `config.settingsBranch` — so the two own different branches. The PRIMARY
|
|
114
|
+
* settings-push path still reaches GitHub (its task payload carries the
|
|
115
|
+
* Lambda-resolved name), but the worker's per-cycle backstop push
|
|
116
|
+
* (`pushSettingsBranches`) targets the wrong branch and its "foreign settings
|
|
117
|
+
* branch" [SYNC-M3] warning misfires against the deployment's own branch.
|
|
118
|
+
* Validated at synth like `baseBranch` (see `assertValidGitBranchName`),
|
|
119
|
+
* since this is interpolated into a git ref and the worker's `.env` heredoc
|
|
120
|
+
* too. `infrastructure/lib/cms-stack.ts`, as scaffolded by
|
|
121
|
+
* `canopycms init-deploy aws`, derives this prop from the same
|
|
122
|
+
* `canopycms.config.ts` at synth time, so the two cannot drift for adopters
|
|
123
|
+
* who deploy through the generated stack.
|
|
124
|
+
*/
|
|
125
|
+
settingsBranch?: string;
|
|
77
126
|
/**
|
|
78
127
|
* Deployment name (default: 'prod'). Namespaces the settings branch
|
|
79
128
|
* (`canopycms-settings-{deploymentName}`) so this stack's CMS Lambda/worker
|