canopycms-cdk 0.0.66-int.83 → 0.0.66-int.84
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.
|
@@ -217,13 +217,17 @@ export declare class AssetSupport extends Construct {
|
|
|
217
217
|
/** Effective advisory upload-size cap (see `AssetSupportProps.maxUploadBytes`). */
|
|
218
218
|
readonly maxUploadBytes: number;
|
|
219
219
|
private readonly behaviors;
|
|
220
|
-
/** Distributions `attachTo` has already wired -- see that method's duplicate check. */
|
|
221
|
-
private readonly attachedDistributions;
|
|
222
220
|
constructor(scope: Construct, id: string, props: AssetSupportProps);
|
|
223
221
|
private buildBehaviors;
|
|
224
222
|
/**
|
|
225
223
|
* The two CloudFront behavior configs this system needs.
|
|
226
224
|
*
|
|
225
|
+
* If you use this rather than `attachTo` -- which now takes an `overrides`
|
|
226
|
+
* parameter, so needing per-behavior options is no longer a reason to fall
|
|
227
|
+
* back here -- you own the ordering, and you should assert it: read the
|
|
228
|
+
* SYNTHESIZED template's `CacheBehaviors` array index, not your own source
|
|
229
|
+
* object, since the property is about emitted order.
|
|
230
|
+
*
|
|
227
231
|
* Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s
|
|
228
232
|
* `assetSupport` prop, which attach these to a distribution in the only
|
|
229
233
|
* safe order automatically. This method exists as the escape hatch for a
|
|
@@ -250,6 +254,33 @@ export declare class AssetSupport extends Construct {
|
|
|
250
254
|
* 0x2A, `t` = 0x74), so alphabetizing the keys reproduces exactly this
|
|
251
255
|
* failure, with no synth or deploy error to catch it.
|
|
252
256
|
*
|
|
257
|
+
* `overrides` is merged into BOTH behaviors, and exists because without it
|
|
258
|
+
* this method is unusable by exactly the adopters who most need the ordering
|
|
259
|
+
* guarantee. A distribution that runs a viewer-request function on every
|
|
260
|
+
* behavior - tier basic-auth, most commonly - needs the asset behaviors to
|
|
261
|
+
* carry that same `functionAssociations`, or `/assets/*` is anonymously
|
|
262
|
+
* readable on an authenticated tier. Before this parameter existed such an
|
|
263
|
+
* adopter had to fall back to `assetBehaviors()` plus two hand-ordered
|
|
264
|
+
* `addBehavior` calls: the precise shape this method was added to eliminate,
|
|
265
|
+
* re-entered while believing ordering was handled upstream, which is worse
|
|
266
|
+
* than never having had the method. (`responseHeadersPolicy` is the same
|
|
267
|
+
* story for a repo with a shared security-headers policy.)
|
|
268
|
+
*
|
|
269
|
+
* Merged into both rather than per-behavior on purpose: applying one set to
|
|
270
|
+
* both is what preserves the ordering guarantee as the only thing this
|
|
271
|
+
* method decides. A caller who genuinely needs the two behaviors to differ
|
|
272
|
+
* has left this method's remit and should use `assetBehaviors()` - and keep
|
|
273
|
+
* their own ordering assertion.
|
|
274
|
+
*
|
|
275
|
+
* Typed `Partial<AddBehaviorOptions>` because that is exactly what
|
|
276
|
+
* `addBehavior(pattern, origin, behaviorOptions?)` accepts - `origin` is a
|
|
277
|
+
* POSITIONAL argument there, so `BehaviorOptions` (which is
|
|
278
|
+
* `AddBehaviorOptions` plus `origin`) would let a caller pass a key the call
|
|
279
|
+
* silently ignores. Measured, because the first version of this comment
|
|
280
|
+
* claimed the opposite: widening the parameter and passing an `origin`
|
|
281
|
+
* override changes nothing in the emitted template, so this narrowing
|
|
282
|
+
* prevents a confusing no-op rather than a broken origin group.
|
|
283
|
+
*
|
|
253
284
|
* Needs a concrete `cloudfront.Distribution` - `addBehavior` is an instance
|
|
254
285
|
* method on that class, not on `IDistribution` (what an imported/looked-up
|
|
255
286
|
* distribution reference gives you). For a distribution built entirely
|
|
@@ -258,7 +289,7 @@ export declare class AssetSupport extends Construct {
|
|
|
258
289
|
* directly instead and list `/assets/t/*` before `/assets/*` yourself - see
|
|
259
290
|
* `AssetCloudFrontBehaviors`'s doc comment.
|
|
260
291
|
*/
|
|
261
|
-
attachTo(distribution: cloudfront.Distribution): void;
|
|
292
|
+
attachTo(distribution: cloudfront.Distribution, overrides?: Partial<cloudfront.AddBehaviorOptions>): void;
|
|
262
293
|
/**
|
|
263
294
|
* Grant the CMS/editor principal (the CMS Lambda's role, typically) the
|
|
264
295
|
* exact permissions `S3AssetStore` (packages/canopycms/src/assets/store-s3.ts)
|
|
@@ -111,6 +111,24 @@ 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
|
+
* Distributions that already carry the asset behaviors, tracked at MODULE level
|
|
116
|
+
* rather than per instance.
|
|
117
|
+
*
|
|
118
|
+
* Per-instance (the first version of this) only caught the same `AssetSupport`
|
|
119
|
+
* attaching twice. Two `AssetSupport` constructs attaching to one distribution
|
|
120
|
+
* slipped through and synthesized
|
|
121
|
+
* ['/assets/t/*','/assets/*','/assets/t/*','/assets/*'] - the identical
|
|
122
|
+
* duplicate-path-pattern deploy failure the guard exists to convert into a
|
|
123
|
+
* synth error. There is no legitimate form of that: both instances attach the
|
|
124
|
+
* same two patterns, so the second is always wrong regardless of which
|
|
125
|
+
* construct owns it.
|
|
126
|
+
*
|
|
127
|
+
* A `WeakSet` keyed on the distribution object, so it holds no reference that
|
|
128
|
+
* would outlive the construct tree and cannot leak across CDK apps in a test
|
|
129
|
+
* process.
|
|
130
|
+
*/
|
|
131
|
+
const distributionsWithAssetBehaviors = new WeakSet();
|
|
114
132
|
/**
|
|
115
133
|
* The two CloudFront path patterns the asset system's behaviors are keyed
|
|
116
134
|
* under, derived from `PREFIXES` above (the single source of truth for these
|
|
@@ -156,8 +174,6 @@ export const ASSET_BEHAVIOR_SPREAD_MISTAKE_KEYS = ['assets', 'assetsTransform'];
|
|
|
156
174
|
export class AssetSupport extends Construct {
|
|
157
175
|
constructor(scope, id, props) {
|
|
158
176
|
super(scope, id);
|
|
159
|
-
/** Distributions `attachTo` has already wired -- see that method's duplicate check. */
|
|
160
|
-
this.attachedDistributions = new WeakSet();
|
|
161
177
|
// Fail closed before anything else: refuse to build a stack around a
|
|
162
178
|
// transform Lambda whose code asset was never verified to contain the
|
|
163
179
|
// linux/arm64 sharp binary. Without this, `pnpm test` (whose
|
|
@@ -346,6 +362,12 @@ export class AssetSupport extends Construct {
|
|
|
346
362
|
/**
|
|
347
363
|
* The two CloudFront behavior configs this system needs.
|
|
348
364
|
*
|
|
365
|
+
* If you use this rather than `attachTo` -- which now takes an `overrides`
|
|
366
|
+
* parameter, so needing per-behavior options is no longer a reason to fall
|
|
367
|
+
* back here -- you own the ordering, and you should assert it: read the
|
|
368
|
+
* SYNTHESIZED template's `CacheBehaviors` array index, not your own source
|
|
369
|
+
* object, since the property is about emitted order.
|
|
370
|
+
*
|
|
349
371
|
* Prefer `attachTo(distribution)` or `CanopyCmsDistribution`'s
|
|
350
372
|
* `assetSupport` prop, which attach these to a distribution in the only
|
|
351
373
|
* safe order automatically. This method exists as the escape hatch for a
|
|
@@ -374,6 +396,33 @@ export class AssetSupport extends Construct {
|
|
|
374
396
|
* 0x2A, `t` = 0x74), so alphabetizing the keys reproduces exactly this
|
|
375
397
|
* failure, with no synth or deploy error to catch it.
|
|
376
398
|
*
|
|
399
|
+
* `overrides` is merged into BOTH behaviors, and exists because without it
|
|
400
|
+
* this method is unusable by exactly the adopters who most need the ordering
|
|
401
|
+
* guarantee. A distribution that runs a viewer-request function on every
|
|
402
|
+
* behavior - tier basic-auth, most commonly - needs the asset behaviors to
|
|
403
|
+
* carry that same `functionAssociations`, or `/assets/*` is anonymously
|
|
404
|
+
* readable on an authenticated tier. Before this parameter existed such an
|
|
405
|
+
* adopter had to fall back to `assetBehaviors()` plus two hand-ordered
|
|
406
|
+
* `addBehavior` calls: the precise shape this method was added to eliminate,
|
|
407
|
+
* re-entered while believing ordering was handled upstream, which is worse
|
|
408
|
+
* than never having had the method. (`responseHeadersPolicy` is the same
|
|
409
|
+
* story for a repo with a shared security-headers policy.)
|
|
410
|
+
*
|
|
411
|
+
* Merged into both rather than per-behavior on purpose: applying one set to
|
|
412
|
+
* both is what preserves the ordering guarantee as the only thing this
|
|
413
|
+
* method decides. A caller who genuinely needs the two behaviors to differ
|
|
414
|
+
* has left this method's remit and should use `assetBehaviors()` - and keep
|
|
415
|
+
* their own ordering assertion.
|
|
416
|
+
*
|
|
417
|
+
* Typed `Partial<AddBehaviorOptions>` because that is exactly what
|
|
418
|
+
* `addBehavior(pattern, origin, behaviorOptions?)` accepts - `origin` is a
|
|
419
|
+
* POSITIONAL argument there, so `BehaviorOptions` (which is
|
|
420
|
+
* `AddBehaviorOptions` plus `origin`) would let a caller pass a key the call
|
|
421
|
+
* silently ignores. Measured, because the first version of this comment
|
|
422
|
+
* claimed the opposite: widening the parameter and passing an `origin`
|
|
423
|
+
* override changes nothing in the emitted template, so this narrowing
|
|
424
|
+
* prevents a confusing no-op rather than a broken origin group.
|
|
425
|
+
*
|
|
377
426
|
* Needs a concrete `cloudfront.Distribution` - `addBehavior` is an instance
|
|
378
427
|
* method on that class, not on `IDistribution` (what an imported/looked-up
|
|
379
428
|
* distribution reference gives you). For a distribution built entirely
|
|
@@ -382,7 +431,7 @@ export class AssetSupport extends Construct {
|
|
|
382
431
|
* directly instead and list `/assets/t/*` before `/assets/*` yourself - see
|
|
383
432
|
* `AssetCloudFrontBehaviors`'s doc comment.
|
|
384
433
|
*/
|
|
385
|
-
attachTo(distribution) {
|
|
434
|
+
attachTo(distribution, overrides) {
|
|
386
435
|
// `addBehavior` does not dedupe, and these calls bypass
|
|
387
436
|
// `CanopyCmsDistribution`'s own synth-time guard because they run after
|
|
388
437
|
// that distribution is constructed. So attaching twice -- passing the
|
|
@@ -391,18 +440,40 @@ export class AssetSupport extends Construct {
|
|
|
391
440
|
// ['/assets/t/*','/assets/*','/assets/t/*','/assets/*'] and fails at
|
|
392
441
|
// deploy time when CloudFront rejects the duplicate patterns. Refuse it
|
|
393
442
|
// here instead, where the message can say which two routes collided.
|
|
394
|
-
if (
|
|
443
|
+
if (distributionsWithAssetBehaviors.has(distribution)) {
|
|
395
444
|
throw new Error(`AssetSupport: attachTo() was already called for this distribution. Each pattern ` +
|
|
396
445
|
`would be attached twice and CloudFront rejects duplicate path patterns at deploy ` +
|
|
397
446
|
`time. This usually means the distribution was given CanopyCmsDistribution's ` +
|
|
398
447
|
`\`assetSupport\` prop (which calls attachTo for you) as well as an explicit ` +
|
|
399
448
|
`attachTo() call -- keep one.`);
|
|
400
449
|
}
|
|
401
|
-
|
|
450
|
+
distributionsWithAssetBehaviors.add(distribution);
|
|
451
|
+
// Drop explicitly-`undefined` keys before merging. A spread copies own
|
|
452
|
+
// enumerable keys INCLUDING ones whose value is undefined, so
|
|
453
|
+
// `{ ...transformRest, ...{ cachePolicy: undefined } }` deletes the
|
|
454
|
+
// construct's choice and lets CDK substitute its own default - which is a
|
|
455
|
+
// DIFFERENT default. Measured: `cachePolicy: undefined` swaps this
|
|
456
|
+
// behavior's custom policy (TRANSFORM_CACHE_MIN_TTL = 0, which exists
|
|
457
|
+
// solely to stop the oversized-output redirect loop documented above) for
|
|
458
|
+
// the managed CACHING_OPTIMIZED and its 1-second min TTL; and
|
|
459
|
+
// `viewerProtocolPolicy: undefined` downgrades BOTH behaviors from
|
|
460
|
+
// redirect-to-https to allow-all, serving assets over plain HTTP.
|
|
461
|
+
//
|
|
462
|
+
// This is not a contrived input - it is what forwarding optional props
|
|
463
|
+
// produces: `attachTo(dist, { cachePolicy: props.maybePolicy })` with the
|
|
464
|
+
// prop unset. It typechecks (every AddBehaviorOptions field is already
|
|
465
|
+
// optional), synthesizes and deploys clean.
|
|
466
|
+
const definedOverrides = Object.fromEntries(Object.entries(overrides ?? {}).filter(([, value]) => value !== undefined));
|
|
402
467
|
const { origin: transformOrigin, ...transformRest } = this.behaviors.assetsTransform;
|
|
403
468
|
const { origin: assetsOrigin, ...assetsRest } = this.behaviors.assets;
|
|
404
|
-
distribution.addBehavior(ASSETS_TRANSFORM_PATH_PATTERN, transformOrigin,
|
|
405
|
-
|
|
469
|
+
distribution.addBehavior(ASSETS_TRANSFORM_PATH_PATTERN, transformOrigin, {
|
|
470
|
+
...transformRest,
|
|
471
|
+
...definedOverrides,
|
|
472
|
+
});
|
|
473
|
+
distribution.addBehavior(ASSETS_PATH_PATTERN, assetsOrigin, {
|
|
474
|
+
...assetsRest,
|
|
475
|
+
...definedOverrides,
|
|
476
|
+
});
|
|
406
477
|
}
|
|
407
478
|
/**
|
|
408
479
|
* Grant the CMS/editor principal (the CMS Lambda's role, typically) the
|
|
@@ -66,9 +66,15 @@ export interface CanopyCmsDistributionProps {
|
|
|
66
66
|
* Attach `AssetSupport`'s CloudFront behaviors (`/assets/*` and
|
|
67
67
|
* `/assets/t/*`) to the distribution this construct builds, in the only
|
|
68
68
|
* safe order - see `AssetSupport.attachTo()`'s doc comment for why the
|
|
69
|
-
* order matters.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* order matters. Calls `assetSupport.attachTo(distribution)` for you right
|
|
70
|
+
* after construction, with nothing to forget.
|
|
71
|
+
*
|
|
72
|
+
* NOT fully equivalent to calling `attachTo` yourself: this path cannot pass
|
|
73
|
+
* that method's `overrides` argument. If the asset behaviors need
|
|
74
|
+
* per-behavior options - a viewer-request function for tier auth being the
|
|
75
|
+
* motivating case, since without it `/assets/*` is anonymously readable on
|
|
76
|
+
* an authenticated tier - drop this prop and call
|
|
77
|
+
* `assetSupport.attachTo(this.distribution, { ... })` after construction.
|
|
72
78
|
*
|
|
73
79
|
* Prefer this over passing `assetSupport.assetBehaviors()` through
|
|
74
80
|
* `additionalBehaviors` by hand - that stays available as an escape hatch
|
|
@@ -20,7 +20,21 @@ import { ASSETS_PATH_PATTERN, ASSETS_TRANSFORM_PATH_PATTERN, ASSET_BEHAVIOR_SPRE
|
|
|
20
20
|
function mergeBehaviors(defaults, caller, attachingAssetSupport) {
|
|
21
21
|
if (!caller)
|
|
22
22
|
return defaults;
|
|
23
|
-
|
|
23
|
+
// Compare NORMALIZED keys here too. CloudFront treats a leading '/' as
|
|
24
|
+
// optional, so a caller passing `'_next/static/*'` did not displace this
|
|
25
|
+
// construct's own `'/_next/static/*'` default and both were emitted -- two
|
|
26
|
+
// behaviors CloudFront reads as the same pattern, which it rejects at
|
|
27
|
+
// deploy. Same reason the hazard checks below normalize.
|
|
28
|
+
const callerPatterns = new Set(Object.keys(caller).map(normalizePathPattern));
|
|
29
|
+
const uncollided = Object.fromEntries(Object.entries(defaults).filter(([pattern]) => !callerPatterns.has(normalizePathPattern(pattern))));
|
|
30
|
+
const normalizedCallerKeys = Object.keys(caller).map(normalizePathPattern);
|
|
31
|
+
const duplicateCallerPattern = normalizedCallerKeys.find((pattern, i) => normalizedCallerKeys.indexOf(pattern) !== i);
|
|
32
|
+
if (duplicateCallerPattern !== undefined) {
|
|
33
|
+
throw new Error(`CanopyCmsDistribution: additionalBehaviors has two keys that CloudFront reads as the ` +
|
|
34
|
+
`same path pattern (${JSON.stringify(duplicateCallerPattern)} with and without its ` +
|
|
35
|
+
`leading '/'). CloudFront rejects duplicate path patterns at deploy time. Keep one ` +
|
|
36
|
+
`spelling.`);
|
|
37
|
+
}
|
|
24
38
|
const merged = { ...uncollided, ...caller };
|
|
25
39
|
assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport);
|
|
26
40
|
return merged;
|
|
@@ -68,6 +82,7 @@ function mergeBehaviors(defaults, caller, attachingAssetSupport) {
|
|
|
68
82
|
function normalizePathPattern(pattern) {
|
|
69
83
|
return pattern.startsWith('/') ? pattern.slice(1) : pattern;
|
|
70
84
|
}
|
|
85
|
+
/** See the three-hazard list documented above `normalizePathPattern`. */
|
|
71
86
|
function assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport) {
|
|
72
87
|
const keys = Object.keys(merged).map(normalizePathPattern);
|
|
73
88
|
const assetsPattern = normalizePathPattern(ASSETS_PATH_PATTERN);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "canopycms-cdk",
|
|
3
|
-
"version": "0.0.66-int.
|
|
3
|
+
"version": "0.0.66-int.84",
|
|
4
4
|
"description": "AWS CDK constructs and EC2 worker for CanopyCMS deployment",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"aws-cdk-lib": "^2.260.0",
|
|
32
32
|
"constructs": "^10.0.0",
|
|
33
|
-
"canopycms": "0.0.66-int.
|
|
33
|
+
"canopycms": "0.0.66-int.84"
|
|
34
34
|
},
|
|
35
35
|
"peerDependenciesMeta": {
|
|
36
36
|
"aws-cdk-lib": {
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"tsx": "^4.19.0",
|
|
55
55
|
"typescript": "^5.6.3",
|
|
56
56
|
"vitest": "^4.1.0",
|
|
57
|
-
"canopycms": "0.0.66-int.
|
|
58
|
-
"canopycms-auth-clerk": "0.0.66-int.
|
|
57
|
+
"canopycms": "0.0.66-int.84",
|
|
58
|
+
"canopycms-auth-clerk": "0.0.66-int.84"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "rm -rf dist && tsc && node ../../scripts/add-js-extensions.mjs dist",
|