canopycms-cdk 0.0.67-int.90 → 0.0.67

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.
Files changed (48) hide show
  1. package/dist/constructs/asset-support.d.ts +137 -269
  2. package/dist/constructs/asset-support.js +275 -455
  3. package/dist/constructs/cms-distribution.d.ts +23 -40
  4. package/dist/constructs/cms-distribution.js +61 -88
  5. package/dist/constructs/cms-service.d.ts +38 -60
  6. package/dist/constructs/cms-service.js +246 -390
  7. package/dist/constructs/lambda-execution-role.d.ts +23 -39
  8. package/dist/constructs/lambda-execution-role.js +37 -60
  9. package/dist/index.js +0 -2
  10. package/dist/worker.d.ts +0 -2
  11. package/dist/worker.js +0 -2
  12. package/lambda/asset-transform/dist/handler.js +33 -49
  13. package/lambda/asset-transform/dist/node_modules/.package-lock.json +38 -38
  14. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/README.md +1 -1
  15. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +2 -2
  16. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/{libvips-cpp.so.8.18.6 → libvips-cpp.so.8.18.7} +0 -0
  17. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/package.json +2 -2
  18. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/versions.json +11 -11
  19. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/index.cjs +1 -1
  20. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/lib/{sharp-linux-arm64-0.35.4.node → sharp-linux-arm64-0.35.5.node} +0 -0
  21. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/package.json +2 -2
  22. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/index.cjs +1 -1
  23. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.5.node.js +1 -0
  24. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/{sharp-wasm32-0.35.4.node.wasm → sharp-wasm32-0.35.5.node.wasm} +0 -0
  25. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/package.json +1 -1
  26. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/versions.json +7 -7
  27. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.cts +2 -2
  28. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.mts +3 -3
  29. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.cjs +8 -2
  30. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.mjs +8 -2
  31. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.cjs +2 -2
  32. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.mjs +2 -2
  33. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.cjs +1 -0
  34. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.mjs +1 -0
  35. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.cjs +14 -14
  36. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.mjs +14 -14
  37. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.cjs +3 -3
  38. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.mjs +3 -3
  39. package/lambda/asset-transform/dist/node_modules/sharp/lib/index.d.ts +2 -2
  40. package/lambda/asset-transform/dist/node_modules/sharp/package.json +35 -35
  41. package/lambda/asset-transform/dist/node_modules/sharp/src/binding.gyp +1 -1
  42. package/lambda/asset-transform/dist/node_modules/sharp/src/common.cc +4 -4
  43. package/lambda/asset-transform/dist/node_modules/sharp/src/common.h +2 -2
  44. package/lambda/asset-transform/dist/node_modules/sharp/src/pipeline.cc +31 -6
  45. package/lambda/asset-transform/dist/node_modules/sharp/src/utilities.cc +1 -1
  46. package/package.json +4 -4
  47. package/worker/dist/index.js +251 -364
  48. package/lambda/asset-transform/dist/node_modules/@img/sharp-wasm32/lib/sharp-wasm32-0.35.4.node.js +0 -1
@@ -20,30 +20,26 @@ export interface CanopyCmsDistributionProps {
20
20
  * timeout, so the two cannot drift.
21
21
  *
22
22
  * Left unset on the origin, CloudFront applies its service default of **30
23
- * seconds** — which silently caps a 60s Lambda at half its budget. Every
24
- * request landing in the 30-60s band is answered 504 at the edge while the
25
- * invocation continues to completion behind it: server-side success,
26
- * viewer-facing failure, and nothing in either log explaining the other
27
- * half. First-touch branch provisioning does a full `git clone` onto EFS
28
- * inside the request, so this is a real path.
23
+ * seconds**, silently capping a 60s Lambda at half its budget: every request
24
+ * in the 30-60s band is answered 504 at the edge while the invocation runs to
25
+ * completion behind it, with nothing in either log explaining the other half.
26
+ * First-touch branch provisioning does a full `git clone` onto EFS inside the
27
+ * request, so this is a real path.
29
28
  *
30
29
  * Capped at 60s: CloudFront rejects more without a service-quota increase.
31
30
  */
32
31
  originReadTimeout?: Duration;
33
32
  /**
34
- * Extra CloudFront behaviors, merged with this construct's own.
35
- *
36
- * The reason this exists: `AssetSupport.assetBehaviors()` returns the two
37
- * behaviors a media-enabled deployment needs (`/assets/*` and
38
- * `/assets/t/*`), and without a way to pass them in there was no route to
39
- * attach them to the distribution the scaffold generates — making the
40
- * scaffold's own "uncomment to enable media" path a dead end.
33
+ * Extra CloudFront behaviors, merged with this construct's own. This is how
34
+ * `AssetSupport.assetBehaviors()`'s two behaviors (`/assets/*` and
35
+ * `/assets/t/*`) reach the distribution the scaffold generates, when they are
36
+ * not attached through the `assetSupport` prop.
41
37
  *
42
38
  * ORDER MATTERS. CloudFront matches path patterns in the order given, so a
43
39
  * more specific pattern must be listed before a more general one that also
44
40
  * matches: `/assets/t/*` before `/assets/*`, or every transform request is
45
- * served by the static S3-only behavior and never fails over to the
46
- * transform Lambda.
41
+ * served by the static S3-only behavior and never fails over to the transform
42
+ * Lambda.
47
43
  *
48
44
  * Keys here override this construct's own behaviors on collision, which is
49
45
  * deliberate — the caller is more specific than the default — and an
@@ -52,14 +48,8 @@ export interface CanopyCmsDistributionProps {
52
48
  * `mergeBehaviors`.)
53
49
  *
54
50
  * 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.
51
+ * here by hand. If you do it by hand, `mergeBehaviors` throws at synth on any
52
+ * of the three hazards listed above `assertNoAssetBehaviorOrderingHazards`.
63
53
  */
64
54
  additionalBehaviors?: Record<string, cloudfront.BehaviorOptions>;
65
55
  /**
@@ -72,20 +62,14 @@ export interface CanopyCmsDistributionProps {
72
62
  * If the asset behaviors need per-behavior options - a viewer-request
73
63
  * function for tier auth being the motivating case, since without it
74
64
  * `/assets/*` is anonymously readable on an authenticated tier - pass
75
- * `assetBehaviorOverrides` alongside this prop. That requirement used to mean
76
- * dropping this prop and hand-calling `attachTo` after construction, which
77
- * sent exactly the adopter who most needs the ordering guarantee back to the
78
- * manual path this prop exists to replace.
65
+ * `assetBehaviorOverrides` alongside this prop rather than dropping it and
66
+ * hand-calling `attachTo` after construction.
79
67
  *
80
68
  * Prefer this over passing `assetSupport.assetBehaviors()` through
81
- * `additionalBehaviors` by hand - that stays available as an escape hatch
82
- * (e.g. for behaviors that are not from `AssetSupport` at all), and this
83
- * construct's synth-time guard (`mergeBehaviors`) still checks it for the
84
- * three ways that manual wiring is known to go wrong: see
85
- * `additionalBehaviors`'s own doc comment.
86
- *
87
- * No ordering logic lives in this construct beyond calling this method -
88
- * ordering is encoded exactly once, in `AssetSupport.attachTo()` itself.
69
+ * `additionalBehaviors` by hand; that stays available as an escape hatch, and
70
+ * `mergeBehaviors` still guards it. No ordering logic lives in this construct
71
+ * beyond calling this method - ordering is encoded exactly once, in
72
+ * `AssetSupport.attachTo()` itself.
89
73
  *
90
74
  * @default - no asset behaviors are attached
91
75
  */
@@ -102,11 +86,10 @@ export interface CanopyCmsDistributionProps {
102
86
  *
103
87
  * Useless without `assetSupport`, and silently so - there would be no
104
88
  * behaviors to merge it into - so that combination throws from this
105
- * construct's CONSTRUCTOR rather than being ignored. (A constructor throw,
106
- * not an `addValidation`: nothing later can make the combination valid. An
107
- * adopter sharing one props object across tiers, with `assetSupport` present
108
- * on only some of them, should make the whole prop conditional rather than
109
- * the overrides object.)
89
+ * construct's CONSTRUCTOR rather than being ignored; nothing later can make
90
+ * it valid, so an `addValidation` would be too late. An adopter sharing one
91
+ * props object across tiers, with `assetSupport` present on only some, should
92
+ * make the whole prop conditional rather than the overrides object.
110
93
  *
111
94
  * @default - the asset behaviors are attached with no overrides
112
95
  */
@@ -9,13 +9,11 @@ import { ASSETS_PATH_PATTERN, ASSETS_TRANSFORM_PATH_PATTERN, ASSET_BEHAVIOR_SPRE
9
9
  * A plain `{ ...defaults, ...caller }` gets the values right and the ORDER
10
10
  * wrong: JavaScript keeps an overridden key at its first-insertion index, so a
11
11
  * caller who overrides `/_next/static/*` finds it pinned ahead of every other
12
- * pattern they passed, regardless of the order they wrote. Since CloudFront
13
- * matches path patterns in order, that silently makes a more specific pattern
14
- * listed after it unreachable -- exactly the failure the prop's doc comment
15
- * warns callers to avoid, introduced by the merge itself.
16
- *
17
- * Dropping collided defaults FIRST means the caller's own object contributes
18
- * its keys in its own order.
12
+ * pattern they passed. CloudFront matches path patterns in order, so that
13
+ * silently makes a more specific pattern listed after it unreachable - the
14
+ * failure the prop's doc comment warns about, introduced by the merge itself.
15
+ * Dropping collided defaults FIRST lets the caller's own object contribute its
16
+ * keys in its own order.
19
17
  */
20
18
  function mergeBehaviors(defaults, caller, attachingAssetSupport) {
21
19
  if (!caller)
@@ -40,49 +38,44 @@ function mergeBehaviors(defaults, caller, attachingAssetSupport) {
40
38
  return merged;
41
39
  }
42
40
  /**
43
- * Guards against the three AssetSupport CloudFront-behavior hazards a manually
44
- * assembled `additionalBehaviors` can introduce (see
45
- * `AssetSupport.attachTo()`'s doc comment for the full mechanism):
46
- *
47
- * 1. The literal keys `assets`/`assetsTransform` present in `merged` mean
48
- * `assetSupport.assetBehaviors()`'s return value was spread directly into
49
- * `additionalBehaviors` (a `Record<pathPattern, BehaviorOptions>`) instead
50
- * of being attached via `assetSupport.attachTo(distribution)` or this
51
- * construct's `assetSupport` prop. This type-checks and deploys clean,
52
- * synthesizing two behaviors matching the literal path patterns `assets`
53
- * and `assetsTransform`, which nothing ever requests.
54
- * 2. `/assets/*` listed before `/assets/t/*` means CloudFront's first-match-
55
- * wins ordering serves every transform request off the broader, S3-only
56
- * `/assets/*` behavior, permanently 403ing any derivative that has not
57
- * already been computed (an OAC-signed S3 miss reports 403).
58
- * 3. Either asset pattern present in `merged` WHILE the `assetSupport` prop is
59
- * also passed means both wiring routes are active at once, so each pattern
60
- * is attached twice. This is the migration mistake specifically - keeping a
61
- * hand-wired block while adopting the prop - and hazard 2 cannot catch it,
62
- * because the hand-written order is usually the correct one.
63
- *
64
- * `Object.keys` insertion order is spec-guaranteed for string keys like
65
- * these (none are integer-like array-index strings), so comparing index
66
- * order here is sound.
67
- *
68
- * SCOPE LIMIT: this protects only callers going through
69
- * `CanopyCmsDistribution`'s own `additionalBehaviors` merge. It cannot help a
70
- * bespoke `new cloudfront.Distribution(...)` assembled elsewhere -
71
- * `AssetSupport.attachTo()` is the answer there.
72
- */
73
- /**
74
- * CloudFront treats the leading `/` on a path pattern as optional -- `assets/*`
41
+ * CloudFront treats the leading `/` on a path pattern as optional - `assets/*`
75
42
  * and `/assets/*` match exactly the same requests, and AWS's own console and
76
- * docs frequently show the slash-less spelling. The checks below therefore
77
- * compare NORMALIZED keys: without this, writing `{ 'assets/*': ..., 'assets/t/*': ... }`
78
- * walked past all three hazards and synthesized the broad-pattern-first order
79
- * this guard exists to refuse -- silently, which is the worst of the failure
80
- * modes here.
43
+ * docs frequently show the slash-less spelling. Every check here compares
44
+ * NORMALIZED keys: without this, `{ 'assets/*': ..., 'assets/t/*': ... }` walks
45
+ * past all three hazards below and synthesizes the broad-pattern-first order
46
+ * they exist to refuse - silently, the worst of the failure modes here.
81
47
  */
82
48
  function normalizePathPattern(pattern) {
83
49
  return pattern.startsWith('/') ? pattern.slice(1) : pattern;
84
50
  }
85
- /** See the three-hazard list documented above `normalizePathPattern`. */
51
+ /**
52
+ * The three AssetSupport CloudFront-behavior hazards a manually assembled
53
+ * `additionalBehaviors` can introduce (see `AssetSupport.attachTo()` for the
54
+ * mechanism):
55
+ *
56
+ * 1. The literal keys `assets`/`assetsTransform` in `merged` mean
57
+ * `assetBehaviors()`'s return value was spread straight into
58
+ * `additionalBehaviors` (a `Record<pathPattern, BehaviorOptions>`) instead of
59
+ * attached via `attachTo(distribution)` or the `assetSupport` prop. That
60
+ * type-checks and deploys clean, synthesizing two behaviors matching the
61
+ * literal patterns `assets` and `assetsTransform`, which nothing requests.
62
+ * 2. `/assets/*` listed before `/assets/t/*`: CloudFront's first-match-wins
63
+ * ordering serves every transform request off the broader, S3-only
64
+ * `/assets/*` behavior, permanently 403ing any derivative not already
65
+ * computed (an OAC-signed S3 miss reports 403).
66
+ * 3. Either asset pattern in `merged` WHILE the `assetSupport` prop is also
67
+ * passed: both wiring routes are active at once, so each pattern is attached
68
+ * twice. Hazard 2 cannot catch it, because the hand-written order is usually
69
+ * the correct one.
70
+ *
71
+ * `Object.keys` insertion order is spec-guaranteed for string keys like these
72
+ * (none are integer-like array-index strings), so comparing index order is
73
+ * sound.
74
+ *
75
+ * SCOPE LIMIT: this protects only callers going through this construct's own
76
+ * `additionalBehaviors` merge, not a bespoke `new cloudfront.Distribution(...)`
77
+ * assembled elsewhere - `AssetSupport.attachTo()` is the answer there.
78
+ */
86
79
  function assertNoAssetBehaviorOrderingHazards(merged, attachingAssetSupport) {
87
80
  const keys = Object.keys(merged).map(normalizePathPattern);
88
81
  const assetsPattern = normalizePathPattern(ASSETS_PATH_PATTERN);
@@ -163,27 +156,19 @@ export class CanopyCmsDistribution extends Construct {
163
156
  'a viewer-request function for tier auth. Pass `assetSupport` as well, or drop the ' +
164
157
  'overrides.');
165
158
  }
166
- // ========================================================================
167
- // DNS — Hosted Zone lookup
168
- // ========================================================================
169
159
  const hostedZone = props.hostedZone ??
170
160
  route53.HostedZone.fromLookup(this, 'Zone', {
171
161
  domainName: props.hostedZoneDomain,
172
162
  });
173
- // ========================================================================
174
- // ACM Certificate
175
- // ========================================================================
176
163
  // CloudFront requires its ACM certificate to live in us-east-1, and this
177
- // construct creates one in the STACK's region. Nothing in the construct,
178
- // the scaffold, or docs/deploying-to-aws.md said so -- the scaffold merely
179
- // asks for an AWS_REGION variable with us-east-1 as an example -- so an
180
- // adopter elsewhere got an opaque region error and had to research both
181
- // workarounds themselves. Fail with the answer instead.
164
+ // construct creates one in the STACK's region - so fail here with both
165
+ // workarounds rather than leave an adopter elsewhere with an opaque region
166
+ // error at deploy.
182
167
  //
183
168
  // Only when the region is actually known at synth: a region-agnostic stack
184
169
  // resolves to a token, and rejecting that would break `cdk synth` for
185
- // everyone (the token is not us-east-1 as a string). Those deploys are
186
- // rejected by CloudFront at deploy time as before.
170
+ // everyone (the token is not us-east-1 as a string). Those deploys stay
171
+ // CloudFront's to reject at deploy time.
187
172
  const stackRegion = Stack.of(this).region;
188
173
  if (!props.certificate && !Token.isUnresolved(stackRegion) && stackRegion !== 'us-east-1') {
189
174
  throw new Error(`CanopyCmsDistribution: CloudFront requires its ACM certificate in us-east-1, but this ` +
@@ -197,9 +182,6 @@ export class CanopyCmsDistribution extends Construct {
197
182
  domainName: props.domainName,
198
183
  validation: acm.CertificateValidation.fromDns(hostedZone),
199
184
  });
200
- // ========================================================================
201
- // CloudFront Distribution
202
- // ========================================================================
203
185
  // Origin: Lambda Function URL secured with Origin Access Control (OAC).
204
186
  // CloudFront signs each origin request with SigV4 so the AWS_IAM-protected
205
187
  // Function URL only accepts traffic from this distribution — direct hits to
@@ -221,18 +203,16 @@ export class CanopyCmsDistribution extends Construct {
221
203
  const origin = origins.FunctionUrlOrigin.withOriginAccessControl(props.functionUrl, {
222
204
  readTimeout,
223
205
  });
224
- // Cache policy for API/editor routes: AWS's managed CACHING_DISABLED
225
- // policy. Deploy-proven (deploy-test epic, 2026-07-23): CloudFront
226
- // rejects ANY non-none cache-key setting on a caching-disabled policy -
227
- // Authorization in a header allowlist (aws/aws-cdk#16977) but also
228
- // cookieBehavior/queryStringBehavior `all()` ("The parameter
229
- // CookieBehavior is invalid for policy with caching disabled"). With
230
- // TTL 0 the cache key is meaningless anyway; the origin still receives
231
- // the full viewer request (headers/cookies/query string, minus Host -
232
- // forwarding Host would break the OAC-signed Function URL) via the
233
- // ALL_VIEWER_EXCEPT_HOST_HEADER origin request policy on the behavior.
206
+ // API/editor routes take AWS's managed CACHING_DISABLED policy as it is:
207
+ // CloudFront rejects ANY non-none cache-key setting on a caching-disabled
208
+ // policy - `Authorization` in a header allowlist, and equally
209
+ // cookieBehavior/queryStringBehavior `all()` ("The parameter CookieBehavior
210
+ // is invalid for policy with caching disabled"). With TTL 0 the cache key is
211
+ // meaningless anyway, and the origin still receives the full viewer request
212
+ // (headers/cookies/query string, minus Host - forwarding Host would break
213
+ // the OAC-signed Function URL) via the ALL_VIEWER_EXCEPT_HOST_HEADER origin
214
+ // request policy on the behavior.
234
215
  const noCachePolicy = cloudfront.CachePolicy.CACHING_DISABLED;
235
- // Cache policy for static assets
236
216
  const staticCachePolicy = new cloudfront.CachePolicy(this, 'StaticCachePolicy', {
237
217
  defaultTtl: Duration.days(365),
238
218
  maxTtl: Duration.days(365),
@@ -243,15 +223,15 @@ export class CanopyCmsDistribution extends Construct {
243
223
  });
244
224
  // CloudFront gives the origin the Function URL's own Host (forwarding the
245
225
  // viewer Host would break the OAC SigV4 signature), and Lambda Web Adapter
246
- // forwards no `x-forwarded-*` headers of its own - so without this
247
- // function, Clerk/Next derive sign-in redirect URLs from the IAM-authed
248
- // Function URL host instead of the public domain, and the redirect 403s
249
- // (direct Function URL hits are rejected). Deploy-proven (deploy-test
250
- // epic, 2026-07-23): x-forwarded-proto is on CloudFront Functions'
251
- // DISALLOWED header list - setting it fails every request with 502
252
- // FunctionValidationError. Only x-forwarded-host is set; proto is
253
- // unambiguous anyway (viewer-facing CloudFront is HTTPS-only via
254
- // REDIRECT_TO_HTTPS).
226
+ // forwards no `x-forwarded-*` headers of its own - so without this function,
227
+ // Clerk/Next derive sign-in redirect URLs from the IAM-authed Function URL
228
+ // host instead of the public domain, and the redirect 403s (direct Function
229
+ // URL hits are rejected).
230
+ //
231
+ // ONLY x-forwarded-host: x-forwarded-proto is on CloudFront Functions'
232
+ // DISALLOWED header list and setting it fails every request with 502
233
+ // FunctionValidationError. Proto is unambiguous anyway - viewer-facing
234
+ // CloudFront is HTTPS-only via REDIRECT_TO_HTTPS.
255
235
  const forwardedHostFunction = new cloudfront.Function(this, 'ForwardedHostFunction', {
256
236
  code: cloudfront.FunctionCode.fromInline([
257
237
  'function handler(event) {',
@@ -291,14 +271,7 @@ export class CanopyCmsDistribution extends Construct {
291
271
  // logic lives exactly once, inside attachTo() itself. Callers who instead
292
272
  // pass `assetSupport.assetBehaviors()` through `additionalBehaviors` by
293
273
  // hand are covered by `mergeBehaviors`'s synth-time guard above instead.
294
- //
295
- // `assetBehaviorOverrides` is forwarded so that needing per-behavior
296
- // options is not a reason to leave this prop - and so the ordering
297
- // guarantee survives the tier-auth case that most needs it.
298
274
  props.assetSupport?.attachTo(this.distribution, props.assetBehaviorOverrides);
299
- // ========================================================================
300
- // DNS Records
301
- // ========================================================================
302
275
  new route53.ARecord(this, 'ARecord', {
303
276
  zone: hostedZone,
304
277
  recordName: props.domainName,
@@ -130,17 +130,14 @@ export interface CanopyCmsServiceProps {
130
130
  /**
131
131
  * Secrets Manager ARN for the App's PEM private key.
132
132
  *
133
- * **ARN-only: there is deliberately no plaintext prop for this key**, unlike
134
- * every other credential the construct knows about, and the reason is
135
- * mechanical rather than a matter of taste. Every value the construct puts in
136
- * the worker's environment is written into a `.env` file that systemd reads
137
- * as `EnvironmentFile=`, where a newline starts a new variable — so
138
- * `assertEnvSafe` refuses one, and a PEM is inherently multi-line. A
139
- * plaintext key could not be delivered to the worker intact by this path at
140
- * all. Passing the PEM itself here is caught by name at synth (see
141
- * `assertNotInlinePrivateKey`, and the note there about the generated stack
142
- * reaching CDK's own ARN complaint first) rather than surfacing as a puzzling
143
- * "an ARN must not contain a newline".
133
+ * **ARN-only: there is deliberately no plaintext prop for this key**, and the
134
+ * reason is mechanical. Every value the construct puts in the worker's
135
+ * environment is written into a `.env` file systemd reads as
136
+ * `EnvironmentFile=`, where a newline starts a new variable, so `assertEnvSafe`
137
+ * refuses one and a PEM is inherently multi-line: a plaintext key could not be
138
+ * delivered to the worker intact by this path at all. Passing the PEM itself
139
+ * here is caught by name at synth (`assertNotInlinePrivateKey`) rather than
140
+ * surfacing as a puzzling "an ARN must not contain a newline".
144
141
  *
145
142
  * The ARN is unioned into the worker's IAM policy alongside the other secret
146
143
  * ARN props; you do not need to repeat it in `secretsArns`.
@@ -153,10 +150,7 @@ export interface CanopyCmsServiceProps {
153
150
  * Stamped into the worker's
154
151
  * `CANOPYCMS_GITHUB_APP_PRIVATE_KEY_SECRET_JSON_FIELD`. See
155
152
  * `githubTokenSecretJsonField` above — same mechanism, same non-relationship
156
- * to the ECS `:KEY::` ARN suffix. This is the case that motivated the JSON
157
- * field support in the first place: an App private key is exactly the kind of
158
- * material an organisation keeps inside one credential document per
159
- * environment.
153
+ * to the ECS `:KEY::` ARN suffix.
160
154
  *
161
155
  * A PEM stored as a JSON string value carries its newlines as `\n` escapes,
162
156
  * which `JSON.parse` turns back into real newlines — and the worker
@@ -190,25 +184,20 @@ export interface CanopyCmsServiceProps {
190
184
  *
191
185
  * Interpolated straight into a git ref (`refs/heads/{baseBranch}`) and into
192
186
  * the worker's `.env` heredoc, so an invalid value is rejected at synth (see
193
- * `assertValidGitBranchName` above) instead of deploying an instance that
194
- * boots successfully and then fails. And fail it will, permanently:
195
- * `verifyBaseBranchExists`
196
- * (packages/canopycms/src/worker/cms-worker.ts) throws when the named branch
197
- * does not exist in the cloned `remote.git`, `start()`'s catch records the
198
- * fatal error, `worker/index.ts` exits 1, and systemd's `Restart=always`
199
- * repeats that forever — there is no working worker at all until the value
200
- * is fixed and the instance replaced, and `rebaseActiveBranches` fetches and
201
- * rebases against the wrong lineage in the meantime.
187
+ * `assertValidGitBranchName` above). A branch that git accepts but the repo
188
+ * does not have fails permanently at boot instead: `verifyBaseBranchExists`
189
+ * (packages/canopycms/src/worker/cms-worker.ts) throws, `worker/index.ts`
190
+ * exits 1, systemd's `Restart=always` repeats that forever, and
191
+ * `rebaseActiveBranches` rebases against the wrong lineage in the meantime.
202
192
  *
203
193
  * MUST match the shared repo's `canopycms.config.ts`'s `defaultBaseBranch`
204
194
  * (both default to 'main' when unset) — the two are resolved by different
205
195
  * processes (this stamps the worker's `.env`; the Lambda reads
206
- * `config.defaultBaseBranch` at request time) with no automatic
207
- * reconciliation between them. `infrastructure/lib/cms-stack.ts`, as
208
- * scaffolded by `canopycms init-deploy aws`, derives this prop FROM that same
209
- * config file at synth time so the two cannot drift for adopters who deploy
210
- * through the generated stack; a hand-rolled stack must set this explicitly
211
- * whenever `defaultBaseBranch` is anything other than 'main'.
196
+ * `config.defaultBaseBranch` at request time) with no automatic reconciliation
197
+ * between them. `infrastructure/lib/cms-stack.ts`, as scaffolded by
198
+ * `canopycms init-deploy aws`, derives this prop FROM that config file so the
199
+ * two cannot drift; a hand-rolled stack must set it explicitly whenever
200
+ * `defaultBaseBranch` is anything other than 'main'.
212
201
  */
213
202
  baseBranch?: string;
214
203
  /**
@@ -218,22 +207,21 @@ export interface CanopyCmsServiceProps {
218
207
  * `canopycms-settings-<deploymentName>` — see `deploymentName` below).
219
208
  *
220
209
  * Only set this to mirror an adopter-configured `config.settingsBranch` in
221
- * `canopycms.config.ts`. Leaving both unset (the default) is safe: Lambda and
222
- * worker then resolve the SAME computed name independently, with nothing to
223
- * keep in sync. But if the shared config sets `settingsBranch` explicitly
224
- * and this prop is left unset, the worker keeps resolving the computed name
225
- * while the Lambda's `getSettingsBranchName` short-circuits on
226
- * `config.settingsBranch` — so the two own different branches. The PRIMARY
227
- * settings-push path still reaches GitHub (its task payload carries the
228
- * Lambda-resolved name), but the worker's per-cycle backstop push
229
- * (`pushSettingsBranches`) targets the wrong branch and its "foreign settings
230
- * branch" [SYNC-M3] warning misfires against the deployment's own branch.
231
- * Validated at synth like `baseBranch` (see `assertValidGitBranchName`),
232
- * since this is interpolated into a git ref and the worker's `.env` heredoc
233
- * too. `infrastructure/lib/cms-stack.ts`, as scaffolded by
234
- * `canopycms init-deploy aws`, derives this prop from the same
235
- * `canopycms.config.ts` at synth time, so the two cannot drift for adopters
236
- * who deploy through the generated stack.
210
+ * `canopycms.config.ts`. Leaving both unset is safe: Lambda and worker resolve
211
+ * the SAME computed name independently, with nothing to keep in sync. But if
212
+ * the shared config sets `settingsBranch` and this prop does not, the worker
213
+ * keeps resolving the computed name while the Lambda's `getSettingsBranchName`
214
+ * short-circuits on `config.settingsBranch`, so the two own different
215
+ * branches: the PRIMARY settings-push path still reaches GitHub (its task
216
+ * payload carries the Lambda-resolved name), but the worker's per-cycle
217
+ * backstop push (`pushSettingsBranches`) targets the wrong branch and its
218
+ * "foreign settings branch" warning misfires against the deployment's own.
219
+ *
220
+ * Interpolated into a git ref and the worker's `.env` heredoc, so validated at
221
+ * synth like `baseBranch` (see `assertValidGitBranchName`).
222
+ * `infrastructure/lib/cms-stack.ts`, as scaffolded by `canopycms init-deploy
223
+ * aws`, derives it from the same `canopycms.config.ts` so the two cannot
224
+ * drift.
237
225
  */
238
226
  settingsBranch?: string;
239
227
  /**
@@ -285,20 +273,10 @@ export interface CanopyCmsServiceProps {
285
273
  * Execution role for the CMS Lambda (default: CDK creates one).
286
274
  *
287
275
  * Set this when the role's ARN has to be computable WITHOUT a reference to
288
- * this construct - the motivating case is an asset bucket in a different AWS
289
- * account from the compute, where the resource-policy half of the
290
- * cross-account grant must be written in the bucket's own stack and needs the
291
- * principal as a plain string. Reading `lambdaFunction.role` across an
292
- * account boundary does not give you that: CDK emits `Fn::GetStackOutput`, a
293
- * CDK-CLI-only intrinsic resolved at deploy time, so the coupling is
294
- * invisible to CloudFormation and unusable by any deploy path that is not
295
- * `cdk deploy`. Create a deterministically NAMED role instead and both stacks
296
- * can compute `arn:aws:iam::<account>:role/<name>` from literals, with
297
- * nothing crossing between them.
298
- *
299
- * A named IAM role means the consuming stack needs `CAPABILITY_NAMED_IAM`,
300
- * and cannot be replaced in place without a rename - that trade is yours to
301
- * make here, which is the point of taking a role rather than a name.
276
+ * this construct - typically a cross-account asset bucket. See
277
+ * `AssetSupportProps.transformRole` for why `Fn::GetStackOutput` does not give
278
+ * you that, and what a deterministically NAMED role costs
279
+ * (`CAPABILITY_NAMED_IAM`, no in-place replacement without a rename).
302
280
  *
303
281
  * This Lambda is VPC-attached, which makes the compensation in
304
282
  * `attachLambdaExecutionPolicies` (./lambda-execution-role) load-bearing