@intentius/chant-lexicon-aws 0.124.52 → 0.124.54

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 (78) hide show
  1. package/dist/codegen/generate-lexicon.d.ts +2 -0
  2. package/dist/codegen/generate-lexicon.d.ts.map +1 -1
  3. package/dist/codegen/generate-lexicon.js +1 -0
  4. package/dist/components/__tests__/mock-cloud-executor.d.ts +4 -0
  5. package/dist/components/__tests__/mock-cloud-executor.d.ts.map +1 -1
  6. package/dist/components/__tests__/mock-cloud-executor.js +18 -1
  7. package/dist/components/apply.d.ts +44 -2
  8. package/dist/components/apply.d.ts.map +1 -1
  9. package/dist/components/apply.js +260 -29
  10. package/dist/components/cfn-adopt.d.ts +99 -0
  11. package/dist/components/cfn-adopt.d.ts.map +1 -0
  12. package/dist/components/cfn-adopt.js +278 -0
  13. package/dist/components/cfn-imports.d.ts +84 -0
  14. package/dist/components/cfn-imports.d.ts.map +1 -0
  15. package/dist/components/cfn-imports.js +239 -0
  16. package/dist/components/cloud-executor.d.ts +31 -2
  17. package/dist/components/cloud-executor.d.ts.map +1 -1
  18. package/dist/components/cloud-executor.js +64 -2
  19. package/dist/importable.d.ts +30 -0
  20. package/dist/importable.d.ts.map +1 -0
  21. package/dist/importable.js +72 -0
  22. package/dist/index-identity.d.ts +19 -0
  23. package/dist/index-identity.d.ts.map +1 -0
  24. package/dist/index-identity.js +79 -0
  25. package/dist/index-import.d.ts +17 -0
  26. package/dist/index-import.d.ts.map +1 -0
  27. package/dist/index-import.js +115 -0
  28. package/dist/integrity.json +3 -3
  29. package/dist/listed-types.d.ts +11 -0
  30. package/dist/listed-types.d.ts.map +1 -0
  31. package/dist/listed-types.js +52 -0
  32. package/dist/manifest.json +1 -1
  33. package/dist/meta.json +13206 -1237
  34. package/dist/plugin.d.ts.map +1 -1
  35. package/dist/plugin.js +9 -0
  36. package/dist/reference-catalog.d.ts.map +1 -1
  37. package/dist/reference-catalog.js +62 -20
  38. package/dist/spec/fetch.d.ts +5 -0
  39. package/dist/spec/fetch.d.ts.map +1 -1
  40. package/dist/spec/parse.d.ts +8 -0
  41. package/dist/spec/parse.d.ts.map +1 -1
  42. package/dist/spec/parse.js +3 -0
  43. package/package.json +2 -2
  44. package/src/codegen/generate-lexicon.ts +3 -0
  45. package/src/components/__tests__/mock-cloud-executor.ts +20 -1
  46. package/src/components/apply.ts +282 -29
  47. package/src/components/cfn-adopt.test.ts +284 -0
  48. package/src/components/cfn-adopt.ts +340 -0
  49. package/src/components/cfn-exports-in-use.test.ts +146 -0
  50. package/src/components/cfn-imports-preview.test.ts +178 -0
  51. package/src/components/cfn-imports-three-stacks.test.ts +107 -0
  52. package/src/components/cfn-imports.test.ts +213 -0
  53. package/src/components/cfn-imports.ts +246 -0
  54. package/src/components/cfn-plan-shape-conformance.test.ts +83 -0
  55. package/src/components/cloud-executor.ts +102 -5
  56. package/src/components/demo-composed-stacks-floci.e2e.test.ts +235 -0
  57. package/src/derived-edges.test.ts +142 -0
  58. package/src/generated/lexicon-aws.json +13206 -1237
  59. package/src/importable.test.ts +86 -0
  60. package/src/importable.ts +83 -0
  61. package/src/index-identity.test.ts +134 -0
  62. package/src/index-identity.ts +98 -0
  63. package/src/index-import.test.ts +242 -0
  64. package/src/index-import.ts +140 -0
  65. package/src/index-search.test.ts +87 -0
  66. package/src/listed-types.test.ts +97 -0
  67. package/src/listed-types.ts +56 -0
  68. package/src/plugin.ts +10 -0
  69. package/src/reference-catalog.ts +63 -20
  70. package/src/spec/fetch.ts +5 -0
  71. package/src/spec/parse.test.ts +15 -0
  72. package/src/spec/parse.ts +12 -0
  73. package/src/testdata/indexed/identity.listing.json +65 -0
  74. package/src/testdata/indexed/import.listing.json +126 -0
  75. package/src/testdata/indexed/listing.json +69 -0
  76. package/src/testdata/indexed/topology.listing.json +350 -0
  77. package/src/testdata/three-cloud/dns.graph.json +336 -0
  78. package/src/three-cloud-parity.test.ts +49 -0
@@ -26,6 +26,8 @@ export interface LexiconEntry {
26
26
  conditionalCreateOnly?: string[];
27
27
  replacementStrategy?: "delete_then_create" | "create_then_delete";
28
28
  tagging?: ResourceTagging;
29
+ /** The registry schema's handler operations, sorted (#4536). Absent for a legacy type. */
30
+ handlers?: string[];
29
31
  runtimeDeprecations?: Record<string, string>;
30
32
  }
31
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"generate-lexicon.d.ts","sourceRoot":"","sources":["../../src/codegen/generate-lexicon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAOhF,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC1D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;IAClE,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAAE,EAC5B,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,EAC/C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GACjD,MAAM,CA+CR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYlE"}
1
+ {"version":3,"file":"generate-lexicon.d.ts","sourceRoot":"","sources":["../../src/codegen/generate-lexicon.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAExE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAOhF,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC1D,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;IAClE,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAAE,EAC5B,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,EAAE,CAAC,EAC/C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,GACjD,MAAM,CAgDR;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYlE"}
@@ -41,6 +41,7 @@ export function generateLexiconJSON(results, naming, constraints, runtimeDepreca
41
41
  ...(r.resource.conditionalCreateOnly?.length && { conditionalCreateOnly: r.resource.conditionalCreateOnly }),
42
42
  ...(r.resource.replacementStrategy && { replacementStrategy: r.resource.replacementStrategy }),
43
43
  ...(r.resource.tagging && { tagging: r.resource.tagging }),
44
+ ...(r.resource.handlers?.length && { handlers: r.resource.handlers }),
44
45
  ...(runtimeDepr && { runtimeDeprecations: runtimeDepr }),
45
46
  };
46
47
  },
@@ -47,6 +47,10 @@ export interface FakeStackConfig {
47
47
  missing?: boolean;
48
48
  /** Set by the mock: the empty `REVIEW_IN_PROGRESS` stack a CREATE change set made for a missing stack. */
49
49
  shell?: boolean;
50
+ /** The stack's exports (#4466c), export name to the output it is; the value is the output's. */
51
+ exports?: Record<string, string>;
52
+ /** The stacks that import each of this stack's exports now, by export name (#4466c). */
53
+ importedBy?: Record<string, string[]>;
50
54
  }
51
55
  export interface FakeDeploymentConfig {
52
56
  /** Terminal status CodeDeploy reports for this deployment. Default: "Succeeded". */
@@ -1 +1 @@
1
- {"version":3,"file":"mock-cloud-executor.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/mock-cloud-executor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mock-cloud-executor.d.ts","sourceRoot":"","sources":["../../../src/components/__tests__/mock-cloud-executor.ts"],"names":[],"mappings":"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"}
@@ -32,7 +32,10 @@ export function createMockCloudExecutor(options = {}) {
32
32
  let stepCounter = 0;
33
33
  function stackStatus(name) {
34
34
  const config = stacks.get(name);
35
- return { stackStatus: config?.terminalStatus ?? "UPDATE_COMPLETE", outputs: config?.outputs ?? {} };
35
+ const exports = config?.exports
36
+ ? Object.fromEntries(Object.entries(config.exports).map(([exportName, output]) => [exportName, { output, value: config.outputs?.[output] ?? "" }]))
37
+ : undefined;
38
+ return { stackStatus: config?.terminalStatus ?? "UPDATE_COMPLETE", outputs: config?.outputs ?? {}, ...(exports ? { exports } : {}) };
36
39
  }
37
40
  const docker = {
38
41
  async build(args) {
@@ -124,6 +127,20 @@ export function createMockCloudExecutor(options = {}) {
124
127
  stacks.set(stackName, { ...config, shell: false, missing: true });
125
128
  return true;
126
129
  },
130
+ // Only a mock some stack's `importedBy` was given has ListImports, so the
131
+ // plans of every other test make the calls they always made (#4466c).
132
+ ...(Object.values(options.stacks ?? {}).some((c) => c.importedBy !== undefined)
133
+ ? {
134
+ async listImports(exportName) {
135
+ record("cloudformation", "listImports", exportName);
136
+ const out = new Set();
137
+ for (const config of stacks.values())
138
+ for (const s of config.importedBy?.[exportName] ?? [])
139
+ out.add(s);
140
+ return [...out].sort();
141
+ },
142
+ }
143
+ : {}),
127
144
  async waitForStack(stackName) {
128
145
  record("cloudformation", "waitForStack", stackName);
129
146
  return stackStatus(stackName);
@@ -18,7 +18,7 @@
18
18
  * `run-migration` dispatches on its target transport (ECS one-off task, Lambda
19
19
  * invoke, or SSM host command).
20
20
  */
21
- import type { Capability, PlannedOutputs } from "@intentius/chant/components/capability";
21
+ import type { Capability, DeclaredRead, PlannedOutputs } from "@intentius/chant/components/capability";
22
22
  import type { ChangeSetEntry } from "@intentius/chant/change-set";
23
23
  import { type PlanShape, type PlanShapeArtifact } from "@intentius/chant/components/covered-round";
24
24
  import { type CfnChange, type CfnChangeSet, type CloudExecutor } from "./cloud-executor.js";
@@ -115,9 +115,25 @@ export declare function cfnPlanShape(stack: string, changeSet: CfnChangeSet, unk
115
115
  * known at that value (`given`, the inputs of this plan). Both are what
116
116
  * the stack's outputs will be, whatever they are now.
117
117
  *
118
+ * An output that passes an import through (`Fn::ImportValue` of a name
119
+ * `imports` holds, #4466) is known at the value the exporting stack's plan
120
+ * gave it, which is the value core read for the import.
121
+ *
118
122
  * `undefined` when there is no template or no standing stack to read from.
119
123
  */
120
- export declare function cfnPlannedOutputs(template: unknown, changes: readonly CfnChange[], standing: Record<string, string> | undefined, given?: Record<string, string>): PlannedOutputs | undefined;
124
+ export declare function cfnPlannedOutputs(template: unknown, changes: readonly CfnChange[], standing: Record<string, string> | undefined, given?: Record<string, string>, imports?: Record<string, string>): PlannedOutputs | undefined;
125
+ /**
126
+ * `cfn-deploy`'s `declaredReads` (#4466): each `Fn::ImportValue` of a name
127
+ * exactly one other `cfn-deploy` step's template exports is a read of that
128
+ * component's output, placed under the step's `imports`. A name two steps
129
+ * export is left alone (CloudFormation refuses the second export), and so
130
+ * is a name no step in the project exports: it reads a stack outside it.
131
+ */
132
+ export declare function cfnDeclaredReads(root: string, members: readonly {
133
+ component: string;
134
+ index: number;
135
+ step: Record<string, unknown>;
136
+ }[]): DeclaredRead[];
121
137
  /**
122
138
  * How `cfn-deploy` handles a changeset that requires resource replacement.
123
139
  * - `block`: refuse to apply; surface the changeset for human review.
@@ -146,6 +162,14 @@ export interface CfnDeployInput {
146
162
  onReplace?: CfnReplacePolicy;
147
163
  /** Stage DynamoDB GSI changes (add-then-backfill-then-remove) instead of an in-place replace. Default: false. */
148
164
  stageGsi?: boolean;
165
+ /**
166
+ * The exports the template imports (`Fn::ImportValue`) from another
167
+ * `cfn-deploy` step of the project, by export name (#4466). Core fills it
168
+ * from `declaredReads` with a `stackOutput()` of the exporting component's
169
+ * output, so a value is `undefined` while that stack's plan cannot say it.
170
+ * Never passed to CloudFormation: the template imports the export itself.
171
+ */
172
+ imports?: Record<string, string | undefined>;
149
173
  }
150
174
  export interface CfnDeployOutput {
151
175
  /** Final stack status (`CREATE_COMPLETE`, `UPDATE_COMPLETE`, ...). */
@@ -155,6 +179,24 @@ export interface CfnDeployOutput {
155
179
  /** Snapshot id captured before an `onReplace: "snapshot-first"` replacement, if one was taken. */
156
180
  snapshotId?: string;
157
181
  }
182
+ /** One export a plan changes or removes while other stacks import it (#4466c). */
183
+ export interface CfnExportInUse {
184
+ exportName: string;
185
+ /** What the plan does to it, as the refusal says it: "removes", or "changes ... from A to B". */
186
+ change: string;
187
+ /** The stacks that import it now, sorted. */
188
+ importers: string[];
189
+ }
190
+ /**
191
+ * Thrown at plan time when a change set would change or remove an export
192
+ * that another stack still imports (#4466c). CloudFormation refuses that
193
+ * update at apply; the plan refuses it first, naming both stacks.
194
+ */
195
+ export declare class CfnExportInUseError extends Error {
196
+ readonly stack: string;
197
+ readonly uses: CfnExportInUse[];
198
+ constructor(stack: string, uses: CfnExportInUse[]);
199
+ }
158
200
  /**
159
201
  * Thrown when a changeset proposes a resource replacement and `onReplace` is
160
202
  * `"block"` (the default) — refusing to apply rather than silently destroying
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/components/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAiC,cAAc,EAAE,MAAM,wCAAwC,CAAC;AACxH,OAAO,KAAK,EAAE,cAAc,EAAgC,MAAM,6BAA6B,CAAC;AAEhG,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,iBAAiB,EAGvB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAwB,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAQ/G;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAKhE;AAID;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAE,SAAS,MAAM,EAAO,GAAG,cAAc,CAgCjH;AAWD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;CACvB;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,GAAG,MAAM,CAI/E;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAGxB,KAAK,EAAE,MAAM;aACb,OAAO,EAAE,MAAM;aACf,GAAG,EAAE,MAAM;IAJ7B,QAAQ,CAAC,SAAS,QAAQ;IAC1B,YACkB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EAO5B;CACF;AA8ID,iGAAiG;AACjG,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,IAAI,EAAE,kBAAkB,CAAC;CAC1B;AAOD,oGAAoG;AACpG,eAAO,MAAM,gBAAgB,sBAAsB,CAAC;AAWpD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK/D;AA+DD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,EAAY,GAAG,SAAS,CAgBlJ;AA4JD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,OAAO,EACjB,OAAO,EAAE,SAAS,SAAS,EAAE,EAC7B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC5C,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACjC,cAAc,GAAG,SAAS,CA0B5B;AA+BD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAEpE,MAAM,WAAW,cAAc;IAC7B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,wLAAwL;IACxL,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mDAAmD;IACnD,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,iHAAiH;IACjH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;aAEjC,KAAK,EAAE,MAAM;aACb,YAAY,EAAE,SAAS,EAAE;IAF3C,YACkB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,SAAS,EAAE,EAQ1C;CACF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,GAAE,aAAsC,GAC/C,UAAU,CAAC,cAAc,EAAE,eAAe,CAAC,CA6H7C;AAYD,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,EAAE,UAAU,CAAC,cAAc,EAAE,eAAe,CAA+B,CAAC;AAI5G,MAAM,WAAW,qBAAqB;IACpC,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qGAAqG;IACrG,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,GAAE,aAAsC,GAC/C,UAAU,CAAC,qBAAqB,EAAE,sBAAsB,CAAC,CAqC3D;AAED,mFAAmF;AACnF,eAAO,MAAM,0BAA0B,EAAE,UAAU,CAAC,qBAAqB,EAAE,sBAAsB,CAC7D,CAAC;AAIrC,MAAM,WAAW,iBAAiB;IAChC,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,iIAAiI;IACjI,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,GAAE,aAAsC,GAC/C,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAqCnD;AAED,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAkC,CAAC;AAIxH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,yEAAyE;IACzE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wHAAwH;AACxH,wBAAgB,sBAAsB,CAAC,QAAQ,GAAE,aAAsC,GAAG,UAAU,CAAC,WAAW,EAAE,YAAY,CAAC,CAQ9H;AAED,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,EAAE,UAAU,CAAC,WAAW,EAAE,YAAY,CAA4B,CAAC;AAIhG,MAAM,WAAW,kBAAkB;IACjC,kCAAkC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,8HAA8H;AAC9H,wBAAgB,6BAA6B,CAAC,QAAQ,GAAE,aAAsC,GAAG,UAAU,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,CAWnJ;AAED,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,EAAE,UAAU,CAAC,kBAAkB,EAAE,mBAAmB,CAAmC,CAAC;AAI5H;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GACvB;IACE,GAAG,EAAE,UAAU,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,yFAAyF;IACzF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GACD;IAAE,GAAG,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE,MAAM,WAAW,iBAAiB;IAChC,oJAAoJ;IACpJ,IAAI,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,MAAM,EAAE,eAAe,CAAC;IACxB,2GAA2G;IAC3G,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,qGAAqG;IACrG,OAAO,EAAE,OAAO,CAAC;IACjB,gGAAgG;IAChG,OAAO,EAAE,MAAM,CAAC;CACjB;AAyBD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,GAAE,aAAsC,GAAG,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CA2ChJ;AAED,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,UAAU,CAAC,iBAAiB,EAAE,kBAAkB,CAAkC,CAAC"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/components/apply.ts"],"names":[],"mappings":"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"}
@@ -19,10 +19,13 @@
19
19
  * invoke, or SSM host command).
20
20
  */
21
21
  import { createHash } from "node:crypto";
22
- import { readFileSync } from "node:fs";
22
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
23
25
  import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
24
26
  import { normalizePlanShape, PlanShapeMovedError, planShapeDifferences, planShapeDigest, } from "@intentius/chant/components/covered-round";
25
27
  import { defaultCloudExecutor } from "./cloud-executor.js";
28
+ import { cfnExports, cfnImportPointer, cfnImports, cfnKnownValues, cfnTemplateWithStandIns, readCfnTemplate, resolveCfnName } from "./cfn-imports.js";
26
29
  /** CloudFormation's wording when a change set has nothing to apply — the stack
27
30
  * is already current. AWS phrases it "The submitted information didn't contain
28
31
  * changes."; `aws cloudformation deploy` reports "No updates are to be
@@ -157,6 +160,8 @@ preview) {
157
160
  templateDigest: templateDigest(input.template),
158
161
  parameters,
159
162
  unresolved,
163
+ // #4466: the exports the template imports from stacks in the change, as resolved.
164
+ ...(input.imports && Object.keys(input.imports).length > 0 ? { imports: importValues(input.imports) } : {}),
160
165
  ...(preview ? { preview } : {}),
161
166
  });
162
167
  }
@@ -167,10 +172,14 @@ preview) {
167
172
  * exist yet gets a CREATE change set, so every resource reads as a create,
168
173
  * and the empty `REVIEW_IN_PROGRESS` stack CloudFormation made to hold it is
169
174
  * deleted too. A change set with nothing to apply (`isNoopChangeSet`) gives
170
- * a member with no entries. A step whose parameters read outputs that are
171
- * not known yet, from a stack the same run applies first, creates no change
172
- * set: its member gets a hole saying whose outputs it waits on, and no
173
- * artifact, so `run` plans it afresh.
175
+ * a member with no entries. A step whose parameters or imports read outputs
176
+ * that are not known yet, from a stack the same run applies first, is
177
+ * previewed: its change set is made against a stand-in for each unknown
178
+ * value (#4423), an import standing in as a parameter of a template copy
179
+ * (#4466b), and `run` checks the real change set's shape against the
180
+ * preview's before it executes. When no preview can be made, its member gets
181
+ * a hole saying whose outputs it waits on, and no artifact, so `run` plans
182
+ * it afresh.
174
183
  *
175
184
  * With `onReplace: "block"` (the default), a change set that replaces a
176
185
  * resource fails the plan with {@link CfnReplacementBlockedError}, so the
@@ -202,31 +211,51 @@ async function planCfnDeploy(executor, ctx, input) {
202
211
  }
203
212
  const unresolved = [...(ctx.unresolvedInputs ?? [])].sort();
204
213
  let planDigest = cfnPlanDigest(ctx, stack, input, parameters, unresolved);
214
+ // #4466: an import whose name the plan cannot resolve is a hole that says why.
215
+ const importHoles = unresolvedImportHoles(stack, input, knownParameters(parameters));
216
+ // #4466c: an export another stack imports that the plan may change, said where it cannot tell.
217
+ const exportHoles = [];
205
218
  const member = (holes) => ({
206
219
  member: stack,
207
220
  lexicon: "aws",
208
221
  planner: "chant",
209
222
  status: "planned",
210
223
  planDigest,
211
- holes,
224
+ holes: [...holes, ...importHoles, ...exportHoles],
212
225
  });
226
+ const standing = standingReader(executor, stack);
213
227
  if (unresolved.length > 0 || missing.length > 0) {
214
228
  const hole = (why) => ({
215
229
  member: stack,
216
230
  planDigest,
217
231
  changeSet: { member: member([{ address: stack, type: "AWS::CloudFormation::Stack", reason: why }]), entries: [] },
218
232
  });
233
+ const waiting = unknownImports(input);
219
234
  const fallback = unresolved.length > 0
220
- ? `inputs from ${unresolved.join(", ")}; re-checked on apply`
235
+ ? missing.length === 0 && waiting.length > 0
236
+ ? `imports ${waiting.join(", ")}, exported by ${unresolved.join(", ")}, which apply first; re-checked on apply`
237
+ : `inputs from ${unresolved.join(", ")}; re-checked on apply`
221
238
  : `inputs ${[...missing].sort().join(", ")} are known only at apply; re-checked on apply`;
222
- // Which parameters are unknown: the inputs that hold nothing yet. When the
223
- // upstream is unresolved but no input says which, nothing can be previewed.
224
- if (missing.length === 0)
239
+ // Which parameters are unknown: the inputs that hold nothing yet, and a
240
+ // stand-in for each import of an export not known yet (#4466b). When the
241
+ // upstream is unresolved but neither says which, nothing can be previewed.
242
+ if (missing.length === 0 && waiting.length === 0)
225
243
  return hole(fallback);
226
- const preview = await previewCfnDeploy(executor, stack, input, [...missing].sort(), unresolved.length > 0 ? unresolved : undefined);
244
+ const copy = waiting.length > 0 ? standInTemplate(stack, input, waiting, knownParameters(parameters)) : undefined;
245
+ if (typeof copy === "string")
246
+ return hole(`${fallback}; no preview: ${copy}`);
247
+ let preview;
248
+ try {
249
+ const previewed = copy ? { ...input, template: copy.path } : input;
250
+ preview = await previewCfnDeploy(executor, stack, previewed, [...missing, ...(copy?.parameters ?? [])].sort(), unresolved.length > 0 ? unresolved : undefined);
251
+ }
252
+ finally {
253
+ copy?.remove();
254
+ }
227
255
  if (typeof preview === "string")
228
256
  return hole(`${fallback}; no preview: ${preview}`);
229
257
  const { entries, shape, outputs } = preview;
258
+ exportHoles.push(...(await exportsInUse(executor, stack, input, knownParameters(parameters), outputs, standing)));
230
259
  const artifact = { kind: "cfn-deploy-shape", member: stack, shape, digest: planShapeDigest(shape) };
231
260
  // The preview's shape enters the digest (#4423); `member` reads it when called below.
232
261
  planDigest = cfnPlanDigest(ctx, stack, input, parameters, unresolved, artifact.digest);
@@ -241,8 +270,8 @@ async function planCfnDeploy(executor, ctx, input) {
241
270
  if (isNoopChangeSet(changeSet)) {
242
271
  const artifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
243
272
  // Nothing to apply, so the outputs the stack has are the ones it will have (#3999).
244
- const standing = await standingOutputs(executor, stack);
245
- const outputs = standing ? { known: { ...standing }, unknown: [] } : undefined;
273
+ const now = await standing();
274
+ const outputs = now ? { known: { ...now.outputs }, unknown: [] } : undefined;
246
275
  return { member: stack, planDigest, artifact, changeSet: { member: member([]), entries: [] }, ...(outputs ? { outputs } : {}) };
247
276
  }
248
277
  if (changeSet.status === "FAILED") {
@@ -253,7 +282,12 @@ async function planCfnDeploy(executor, ctx, input) {
253
282
  throw new CfnReplacementBlockedError(stack, blocked);
254
283
  const entries = changeSet.changes.map((c) => cfnChangeEntry(stack, c));
255
284
  const artifact = { kind: "cfn-deploy-plan", stack, changesDigest: cfnChangesDigest(stack, changeSet) };
256
- const outputs = changeSet.stackCreated ? undefined : await plannedStackOutputs(executor, stack, input.template, changeSet.changes, knownParameters(parameters));
285
+ const outputs = changeSet.stackCreated
286
+ ? undefined
287
+ : await plannedStackOutputs(standing, input.template, changeSet.changes, knownParameters(parameters), importValues(input.imports ?? {}));
288
+ // #4466c: CloudFormation refuses to change or remove an export another stack imports; say so now, not at apply.
289
+ if (!changeSet.stackCreated)
290
+ exportHoles.push(...(await exportsInUse(executor, stack, input, knownParameters(parameters), outputs, standing)));
257
291
  // The real change set's shape, with the replacement CloudFormation reports,
258
292
  // for a round that checks it against a preview (#4419).
259
293
  const shape = cfnPlanShape(stack, changeSet, []);
@@ -388,7 +422,9 @@ export function cfnPlanShape(stack, changeSet, unknown, templatePath, refs = unk
388
422
  * Floci accepts a change set that real CloudFormation refuses (a missing
389
423
  * export, an id that does not exist), reports `Parameters: null`, and says
390
424
  * `Replacement: False` for a change that replaces. So a cross-stack value
391
- * goes through a parameter here, never an export, and the replacement a
425
+ * goes through a parameter here, never an export: an import of an export
426
+ * not known yet is planned from a copy of the template in which it is a
427
+ * stand-in parameter (#4466b, `cfnTemplateWithStandIns`). The replacement a
392
428
  * preview reports is whatever the backend says; the shape check at apply
393
429
  * compares like with like.
394
430
  */
@@ -414,7 +450,7 @@ async function previewCfnDeploy(executor, stack, input, unknown, refs = unknown)
414
450
  // output that reads one of the unknown parameters is unknown, one that
415
451
  // reads a known parameter or states its value is not (#4419).
416
452
  const given = Object.fromEntries(Object.entries(parameters).filter(([k]) => !unknown.includes(k)));
417
- const outputs = noop || changeSet.stackCreated ? undefined : await plannedStackOutputs(executor, stack, input.template, changeSet.changes, given);
453
+ const outputs = noop || changeSet.stackCreated ? undefined : await plannedStackOutputs(standingReader(executor, stack), input.template, changeSet.changes, given);
418
454
  return { entries, shape: cfnPlanShape(stack, changeSet, unknown, input.template, refs), ...(outputs ? { outputs } : {}) };
419
455
  }
420
456
  finally {
@@ -443,26 +479,23 @@ async function cleanUpPlanChangeSet(executor, changeSet) {
443
479
  }
444
480
  }
445
481
  }
446
- /** The stack's outputs as they stand, or `undefined` when they cannot be read. */
447
- async function standingOutputs(executor, stack) {
448
- try {
449
- return { ...(await executor.cloudformation.describeStack(stack)).outputs };
450
- }
451
- catch {
452
- return undefined;
453
- }
482
+ /** The stack as it stands, read once however often it is asked for, or `undefined` when it cannot be read. */
483
+ function standingReader(executor, stack) {
484
+ let read;
485
+ return () => (read ?? (read = executor.cloudformation.describeStack(stack).then((s) => s, () => undefined)));
454
486
  }
455
487
  /**
456
488
  * What the plan says of the stack's outputs once `changes` apply: none to
457
489
  * say for a template with no `Outputs`, which needs no `DescribeStacks`.
458
490
  */
459
- async function plannedStackOutputs(executor, stack, templatePath, changes, parameters = {}) {
491
+ async function plannedStackOutputs(standing, templatePath, changes, parameters = {}, imports = {}) {
460
492
  const template = readTemplate(templatePath);
461
493
  if (!template || typeof template !== "object")
462
494
  return undefined;
463
495
  if (Object.keys(template.Outputs ?? {}).length === 0)
464
496
  return { known: {}, unknown: [] };
465
- return cfnPlannedOutputs(template, changes, await standingOutputs(executor, stack), parameters);
497
+ const now = await standing();
498
+ return cfnPlannedOutputs(template, changes, now ? { ...now.outputs } : undefined, parameters, imports);
466
499
  }
467
500
  /** The template at `path` as JSON, or `undefined` when it cannot be read or parsed here. */
468
501
  function readTemplate(path) {
@@ -551,9 +584,13 @@ function valueReads(value) {
551
584
  * known at that value (`given`, the inputs of this plan). Both are what
552
585
  * the stack's outputs will be, whatever they are now.
553
586
  *
587
+ * An output that passes an import through (`Fn::ImportValue` of a name
588
+ * `imports` holds, #4466) is known at the value the exporting stack's plan
589
+ * gave it, which is the value core read for the import.
590
+ *
554
591
  * `undefined` when there is no template or no standing stack to read from.
555
592
  */
556
- export function cfnPlannedOutputs(template, changes, standing, given = {}) {
593
+ export function cfnPlannedOutputs(template, changes, standing, given = {}, imports = {}) {
557
594
  if (!standing || template === null || typeof template !== "object")
558
595
  return undefined;
559
596
  const t = template;
@@ -568,7 +605,7 @@ export function cfnPlannedOutputs(template, changes, standing, given = {}) {
568
605
  const known = {};
569
606
  const unknown = [];
570
607
  for (const [name, output] of Object.entries(t.Outputs ?? {}).sort(([a], [b]) => a.localeCompare(b))) {
571
- const stated = statedOutput(output, given, t.Parameters);
608
+ const stated = statedOutput(output, given, t.Parameters, imports);
572
609
  if (stated !== undefined) {
573
610
  known[name] = stated;
574
611
  continue;
@@ -592,7 +629,7 @@ function knownParameters(parameters) {
592
629
  * parameter this plan has a value for. `undefined` for anything that reads a
593
630
  * resource, an import or a condition, which the standing outputs speak for.
594
631
  */
595
- function statedOutput(output, parameters, declared) {
632
+ function statedOutput(output, parameters, declared, imports = {}) {
596
633
  if (output?.Condition !== undefined)
597
634
  return undefined;
598
635
  const value = output?.Value;
@@ -603,11 +640,202 @@ function statedOutput(output, parameters, declared) {
603
640
  const [key, arg] = entries[0] ?? [];
604
641
  if (entries.length === 1 && key === "Ref" && typeof arg === "string" && declared && arg in declared && arg in parameters)
605
642
  return parameters[arg];
643
+ if (entries.length === 1 && key === "Fn::ImportValue") {
644
+ const name = resolveCfnName(arg, parameters);
645
+ if ("name" in name && name.name in imports)
646
+ return imports[name.name];
647
+ }
606
648
  }
607
649
  return undefined;
608
650
  }
651
+ // ── Fn::ImportValue between stacks (#4466) ──────────────────────────────────
652
+ /** The imports core read a value for, as strings; one still unknown is left out. */
653
+ function importValues(imports) {
654
+ const out = {};
655
+ for (const [name, v] of Object.entries(imports).sort(([a], [b]) => a.localeCompare(b))) {
656
+ if (v === undefined || v === null)
657
+ continue;
658
+ out[name] = typeof v === "string" ? v : JSON.stringify(v);
659
+ }
660
+ return out;
661
+ }
662
+ /** The export names the step imports from a stack in the change whose value is not known yet, sorted. */
663
+ function unknownImports(input) {
664
+ return Object.entries(input.imports ?? {})
665
+ .filter(([, v]) => v === undefined || v === null)
666
+ .map(([name]) => name)
667
+ .sort();
668
+ }
669
+ /**
670
+ * A hole for each `Fn::ImportValue` in the template whose name the plan
671
+ * cannot resolve (#4466): it may name an export of a stack in this change,
672
+ * and then nothing orders the two, so the plan says it cannot tell.
673
+ */
674
+ function unresolvedImportHoles(stack, input, parameters) {
675
+ const template = readCfnTemplate(input.template);
676
+ if (!template)
677
+ return [];
678
+ const resources = (template.Resources ?? {});
679
+ const holes = [];
680
+ for (const i of cfnImports(template, cfnKnownValues(template, stack, parameters))) {
681
+ if (!("unresolved" in i))
682
+ continue;
683
+ const type = typeof resources[i.logicalId]?.Type === "string" ? resources[i.logicalId].Type : undefined;
684
+ holes.push({
685
+ address: i.logicalId,
686
+ ...(type ? { type } : {}),
687
+ reason: `the Fn::ImportValue at ${i.at} ${i.unresolved}, so the plan cannot say which export it reads or whether a stack in this change exports it`,
688
+ });
689
+ }
690
+ return holes;
691
+ }
692
+ /**
693
+ * The importer's template with each import of `names` as a stand-in
694
+ * parameter (#4466b), written to a scratch file the preview creates its
695
+ * change set from; or why there is none. `remove` deletes the file.
696
+ */
697
+ function standInTemplate(stack, input, names, parameters) {
698
+ const template = readCfnTemplate(input.template);
699
+ if (!template)
700
+ return "the template could not be read as JSON to stand in for its imports";
701
+ const { template: copy, standIns } = cfnTemplateWithStandIns(template, names, cfnKnownValues(template, stack, parameters));
702
+ const params = Object.keys(standIns).sort();
703
+ if (params.length === 0)
704
+ return `the template imports none of ${names.join(", ")} by a name the plan resolves`;
705
+ const dir = mkdtempSync(join(tmpdir(), "chant-cfn-preview-"));
706
+ const path = join(dir, `${stack}.preview.template.json`);
707
+ writeFileSync(path, JSON.stringify(copy, null, 2));
708
+ return { path, parameters: params, remove: () => rmSync(dir, { recursive: true, force: true }) };
709
+ }
710
+ /**
711
+ * `cfn-deploy`'s `declaredReads` (#4466): each `Fn::ImportValue` of a name
712
+ * exactly one other `cfn-deploy` step's template exports is a read of that
713
+ * component's output, placed under the step's `imports`. A name two steps
714
+ * export is left alone (CloudFormation refuses the second export), and so
715
+ * is a name no step in the project exports: it reads a stack outside it.
716
+ */
717
+ export function cfnDeclaredReads(root, members) {
718
+ const seen = members.map((m) => {
719
+ const template = readCfnTemplate(m.step.template, root);
720
+ const stack = typeof m.step.stack === "string" ? m.step.stack : m.component;
721
+ const inputs = (m.step.inputs ?? {});
722
+ const known = cfnKnownValues(template, stack, typeof m.step.imageRef === "string" ? { ...inputs, ImageRef: m.step.imageRef } : inputs);
723
+ return { member: m, template, known };
724
+ });
725
+ const exporters = new Map();
726
+ for (const { member, template, known } of seen) {
727
+ for (const e of cfnExports(template, known)) {
728
+ if (!("name" in e))
729
+ continue;
730
+ (exporters.get(e.name) ?? exporters.set(e.name, []).get(e.name)).push({ component: member.component, output: e.output });
731
+ }
732
+ }
733
+ const out = [];
734
+ for (const { member, template, known } of seen) {
735
+ const done = new Set();
736
+ for (const i of cfnImports(template, known)) {
737
+ if (!("name" in i) || done.has(i.name))
738
+ continue;
739
+ done.add(i.name);
740
+ const owners = (exporters.get(i.name) ?? []).filter((o) => o.component !== member.component);
741
+ if (owners.length !== 1 || (exporters.get(i.name) ?? []).length !== 1)
742
+ continue;
743
+ out.push({ reader: member.component, index: member.index, upstream: owners[0].component, output: owners[0].output, at: cfnImportPointer(i.name) });
744
+ }
745
+ }
746
+ return out;
747
+ }
609
748
  /** Whether a CloudFormation error says the stack does not exist. */
610
749
  const STACK_MISSING = /does not exist/i;
750
+ /**
751
+ * Thrown at plan time when a change set would change or remove an export
752
+ * that another stack still imports (#4466c). CloudFormation refuses that
753
+ * update at apply; the plan refuses it first, naming both stacks.
754
+ */
755
+ export class CfnExportInUseError extends Error {
756
+ constructor(stack, uses) {
757
+ const importers = [...new Set(uses.flatMap((u) => u.importers))].sort();
758
+ super(`cfn-deploy "${stack}": refusing the plan: ` +
759
+ uses
760
+ .map((u) => `it ${u.change.replace("{export}", `export ${u.exportName}`)}, which ${u.importers.length === 1 ? "stack" : "stacks"} ${u.importers.join(", ")} still ${u.importers.length === 1 ? "imports" : "import"}`)
761
+ .join("; ") +
762
+ `. CloudFormation refuses to change or remove an export another stack imports. Change ${importers.join(", ")} to stop importing it first, then change ${stack}.`);
763
+ this.stack = stack;
764
+ this.uses = uses;
765
+ this.name = "CfnExportInUseError";
766
+ }
767
+ }
768
+ /**
769
+ * The exports `stack` has now that this plan changes or removes while
770
+ * another stack imports them (#4466c). An export the new template no longer
771
+ * declares is removed; one whose planned value differs from the value it
772
+ * has is changed: both throw {@link CfnExportInUseError}. One the plan
773
+ * cannot say about (its output's value is known only after apply, or an
774
+ * export name in the new template does not resolve) is a hole naming the
775
+ * importers. Asks `ListImports` only for exports at stake, and nothing at
776
+ * all when the executor cannot list imports or the stack exports nothing.
777
+ */
778
+ async function exportsInUse(executor, stack, input, parameters, planned, standing) {
779
+ const listImports = executor.cloudformation.listImports?.bind(executor.cloudformation);
780
+ if (!listImports)
781
+ return [];
782
+ const was = (await standing())?.exports;
783
+ if (!was || Object.keys(was).length === 0)
784
+ return [];
785
+ const template = readCfnTemplate(input.template);
786
+ if (!template)
787
+ return [];
788
+ const declared = cfnExports(template, cfnKnownValues(template, stack, parameters));
789
+ const now = new Map(declared.filter((e) => "name" in e).map((e) => [e.name, e.output]));
790
+ const unresolvedNow = declared.some((e) => "unresolved" in e);
791
+ const refused = [];
792
+ const holes = [];
793
+ for (const [exportName, { output, value }] of Object.entries(was).sort(([a], [b]) => a.localeCompare(b))) {
794
+ const at = now.get(exportName);
795
+ let change;
796
+ let maybe;
797
+ if (at === undefined) {
798
+ if (unresolvedNow)
799
+ maybe = `may remove export ${exportName}: an export name the template declares does not resolve at plan`;
800
+ else
801
+ change = "removes {export}";
802
+ }
803
+ else {
804
+ const next = planned?.known[at];
805
+ if (next === undefined)
806
+ maybe = `may change export ${exportName}: the value of output ${at} is known only after apply`;
807
+ else if ((typeof next === "string" ? next : JSON.stringify(next)) !== value)
808
+ change = `changes {export} from ${value} to ${typeof next === "string" ? next : JSON.stringify(next)}`;
809
+ }
810
+ if (change === undefined && maybe === undefined)
811
+ continue;
812
+ let importers;
813
+ try {
814
+ importers = (await listImports(exportName)).filter((s) => s !== stack);
815
+ }
816
+ catch (err) {
817
+ // A backend without ListImports (an emulator, say) cannot say who imports it; the plan says so instead of failing.
818
+ holes.push({
819
+ address: `Outputs.${at ?? output}`,
820
+ reason: `the plan ${change?.replace("{export}", `export ${exportName}`) ?? maybe}, and the stacks that import it could not be listed: ${err instanceof Error ? err.message : String(err)}`,
821
+ });
822
+ continue;
823
+ }
824
+ if (importers.length === 0)
825
+ continue;
826
+ if (change !== undefined)
827
+ refused.push({ exportName, change, importers });
828
+ else {
829
+ holes.push({
830
+ address: `Outputs.${at ?? output}`,
831
+ reason: `the plan ${maybe}, and ${importers.join(", ")} still ${importers.length === 1 ? "imports" : "import"} it; CloudFormation refuses the apply if it changes`,
832
+ });
833
+ }
834
+ }
835
+ if (refused.length > 0)
836
+ throw new CfnExportInUseError(stack, refused);
837
+ return holes;
838
+ }
611
839
  /**
612
840
  * Thrown when a changeset proposes a resource replacement and `onReplace` is
613
841
  * `"block"` (the default) — refusing to apply rather than silently destroying
@@ -655,6 +883,9 @@ export function createCfnDeployCapability(executor = defaultCloudExecutor()) {
655
883
  plan(ctx, input) {
656
884
  return planCfnDeploy(executor, ctx, input);
657
885
  },
886
+ declaredReads(ctx, members) {
887
+ return cfnDeclaredReads(ctx.root, members);
888
+ },
658
889
  async outputs(ctx, input) {
659
890
  // The outputs the stack has now, for a dependent planned before it
660
891
  // applies (#3183). A stack that does not exist yet has none.