@intentius/chant-lexicon-terraform 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 (336) hide show
  1. package/README.md +7 -6
  2. package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
  3. package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
  4. package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
  5. package/dist/__fixtures__/report/generate.d.ts +26 -0
  6. package/dist/__fixtures__/report/generate.d.ts.map +1 -0
  7. package/dist/__fixtures__/report/generate.js +58 -0
  8. package/dist/applied-state.d.ts +27 -0
  9. package/dist/applied-state.d.ts.map +1 -0
  10. package/dist/applied-state.js +44 -0
  11. package/dist/backend-config.d.ts +53 -0
  12. package/dist/backend-config.d.ts.map +1 -0
  13. package/dist/backend-config.js +136 -0
  14. package/dist/behaviour/kinds.d.ts.map +1 -1
  15. package/dist/behaviour/kinds.js +4 -0
  16. package/dist/behaviour/predict.d.ts.map +1 -1
  17. package/dist/behaviour/predict.js +6 -2
  18. package/dist/behaviour/reference-catalog.d.ts +16 -5
  19. package/dist/behaviour/reference-catalog.d.ts.map +1 -1
  20. package/dist/behaviour/reference-catalog.js +16 -5
  21. package/dist/behaviour/request.d.ts.map +1 -1
  22. package/dist/behaviour/request.js +3 -2
  23. package/dist/changed-roots.d.ts +6 -4
  24. package/dist/changed-roots.d.ts.map +1 -1
  25. package/dist/changed-roots.js +14 -18
  26. package/dist/commands.d.ts.map +1 -1
  27. package/dist/commands.js +4 -3
  28. package/dist/components/capability-plugin.d.ts +65 -3
  29. package/dist/components/capability-plugin.d.ts.map +1 -1
  30. package/dist/components/capability-plugin.js +197 -13
  31. package/dist/components/compose-group.d.ts +145 -0
  32. package/dist/components/compose-group.d.ts.map +1 -0
  33. package/dist/components/compose-group.js +407 -0
  34. package/dist/components/placeholder-preview.d.ts +78 -0
  35. package/dist/components/placeholder-preview.d.ts.map +1 -0
  36. package/dist/components/placeholder-preview.js +254 -0
  37. package/dist/compose/apply-events.d.ts +46 -0
  38. package/dist/compose/apply-events.d.ts.map +1 -0
  39. package/dist/compose/apply-events.js +77 -0
  40. package/dist/compose/estate.d.ts +194 -0
  41. package/dist/compose/estate.d.ts.map +1 -0
  42. package/dist/compose/estate.js +363 -0
  43. package/dist/compose/hcl.d.ts +99 -0
  44. package/dist/compose/hcl.d.ts.map +1 -0
  45. package/dist/compose/hcl.js +302 -0
  46. package/dist/compose/module-address.d.ts +24 -0
  47. package/dist/compose/module-address.d.ts.map +1 -0
  48. package/dist/compose/module-address.js +43 -0
  49. package/dist/compose/root.d.ts +127 -0
  50. package/dist/compose/root.d.ts.map +1 -0
  51. package/dist/compose/root.js +586 -0
  52. package/dist/compose/run.d.ts +183 -0
  53. package/dist/compose/run.d.ts.map +1 -0
  54. package/dist/compose/run.js +447 -0
  55. package/dist/compose/slice.d.ts +102 -0
  56. package/dist/compose/slice.d.ts.map +1 -0
  57. package/dist/compose/slice.js +249 -0
  58. package/dist/compose/state-address.d.ts +28 -0
  59. package/dist/compose/state-address.d.ts.map +1 -0
  60. package/dist/compose/state-address.js +154 -0
  61. package/dist/compose/state-merge.d.ts +52 -0
  62. package/dist/compose/state-merge.d.ts.map +1 -0
  63. package/dist/compose/state-merge.js +135 -0
  64. package/dist/compose.d.ts +179 -0
  65. package/dist/compose.d.ts.map +1 -0
  66. package/dist/compose.js +482 -0
  67. package/dist/composites/terraform-apply-op.d.ts.map +1 -1
  68. package/dist/composites/terraform-apply-op.js +7 -3
  69. package/dist/config.d.ts +59 -13
  70. package/dist/config.d.ts.map +1 -1
  71. package/dist/config.js +43 -9
  72. package/dist/describe-resources.d.ts +74 -12
  73. package/dist/describe-resources.d.ts.map +1 -1
  74. package/dist/describe-resources.js +374 -66
  75. package/dist/drift-read.d.ts +33 -0
  76. package/dist/drift-read.d.ts.map +1 -0
  77. package/dist/drift-read.js +243 -0
  78. package/dist/drift-record.d.ts +147 -0
  79. package/dist/drift-record.d.ts.map +1 -0
  80. package/dist/drift-record.js +383 -0
  81. package/dist/hcl/edges.d.ts.map +1 -1
  82. package/dist/hcl/edges.js +24 -1
  83. package/dist/hcl/files.d.ts +61 -0
  84. package/dist/hcl/files.d.ts.map +1 -0
  85. package/dist/hcl/files.js +221 -0
  86. package/dist/hcl/instances.d.ts +82 -0
  87. package/dist/hcl/instances.d.ts.map +1 -0
  88. package/dist/hcl/instances.js +390 -0
  89. package/dist/hcl/parse.d.ts +81 -9
  90. package/dist/hcl/parse.d.ts.map +1 -1
  91. package/dist/hcl/parse.js +114 -16
  92. package/dist/hcl/roots.d.ts.map +1 -1
  93. package/dist/hcl/roots.js +5 -0
  94. package/dist/hcl/suppressions.d.ts.map +1 -1
  95. package/dist/hcl/suppressions.js +8 -2
  96. package/dist/index.d.ts +11 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +21 -2
  99. package/dist/integrity.json +3 -3
  100. package/dist/live-plan-reasons.d.ts +21 -0
  101. package/dist/live-plan-reasons.d.ts.map +1 -0
  102. package/dist/live-plan-reasons.js +47 -0
  103. package/dist/lsp/option-keys.d.ts.map +1 -1
  104. package/dist/lsp/option-keys.js +2 -0
  105. package/dist/manifest.json +1 -1
  106. package/dist/op/activities/index.d.ts +6 -2
  107. package/dist/op/activities/index.d.ts.map +1 -1
  108. package/dist/op/activities/index.js +5 -1
  109. package/dist/op/activities/live-detect.d.ts +4 -3
  110. package/dist/op/activities/live-detect.d.ts.map +1 -1
  111. package/dist/op/activities/live-detect.js +57 -32
  112. package/dist/op/activities/pin-rollout.d.ts +2 -0
  113. package/dist/op/activities/pin-rollout.d.ts.map +1 -1
  114. package/dist/op/activities/pin-rollout.js +1 -0
  115. package/dist/op/activities/terraform.d.ts +153 -5
  116. package/dist/op/activities/terraform.d.ts.map +1 -1
  117. package/dist/op/activities/terraform.js +205 -20
  118. package/dist/op/activity-contracts.d.ts +27 -0
  119. package/dist/op/activity-contracts.d.ts.map +1 -1
  120. package/dist/op/activity-contracts.js +19 -1
  121. package/dist/op/builders.d.ts +7 -1
  122. package/dist/op/builders.d.ts.map +1 -1
  123. package/dist/op/builders.js +9 -0
  124. package/dist/pin/edit.d.ts.map +1 -1
  125. package/dist/pin/edit.js +35 -0
  126. package/dist/pin/rollout.d.ts +2 -0
  127. package/dist/pin/rollout.d.ts.map +1 -1
  128. package/dist/pin/rollout.js +14 -11
  129. package/dist/plan-shape.d.ts +30 -0
  130. package/dist/plan-shape.d.ts.map +1 -0
  131. package/dist/plan-shape.js +125 -0
  132. package/dist/plugin.d.ts.map +1 -1
  133. package/dist/plugin.js +32 -18
  134. package/dist/report.d.ts +15 -6
  135. package/dist/report.d.ts.map +1 -1
  136. package/dist/report.js +28 -55
  137. package/dist/skills/chant-terraform.md +1 -1
  138. package/dist/state/azure-blob.d.ts +68 -0
  139. package/dist/state/azure-blob.d.ts.map +1 -0
  140. package/dist/state/azure-blob.js +201 -0
  141. package/dist/state/backend.d.ts +158 -0
  142. package/dist/state/backend.d.ts.map +1 -0
  143. package/dist/state/backend.js +406 -0
  144. package/dist/state/exec.d.ts +39 -0
  145. package/dist/state/exec.d.ts.map +1 -0
  146. package/dist/state/exec.js +80 -0
  147. package/dist/state/gcs.d.ts +62 -0
  148. package/dist/state/gcs.d.ts.map +1 -0
  149. package/dist/state/gcs.js +224 -0
  150. package/dist/state/index.d.ts +15 -0
  151. package/dist/state/index.d.ts.map +1 -0
  152. package/dist/state/index.js +14 -0
  153. package/dist/state/locks.d.ts +105 -0
  154. package/dist/state/locks.d.ts.map +1 -0
  155. package/dist/state/locks.js +130 -0
  156. package/dist/state/object-store.d.ts +37 -0
  157. package/dist/state/object-store.d.ts.map +1 -0
  158. package/dist/state/object-store.js +20 -0
  159. package/dist/state/root-states.d.ts +131 -0
  160. package/dist/state/root-states.d.ts.map +1 -0
  161. package/dist/state/root-states.js +241 -0
  162. package/dist/state/s3.d.ts +68 -0
  163. package/dist/state/s3.d.ts.map +1 -0
  164. package/dist/state/s3.js +170 -0
  165. package/dist/state/store.d.ts +51 -0
  166. package/dist/state/store.d.ts.map +1 -0
  167. package/dist/state/store.js +185 -0
  168. package/dist/terragrunt/affected.d.ts.map +1 -1
  169. package/dist/terragrunt/affected.js +2 -1
  170. package/package.json +7 -3
  171. package/src/__fixtures__/ACCEPTANCE.md +69 -7
  172. package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
  173. package/src/__fixtures__/compose/state-app.json +66 -0
  174. package/src/__fixtures__/compose/state-edge.json +40 -0
  175. package/src/__fixtures__/compose/state-net.json +58 -0
  176. package/src/__fixtures__/compose/three-app.plan.json +184 -0
  177. package/src/__fixtures__/compose/three-composed.plan.json +509 -0
  178. package/src/__fixtures__/compose/three-edge.plan.json +144 -0
  179. package/src/__fixtures__/compose/three-net.plan.json +174 -0
  180. package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
  181. package/src/__fixtures__/compose/working-after-apply.json +145 -0
  182. package/src/__fixtures__/config-blocks/app/main.tf +61 -0
  183. package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
  184. package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
  185. package/src/__fixtures__/config-files/shadow/main.tf +3 -0
  186. package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
  187. package/src/__fixtures__/config-files/shadow/other.tf +3 -0
  188. package/src/__fixtures__/config-files/tf/main.tf +11 -0
  189. package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
  190. package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
  191. package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
  192. package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
  193. package/src/__fixtures__/drift-record/README.md +11 -0
  194. package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
  195. package/src/__fixtures__/instance-forms/app/main.tf +110 -0
  196. package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
  197. package/src/__fixtures__/instance-vars/app/main.tf +52 -0
  198. package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
  199. package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
  200. package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
  201. package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
  202. package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
  203. package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
  204. package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
  205. package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
  206. package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
  207. package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
  208. package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
  209. package/src/__fixtures__/pr-loop/README.md +50 -0
  210. package/src/__fixtures__/pr-loop/chant.config.json +13 -0
  211. package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
  212. package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
  213. package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
  214. package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
  215. package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
  216. package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
  217. package/src/__fixtures__/report/applied.show.json +51 -0
  218. package/src/__fixtures__/report/generate.ts +63 -0
  219. package/src/__fixtures__/report/plan.show.json +200 -0
  220. package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
  221. package/src/__fixtures__/report/tf-apply/report.json +338 -0
  222. package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
  223. package/src/__fixtures__/report/tf-plan/report.json +320 -0
  224. package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
  225. package/src/applied-state.ts +52 -0
  226. package/src/backend-config.test.ts +105 -0
  227. package/src/backend-config.ts +133 -0
  228. package/src/behaviour/kinds.ts +4 -0
  229. package/src/behaviour/predict.ts +5 -2
  230. package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
  231. package/src/behaviour/reference-catalog.ts +16 -5
  232. package/src/behaviour/request.ts +3 -2
  233. package/src/change-set-instances.test.ts +98 -0
  234. package/src/changed-roots.ts +14 -16
  235. package/src/commands.ts +4 -3
  236. package/src/components/capability-plugin.test.ts +172 -5
  237. package/src/components/capability-plugin.ts +212 -11
  238. package/src/components/change-set-graph.e2e.test.ts +11 -5
  239. package/src/components/compose-group.test.ts +486 -0
  240. package/src/components/compose-group.ts +489 -0
  241. package/src/components/demo-composed-roots.e2e.test.ts +440 -0
  242. package/src/components/member-outputs.test.ts +76 -0
  243. package/src/components/placeholder-preview.test.ts +147 -0
  244. package/src/components/placeholder-preview.ts +257 -0
  245. package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
  246. package/src/components/pr-loop.tofu.test.ts +112 -56
  247. package/src/compose/apply-events.test.ts +62 -0
  248. package/src/compose/apply-events.ts +100 -0
  249. package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
  250. package/src/compose/estate.test.ts +421 -0
  251. package/src/compose/estate.ts +443 -0
  252. package/src/compose/hcl.ts +311 -0
  253. package/src/compose/module-address.ts +49 -0
  254. package/src/compose/root.test.ts +416 -0
  255. package/src/compose/root.ts +592 -0
  256. package/src/compose/run.test.ts +440 -0
  257. package/src/compose/run.tofu.test.ts +176 -0
  258. package/src/compose/run.ts +527 -0
  259. package/src/compose/slice.test.ts +184 -0
  260. package/src/compose/slice.ts +290 -0
  261. package/src/compose/state-address.ts +157 -0
  262. package/src/compose/state-merge.test.ts +131 -0
  263. package/src/compose/state-merge.ts +148 -0
  264. package/src/compose.test.ts +248 -0
  265. package/src/compose.ts +627 -0
  266. package/src/composites/terraform-apply-op.test.ts +9 -0
  267. package/src/composites/terraform-apply-op.ts +7 -3
  268. package/src/config-file-listers.test.ts +265 -0
  269. package/src/config.ts +44 -9
  270. package/src/describe-resources.engine-facts.test.ts +412 -0
  271. package/src/describe-resources.live-module-keyed.test.ts +290 -0
  272. package/src/describe-resources.ts +416 -70
  273. package/src/drift-read.ts +284 -0
  274. package/src/drift-record.test.ts +223 -0
  275. package/src/drift-record.ts +455 -0
  276. package/src/hcl/config-blocks.test.ts +174 -0
  277. package/src/hcl/edges.ts +21 -1
  278. package/src/hcl/files.ts +234 -0
  279. package/src/hcl/instance-forms.test.ts +94 -0
  280. package/src/hcl/instances.test.ts +137 -0
  281. package/src/hcl/instances.ts +422 -0
  282. package/src/hcl/parse.test.ts +139 -1
  283. package/src/hcl/parse.ts +149 -16
  284. package/src/hcl/roots.ts +6 -0
  285. package/src/hcl/suppressions.ts +8 -2
  286. package/src/index.ts +76 -0
  287. package/src/live-plan-reasons.ts +54 -0
  288. package/src/lsp/completions.test.ts +1 -1
  289. package/src/lsp/option-keys.ts +2 -0
  290. package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
  291. package/src/op/activities/index.ts +9 -0
  292. package/src/op/activities/live-detect.ts +55 -26
  293. package/src/op/activities/pin-rollout.ts +3 -0
  294. package/src/op/activities/registry.test.ts +2 -1
  295. package/src/op/activities/terraform.test.ts +55 -0
  296. package/src/op/activities/terraform.ts +266 -22
  297. package/src/op/activity-contracts.test.ts +1 -0
  298. package/src/op/activity-contracts.ts +24 -1
  299. package/src/op/builders.ts +14 -0
  300. package/src/pin/bundle.test.ts +2 -0
  301. package/src/pin/edit.ts +30 -0
  302. package/src/pin/rollout.ts +16 -11
  303. package/src/plan-shape.test.ts +148 -0
  304. package/src/plan-shape.ts +128 -0
  305. package/src/plugin.ts +33 -19
  306. package/src/report.test.ts +82 -1
  307. package/src/report.ts +31 -54
  308. package/src/signal-only-root.test.ts +72 -0
  309. package/src/skills/chant-terraform.md +1 -1
  310. package/src/state/azure-blob.ts +223 -0
  311. package/src/state/backend-verdict.test.ts +187 -0
  312. package/src/state/backend.test.ts +127 -0
  313. package/src/state/backend.ts +488 -0
  314. package/src/state/exec.ts +111 -0
  315. package/src/state/gcs.ts +254 -0
  316. package/src/state/index.ts +15 -0
  317. package/src/state/locks.test.ts +84 -0
  318. package/src/state/locks.ts +201 -0
  319. package/src/state/object-store.ts +35 -0
  320. package/src/state/root-states-floci.e2e.test.ts +66 -0
  321. package/src/state/root-states.test.ts +462 -0
  322. package/src/state/root-states.ts +309 -0
  323. package/src/state/s3.ts +193 -0
  324. package/src/state/store.ts +225 -0
  325. package/src/terragrunt/affected.ts +2 -1
  326. package/src/workspace-kinds.test.ts +4 -17
  327. package/workspace-kinds.json +1 -24
  328. package/dist/workspace-member.d.ts +0 -30
  329. package/dist/workspace-member.d.ts.map +0 -1
  330. package/dist/workspace-member.js +0 -91
  331. package/schemas/pulumi.schema.json +0 -34
  332. package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
  333. package/src/testdata/pulumi-member/stack-export.json +0 -53
  334. package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
  335. package/src/workspace-member.test.ts +0 -89
  336. package/src/workspace-member.ts +0 -126
@@ -0,0 +1,363 @@
1
+ /**
2
+ * The composed run of choudoufu estates (chant#4445, epic #4419): when every
3
+ * member runs choudoufu against a `live` estate (`Composition.estate`), one
4
+ * `choudoufu plan` and one `choudoufu apply` from a composed root of `module`
5
+ * calls (choudoufu#2059).
6
+ *
7
+ * An estate has no state file, so nothing is pulled, locked, split or
8
+ * written back: each resource's record goes to its own estate as it applies,
9
+ * and a `terraform_estate_outputs` read of another estate in the run is
10
+ * answered inside the run. The composed root is `writeEstateRoot`'s
11
+ * (./root.ts, #4441). The run takes the job's environment and assumes no
12
+ * role of its own: each estate's provider blocks name its role.
13
+ *
14
+ * choudoufu refuses a bare `show` under a `live` block, so the values the
15
+ * plan left unknown are read after the apply from a second plan of the
16
+ * composed root: its `prior_state`, sliced per module (./slice.ts, #4442) and
17
+ * read through `../applied-state`, the reader `chant terraform report` uses.
18
+ * The same plan says how far a failed apply got: a member whose part plans
19
+ * no change any more applied.
20
+ *
21
+ * Lifted from terragucci's `packages/terragucci/src/compose.ts` at c493e0e
22
+ * (`ComposedRun` with `estate` set: `plan`, `apply`, `finishEstate`,
23
+ * `showEstateMembers`, `composedEnv`). The state-backed run is #4444; this one
24
+ * has the same `plan()`, `apply()` and `release()` and returns the same
25
+ * shapes, and #4447 picks one by `Composition.estate`.
26
+ */
27
+ import { existsSync, mkdtempSync, readdirSync, rmdirSync, rmSync } from "node:fs";
28
+ import { tmpdir } from "node:os";
29
+ import { join, resolve } from "node:path";
30
+ import { stateOutputs, stateResources } from "../applied-state.js";
31
+ import { terraformPlanDigest } from "../plan-digest.js";
32
+ import { terraformPlannedOutputs } from "../components/capability-plugin.js";
33
+ import { computePlanDigest } from "@intentius/chant/lifecycle/plan-digest";
34
+ import { isOlderVersion, MIN_CHOUDOUFU_ESTATES_VERSION, parseChoudoufuVersion } from "../op/activities/terraform.js";
35
+ import { runBinary } from "../state/exec.js";
36
+ import { memberApply } from "./apply-events.js";
37
+ import { liveEstate } from "./hcl.js";
38
+ import { stripModule, topModule } from "./module-address.js";
39
+ import { COMPOSE_DIR, withoutVarFiles, writeEstateRoot } from "./root.js";
40
+ import { composedMemberPlans, planCounts, slicePlan, sliceText } from "./slice.js";
41
+ const firstLines = (s, n = 20) => s.trim().split("\n").slice(-n).join("\n");
42
+ /**
43
+ * Why a choudoufu's `version` output cannot run a composed run of estates, or
44
+ * undefined when it can. A release older than
45
+ * {@link MIN_CHOUDOUFU_ESTATES_VERSION} falls back; a dev build names no
46
+ * release and passes.
47
+ */
48
+ export function estateVersionFallback(versionOutput) {
49
+ const version = parseChoudoufuVersion(versionOutput);
50
+ if (version === undefined || !isOlderVersion(version, MIN_CHOUDOUFU_ESTATES_VERSION))
51
+ return undefined;
52
+ return {
53
+ kind: "binary",
54
+ reason: `choudoufu ${version} is older than v${MIN_CHOUDOUFU_ESTATES_VERSION}, the first release that plans and applies several estates as one (choudoufu#2059)`,
55
+ };
56
+ }
57
+ /**
58
+ * What choudoufu's human `apply` output says of each module: the changes
59
+ * that completed (`<addr>: Creation complete`, `Modifications complete`,
60
+ * `Destruction complete`), started (`Creating...` and the rest) or errored
61
+ * (an `Error:` box naming `with <addr>,`). An estate's apply runs under live
62
+ * resource markers and prints no `-json` lines, so this reads the text.
63
+ */
64
+ export function readEstateApplyText(out, modules) {
65
+ const ev = { done: new Map(), started: new Map(), errored: new Map(), other: [] };
66
+ const known = new Set(modules);
67
+ const moduleOf = (addr) => {
68
+ const m = topModule(addr);
69
+ return m !== undefined && known.has(m) && addr !== `module.${m}` ? m : undefined;
70
+ };
71
+ const bump = (map, m) => {
72
+ map.set(m, (map.get(m) ?? 0) + 1);
73
+ };
74
+ const lines = out.split("\n").map((l) => l.replace(/^[│╷╵\s]+/, "").trimEnd());
75
+ for (let i = 0; i < lines.length; i++) {
76
+ const l = lines[i];
77
+ const hit = l.match(/^(module\.\S+): (Creation|Modifications|Destruction) complete\b/);
78
+ if (hit) {
79
+ const m = moduleOf(hit[1]);
80
+ if (m)
81
+ bump(ev.done, m);
82
+ continue;
83
+ }
84
+ const start = l.match(/^(module\.\S+): (Creating|Modifying|Destroying)\.\.\./);
85
+ if (start) {
86
+ const m = moduleOf(start[1]);
87
+ if (m)
88
+ bump(ev.started, m);
89
+ continue;
90
+ }
91
+ if (l.startsWith("Error: ")) {
92
+ let addr;
93
+ for (let j = i + 1; j < Math.min(lines.length, i + 8) && !lines[j].startsWith("Error: "); j++) {
94
+ const w = lines[j].match(/^with (module\.[^,\s]+),?$/);
95
+ if (w) {
96
+ addr = w[1];
97
+ break;
98
+ }
99
+ }
100
+ const m = addr ? moduleOf(addr) : undefined;
101
+ if (m && !ev.errored.has(m))
102
+ ev.errored.set(m, `${stripModule(addr)}: ${l}`);
103
+ else if (!m)
104
+ ev.other.push(l);
105
+ }
106
+ }
107
+ return ev;
108
+ }
109
+ /**
110
+ * Each member's outcome from its planned changes, what the apply printed, its
111
+ * exit code and, when the post-apply plan was made, the changes each member's
112
+ * part still plans. A clean exit means every planned change applied. After a
113
+ * failure, a member's changes left in the post-apply plan say how many of its
114
+ * planned changes applied; without that plan the printed completions do.
115
+ */
116
+ export function estateOutcomes(c, planned, out, code, remaining) {
117
+ const ev = readEstateApplyText(out, c.order.map((member) => c.modules.get(member)));
118
+ for (const member of c.order) {
119
+ const m = c.modules.get(member);
120
+ const n = planned.get(member) ?? 0;
121
+ if (code === 0)
122
+ ev.done.set(m, n);
123
+ else if (remaining?.has(member))
124
+ ev.done.set(m, Math.max(0, n - remaining.get(member)));
125
+ }
126
+ const members = {};
127
+ for (const member of c.order) {
128
+ const m = c.modules.get(member);
129
+ const n = planned.get(member) ?? 0;
130
+ const { apply, done } = memberApply(m, n, ev);
131
+ const error = ev.errored.get(m);
132
+ members[member] = { status: apply, done, planned: n, ...(error && apply !== "applied" && apply !== "no-changes" ? { error } : {}) };
133
+ }
134
+ return { members, other: ev.other };
135
+ }
136
+ /**
137
+ * One composed run of estates: `plan()`, then `apply()` once the approval is
138
+ * given, then `release()` whatever happened. No call reads, locks or writes
139
+ * a state.
140
+ */
141
+ export class EstateRun {
142
+ constructor(o) {
143
+ this.o = o;
144
+ const c = o.composition;
145
+ if (!c.estate || c.fallback || c.members.length < 2)
146
+ throw new Error(`EstateRun runs a composition of estates with two or more members${c.fallback ? `; this one fell back: ${c.fallback.reason}` : ""}`);
147
+ this.exec = o.exec ?? runBinary;
148
+ this.binary = o.binary ?? o.terraform.binary ?? "choudoufu";
149
+ this.composeDir = join(resolve(o.projectRoot), COMPOSE_DIR);
150
+ this.madeWork = o.work === undefined;
151
+ this.work = o.work ?? mkdtempSync(join(tmpdir(), "chant-compose-estate-"));
152
+ }
153
+ get c() {
154
+ return this.o.composition;
155
+ }
156
+ log(line) {
157
+ this.o.log?.(line);
158
+ }
159
+ /**
160
+ * The composed root's environment: the job's, with no role added (each
161
+ * estate's provider blocks name its role) and its `-var-file` flags taken
162
+ * out. With a shared provider cache and no lock file to check a cached
163
+ * provider against, the cache may fill the lock file.
164
+ */
165
+ env() {
166
+ const env = withoutVarFiles(this.o.env);
167
+ return env.TF_PLUGIN_CACHE_DIR && !existsSync(join(this.composeDir, ".terraform.lock.hcl")) ? { ...env, TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE: "true" } : env;
168
+ }
169
+ /** The estate each member's records go to: its root's `estate`, else its `live` block's. */
170
+ estateOf(member) {
171
+ const key = this.c.roots.get(member);
172
+ return (key !== undefined ? this.o.terraform.roots[key]?.estate : undefined) ?? liveEstate(this.c.dirs.get(member), this.binary).estate;
173
+ }
174
+ notPlanned(extra) {
175
+ return {
176
+ members: {},
177
+ digest: "",
178
+ planFile: join(this.work, "composed.tfplan"),
179
+ text: "",
180
+ texts: {},
181
+ generated: [],
182
+ counts: { add: 0, change: 0, destroy: 0, changes: 0, destroys: 0 },
183
+ summary: "",
184
+ ...extra,
185
+ };
186
+ }
187
+ /**
188
+ * Why this choudoufu cannot run the composition, or undefined. Runs
189
+ * `choudoufu version`; what #4447's `groupFallback` asks before planning.
190
+ */
191
+ async fallback() {
192
+ const v = await this.exec(this.binary, ["version"], resolve(this.o.projectRoot), this.o.env);
193
+ // A binary that cannot say its version is left to the plan, which says why it fails.
194
+ return v.code === 0 ? estateVersionFallback(v.stdout) : undefined;
195
+ }
196
+ writeRoot() {
197
+ this.root = writeEstateRoot({ projectRoot: this.o.projectRoot, composition: this.c });
198
+ return this.root;
199
+ }
200
+ restoreRoot() {
201
+ const r = this.root;
202
+ this.root = undefined;
203
+ r?.restore();
204
+ }
205
+ /**
206
+ * Check the binary's version, write the composed root, init it and plan
207
+ * once, then cut the plan into each member's part. The composed root is
208
+ * taken away again before this returns.
209
+ */
210
+ async plan() {
211
+ const fallback = await this.fallback();
212
+ if (fallback) {
213
+ this.log(`composed: ${fallback.reason}; the estates plan wave by wave`);
214
+ return this.notPlanned({ fallback });
215
+ }
216
+ let root;
217
+ try {
218
+ root = this.writeRoot();
219
+ }
220
+ catch (e) {
221
+ return this.notPlanned({ error: `the composed root could not be written: ${e.message}` });
222
+ }
223
+ const planFile = join(this.work, "composed.tfplan");
224
+ let plan;
225
+ let text = "";
226
+ let summary = "";
227
+ try {
228
+ const env = this.env();
229
+ const init = await this.exec(this.binary, ["init", "-input=false", "-no-color"], this.composeDir, env);
230
+ if (init.code !== 0)
231
+ return this.notPlanned({ error: `the composed root's init failed\n${firstLines(init.out)}` });
232
+ const p = await this.exec(this.binary, ["plan", "-input=false", "-no-color", `-out=${planFile}`], this.composeDir, env);
233
+ if (p.code !== 0)
234
+ return this.notPlanned({ error: `the composed plan failed\n${firstLines(p.out)}` });
235
+ summary = p.stdout.match(/Plan: .*|No changes\..*/)?.[0] ?? "planned";
236
+ const json = await this.exec(this.binary, ["show", "-json", planFile], this.composeDir, env);
237
+ try {
238
+ plan = JSON.parse(json.stdout);
239
+ }
240
+ catch {
241
+ return this.notPlanned({ error: `show -json printed no plan\n${firstLines(json.out, 6)}` });
242
+ }
243
+ text = (await this.exec(this.binary, ["show", "-no-color", planFile], this.composeDir, env)).stdout;
244
+ }
245
+ finally {
246
+ this.restoreRoot();
247
+ }
248
+ const members = composedMemberPlans(this.c, plan, { planner: "choudoufu", scope: (m) => this.estateOf(m) });
249
+ const texts = Object.fromEntries(this.c.order.map((m) => [m, sliceText(text, this.c.modules.get(m), members[m].counts)]));
250
+ const digest = computePlanDigest("terraform-compose-estate", { plan: terraformPlanDigest(plan), modules: this.c.order.map((m) => [m, this.c.modules.get(m)]), generated: root.generated });
251
+ this.planned = { members, digest, plan, planFile, text, texts, generated: root.generated, counts: planCounts(plan), summary };
252
+ return this.planned;
253
+ }
254
+ /**
255
+ * Apply the saved plan from the composed root, then plan again and read
256
+ * each member's values from that plan's `prior_state`. A failed apply
257
+ * leaves what applied applied, and each member's outcome says how far its
258
+ * part got.
259
+ */
260
+ async apply() {
261
+ const p = this.planned;
262
+ const notStarted = (error) => ({
263
+ ok: false,
264
+ members: Object.fromEntries(this.c.order.map((m) => [m, { status: "not-started", planned: p?.members[m]?.counts.changes ?? 0, done: 0 }])),
265
+ error,
266
+ });
267
+ if (!p?.plan)
268
+ return notStarted("the composed run did not plan");
269
+ let root;
270
+ try {
271
+ root = this.writeRoot();
272
+ }
273
+ catch (e) {
274
+ return notStarted(`the composed root could not be written: ${e.message}`);
275
+ }
276
+ let out = "";
277
+ let code = 1;
278
+ let after;
279
+ try {
280
+ if (JSON.stringify(root.generated) !== JSON.stringify(p.generated))
281
+ return notStarted("the composed root differs from the one planned, so nothing applied");
282
+ const a = await this.exec(this.binary, ["apply", "-input=false", "-no-color", p.planFile], this.composeDir, this.env());
283
+ out = a.out;
284
+ code = a.code;
285
+ for (const line of a.stdout.split("\n"))
286
+ if (line.trim())
287
+ this.log(`composed: ${line}`);
288
+ after = await this.planAfter();
289
+ }
290
+ finally {
291
+ this.restoreRoot();
292
+ }
293
+ const planned = new Map(this.c.order.map((m) => [m, p.members[m].counts.changes]));
294
+ const slices = after !== undefined ? new Map(this.c.order.map((m) => [m, slicePlan(after, this.c.modules.get(m))])) : undefined;
295
+ const remaining = slices ? new Map(this.c.order.map((m) => [m, planCounts(slices.get(m)).changes])) : undefined;
296
+ const ended = estateOutcomes(this.c, planned, out, code, remaining);
297
+ const members = {};
298
+ for (const m of this.c.order) {
299
+ const row = { ...ended.members[m] };
300
+ const slice = slices?.get(m);
301
+ if (slice !== undefined) {
302
+ row.resources = stateResources(slice);
303
+ if (row.status === "applied" || row.status === "no-changes") {
304
+ // A state choudoufu builds from records may carry no root outputs; the same plan's planned outputs, all known after the apply, stand in.
305
+ const outputs = stateOutputs(slice);
306
+ row.outputs = Object.keys(outputs).length > 0 ? outputs : terraformPlannedOutputs(slice).known;
307
+ }
308
+ }
309
+ members[m] = row;
310
+ }
311
+ const ok = code === 0 && Object.values(members).every((x) => x.status === "applied" || x.status === "no-changes");
312
+ const error = ok ? undefined : (ended.other[0] ?? Object.values(members).find((x) => x.error)?.error ?? "the composed apply failed");
313
+ return { ok, members, ...(after !== undefined ? { after } : {}), ...(error ? { error } : {}) };
314
+ }
315
+ /**
316
+ * `show -json` of a plan of the composed root made after the apply, whose
317
+ * `prior_state` choudoufu builds from the records and the live system.
318
+ * Undefined when it cannot be made: the values unknown at plan then stay
319
+ * unknown. Never fails the run.
320
+ */
321
+ async planAfter() {
322
+ const planFile = join(this.work, "composed-after.tfplan");
323
+ const env = this.env();
324
+ try {
325
+ const p = await this.exec(this.binary, ["plan", "-input=false", "-no-color", `-out=${planFile}`], this.composeDir, env);
326
+ if (p.code !== 0) {
327
+ this.log(`composed: the estates could not be planned after the apply; the values unknown at plan stay unknown\n${firstLines(p.out)}`);
328
+ return undefined;
329
+ }
330
+ const s = await this.exec(this.binary, ["show", "-json", planFile], this.composeDir, env);
331
+ if (s.code !== 0)
332
+ return undefined;
333
+ return JSON.parse(s.stdout);
334
+ }
335
+ catch (e) {
336
+ this.log(`composed: the estates could not be read after the apply (${e.message}); the values unknown at plan stay unknown`);
337
+ return undefined;
338
+ }
339
+ }
340
+ /**
341
+ * Take the composed root away if a call left it, with the binary's working
342
+ * files under `.chant-compose/`, and the plan files. There is no lock to
343
+ * let go of. Safe to call more than once.
344
+ */
345
+ async release() {
346
+ this.restoreRoot();
347
+ for (const f of [".terraform", ".terraform.lock.hcl"])
348
+ rmSync(join(this.composeDir, f), { recursive: true, force: true });
349
+ try {
350
+ if (existsSync(this.composeDir) && readdirSync(this.composeDir).length === 0)
351
+ rmdirSync(this.composeDir);
352
+ }
353
+ catch {
354
+ // Gone already.
355
+ }
356
+ if (this.madeWork)
357
+ rmSync(this.work, { recursive: true, force: true });
358
+ else
359
+ for (const f of ["composed.tfplan", "composed-after.tfplan"])
360
+ rmSync(join(this.work, f), { force: true });
361
+ this.planned = undefined;
362
+ }
363
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * The HCL a composition reads from a root's files (chant#4438): its backend
3
+ * and the state that backend names, its `terraform_remote_state` and
4
+ * `terraform_estate_outputs` reads, its `live` estate, the providers its lock
5
+ * file pins, and the local modules it calls.
6
+ *
7
+ * Lifted from terragucci at c493e0e (`compose.ts` `blankComments`,
8
+ * `lockedProviders`, `localModules`, `importLiteral`, `jsonBlocks`;
9
+ * `detect.ts` `blockBody`, `hclAttr`, `stateOf`, `estateOf`,
10
+ * `estateOutputReads`, `liveRoots`; `linked.ts` `configFiles`), reading a
11
+ * root's `-backend-config` from `terraform.roots.<name>.backendConfig` where
12
+ * terragucci read `TF_CLI_ARGS_init`. Regex over text with comments blanked,
13
+ * as there: no HCL parse runs.
14
+ */
15
+ import { type Attr, type StateKey } from "./state-address.js";
16
+ import { type ReadBackendInput } from "../state/backend.js";
17
+ export type { StateKey } from "./state-address.js";
18
+ /**
19
+ * HCL text with its comments blanked to spaces, newlines kept, so every
20
+ * position in it is the same position in the text as written. Strings and
21
+ * their escapes are read through, so a `#` or `//` in one stays.
22
+ */
23
+ export declare function blankComments(text: string): string;
24
+ /** The text of the block that starts at `start`: up to the brace that closes its first `{`. */
25
+ export declare function blockBody(text: string, start: number): string;
26
+ /**
27
+ * An HCL block's attribute: its string, `null` when it is written as anything
28
+ * but a plain string, undefined when it is not written.
29
+ */
30
+ export declare const hclAttr: (body: string) => Attr;
31
+ /**
32
+ * A root's own configuration files, not its modules', as paths: the set the
33
+ * root's `binary` reads (`.tf` and `.tf.json`, plus `.tofu` and `.tofu.json`
34
+ * for tofu or choudoufu), from the lexicon's one lister (chant#4457).
35
+ */
36
+ export declare function configFiles(dir: string, binary?: string): string[];
37
+ /** One configuration file's text, comments blanked when it is HCL. */
38
+ export interface RootText {
39
+ file: string;
40
+ text: string;
41
+ json: boolean;
42
+ }
43
+ /** The text of a root's own configuration files, comments blanked so positions still match. */
44
+ export declare function rootTexts(dir: string, binary?: string): RootText[];
45
+ /** The blocks of a kind in a JSON-syntax file: `{"import": [...]}` or `{"import": {...}}`. */
46
+ export declare function jsonBlocks(doc: unknown, kind: string): Record<string, unknown>[];
47
+ /** A `terraform_remote_state` block: its label, the state it reads, and whether it repeats (count or for_each). */
48
+ export interface RemoteStateRead extends StateKey {
49
+ name: string;
50
+ repeated: boolean;
51
+ }
52
+ /** What a root's code says about state: the state its backend keeps and the states its `terraform_remote_state` blocks read. */
53
+ export interface RootState {
54
+ own?: StateKey;
55
+ reads: RemoteStateRead[];
56
+ }
57
+ /**
58
+ * The state the root at `dir` keeps, and the states its
59
+ * `terraform_remote_state` blocks read. `rel` is the root's directory
60
+ * relative to the project, which a `local` path resolves against.
61
+ *
62
+ * Which backend and which configuration name the root's own state is
63
+ * state/backend.ts's {@link readBackend} verdict (chant#4478): the code with
64
+ * the `-backend-config` flags of `TF_CLI_ARGS`, `TF_CLI_ARGS_init`,
65
+ * `backendConfigFiles` and `backendConfig` over it, and what `init` recorded
66
+ * once it has run. A key neither gives is read from the code's own block,
67
+ * which still names nested ones (a `cloud` or `remote` backend's
68
+ * `workspaces`). An address the code does not spell in plain strings is left
69
+ * out, so nothing lines up with it.
70
+ */
71
+ export declare function rootState(dir: string, rel: string, input?: ReadBackendInput): RootState;
72
+ /** Two addresses name one state: the same key, and the same bucket where both name one. */
73
+ export declare const sameState: (a: StateKey, b: StateKey) => boolean;
74
+ /**
75
+ * A choudoufu root's estate, from its `estate.chdf.hcl` or the `live` block in
76
+ * its `terraform` block; undefined when it declares none. A `live` block takes
77
+ * effect only under choudoufu, so the files read are the ones choudoufu reads
78
+ * unless `binary` names another.
79
+ */
80
+ export declare function liveEstate(dir: string, binary?: string): {
81
+ live: boolean;
82
+ estate?: string;
83
+ };
84
+ /** Each `data "terraform_estate_outputs"` block of a root: its label and the estate whose outputs it reads. */
85
+ export declare function estateOutputReads(dir: string, binary?: string): {
86
+ name: string;
87
+ estate: string;
88
+ }[];
89
+ /** The provider versions a root's lock file pins, by provider address. */
90
+ export declare function lockedProviders(dir: string): Map<string, string>;
91
+ /** Local module directories the root at `dir` calls (`source = "./..."` or `"../..."`), followed through, absolute. */
92
+ export declare function localModules(dir: string, binary?: string): string[];
93
+ /** `abs` relative to `from`, with `/` between parts, `.` for `from` itself. */
94
+ export declare const relPath: (from: string, abs: string) => string;
95
+ /** An `import` block the composed root can take as it is: a literal string id, no for_each, no provider of the root's own. */
96
+ export declare function importLiteral(body: string): boolean;
97
+ /** Whether a JSON-syntax `import` block takes no literal string id (or has for_each or a provider). */
98
+ export declare function jsonImportRefused(text: string): boolean;
99
+ //# sourceMappingURL=hcl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hcl.d.ts","sourceRoot":"","sources":["../../src/compose/hcl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAyB,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEtE,YAAY,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBlD;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7D;AAED;;;GAGG;AACH,eAAO,MAAM,OAAO,SACX,MAAM,KAAG,IAgBf,CAAC;AAEJ;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAElE;AAED,sEAAsE;AACtE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAMlE;AAaD,8FAA8F;AAC9F,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAIhF;AAED,mHAAmH;AACnH,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,gIAAgI;AAChI,MAAM,WAAW,SAAS;IACxB,GAAG,CAAC,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B;AAWD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,gBAAqB,GAAG,SAAS,CAsD3F;AAED,2FAA2F;AAC3F,eAAO,MAAM,SAAS,MAAO,QAAQ,KAAK,QAAQ,KAAG,OAA+E,CAAC;AAIrI;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAiBhG;AAED,+GAA+G;AAC/G,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,CAUlG;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAahE;AAED,uHAAuH;AACvH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBnE;AAED,+EAA+E;AAC/E,eAAO,MAAM,OAAO,SAAU,MAAM,OAAO,MAAM,KAAG,MAA0D,CAAC;AAE/G,8HAA8H;AAC9H,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGnD;AAED,uGAAuG;AACvG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD"}