@intentius/chant-lexicon-terraform 0.124.52 → 0.124.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -10
- package/dist/__fixtures__/pr-loop/estate.component.d.ts +7 -0
- package/dist/__fixtures__/pr-loop/estate.component.d.ts.map +1 -0
- package/dist/__fixtures__/pr-loop/estate.component.js +7 -0
- package/dist/__fixtures__/report/generate.d.ts +26 -0
- package/dist/__fixtures__/report/generate.d.ts.map +1 -0
- package/dist/__fixtures__/report/generate.js +58 -0
- package/dist/applied-state.d.ts +27 -0
- package/dist/applied-state.d.ts.map +1 -0
- package/dist/applied-state.js +44 -0
- package/dist/backend-config.d.ts +53 -0
- package/dist/backend-config.d.ts.map +1 -0
- package/dist/backend-config.js +136 -0
- package/dist/behaviour/kinds.d.ts.map +1 -1
- package/dist/behaviour/kinds.js +4 -0
- package/dist/behaviour/predict.d.ts.map +1 -1
- package/dist/behaviour/predict.js +6 -2
- package/dist/behaviour/reference-catalog.d.ts +16 -5
- package/dist/behaviour/reference-catalog.d.ts.map +1 -1
- package/dist/behaviour/reference-catalog.js +16 -5
- package/dist/behaviour/request.d.ts.map +1 -1
- package/dist/behaviour/request.js +3 -2
- package/dist/changed-roots.d.ts +6 -4
- package/dist/changed-roots.d.ts.map +1 -1
- package/dist/changed-roots.js +14 -18
- package/dist/ci-setup.d.ts +11 -0
- package/dist/ci-setup.d.ts.map +1 -0
- package/dist/ci-setup.js +41 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +4 -3
- package/dist/components/capability-plugin.d.ts +105 -9
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/capability-plugin.js +357 -36
- package/dist/components/compose-group.d.ts +145 -0
- package/dist/components/compose-group.d.ts.map +1 -0
- package/dist/components/compose-group.js +407 -0
- package/dist/components/placeholder-preview.d.ts +78 -0
- package/dist/components/placeholder-preview.d.ts.map +1 -0
- package/dist/components/placeholder-preview.js +254 -0
- package/dist/components/release-inputs.d.ts +29 -0
- package/dist/components/release-inputs.d.ts.map +1 -0
- package/dist/components/release-inputs.js +57 -0
- package/dist/components/trace-facts.d.ts +16 -0
- package/dist/components/trace-facts.d.ts.map +1 -0
- package/dist/components/trace-facts.js +19 -0
- package/dist/compose/apply-events.d.ts +46 -0
- package/dist/compose/apply-events.d.ts.map +1 -0
- package/dist/compose/apply-events.js +77 -0
- package/dist/compose/estate.d.ts +194 -0
- package/dist/compose/estate.d.ts.map +1 -0
- package/dist/compose/estate.js +363 -0
- package/dist/compose/hcl.d.ts +99 -0
- package/dist/compose/hcl.d.ts.map +1 -0
- package/dist/compose/hcl.js +302 -0
- package/dist/compose/module-address.d.ts +24 -0
- package/dist/compose/module-address.d.ts.map +1 -0
- package/dist/compose/module-address.js +43 -0
- package/dist/compose/root.d.ts +127 -0
- package/dist/compose/root.d.ts.map +1 -0
- package/dist/compose/root.js +586 -0
- package/dist/compose/run.d.ts +183 -0
- package/dist/compose/run.d.ts.map +1 -0
- package/dist/compose/run.js +447 -0
- package/dist/compose/slice.d.ts +102 -0
- package/dist/compose/slice.d.ts.map +1 -0
- package/dist/compose/slice.js +249 -0
- package/dist/compose/state-address.d.ts +28 -0
- package/dist/compose/state-address.d.ts.map +1 -0
- package/dist/compose/state-address.js +154 -0
- package/dist/compose/state-merge.d.ts +52 -0
- package/dist/compose/state-merge.d.ts.map +1 -0
- package/dist/compose/state-merge.js +135 -0
- package/dist/compose.d.ts +179 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +482 -0
- package/dist/composites/terraform-apply-op.d.ts.map +1 -1
- package/dist/composites/terraform-apply-op.js +7 -3
- package/dist/cone.d.ts +53 -0
- package/dist/cone.d.ts.map +1 -0
- package/dist/cone.js +119 -0
- package/dist/config.d.ts +59 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -9
- package/dist/cost-hold.d.ts +27 -0
- package/dist/cost-hold.d.ts.map +1 -0
- package/dist/cost-hold.js +21 -0
- package/dist/describe-resources.d.ts +74 -12
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/describe-resources.js +374 -66
- package/dist/drift-read.d.ts +33 -0
- package/dist/drift-read.d.ts.map +1 -0
- package/dist/drift-read.js +243 -0
- package/dist/drift-record.d.ts +147 -0
- package/dist/drift-record.d.ts.map +1 -0
- package/dist/drift-record.js +383 -0
- package/dist/drift-rows.d.ts +61 -0
- package/dist/drift-rows.d.ts.map +1 -0
- package/dist/drift-rows.js +108 -0
- package/dist/exit-codes.d.ts +55 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +48 -0
- package/dist/hcl/edges.d.ts.map +1 -1
- package/dist/hcl/edges.js +24 -1
- package/dist/hcl/files.d.ts +61 -0
- package/dist/hcl/files.d.ts.map +1 -0
- package/dist/hcl/files.js +221 -0
- package/dist/hcl/instances.d.ts +82 -0
- package/dist/hcl/instances.d.ts.map +1 -0
- package/dist/hcl/instances.js +390 -0
- package/dist/hcl/parse.d.ts +81 -9
- package/dist/hcl/parse.d.ts.map +1 -1
- package/dist/hcl/parse.js +114 -16
- package/dist/hcl/roots.d.ts.map +1 -1
- package/dist/hcl/roots.js +5 -0
- package/dist/hcl/suppressions.d.ts.map +1 -1
- package/dist/hcl/suppressions.js +8 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/integrity.json +8 -5
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/audit-catalog.js +27 -3
- package/dist/lint/audit-lineage.d.ts +14 -2
- package/dist/lint/audit-lineage.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/index.js +4 -0
- package/dist/lint/post-synth/record-store.d.ts +54 -0
- package/dist/lint/post-synth/record-store.d.ts.map +1 -0
- package/dist/lint/post-synth/record-store.js +104 -0
- package/dist/lint/post-synth/tf001.d.ts +2 -2
- package/dist/lint/post-synth/tf001.js +2 -2
- package/dist/lint/post-synth/tf024.d.ts +35 -11
- package/dist/lint/post-synth/tf024.d.ts.map +1 -1
- package/dist/lint/post-synth/tf024.js +92 -20
- package/dist/lint/post-synth/tf046.d.ts +64 -0
- package/dist/lint/post-synth/tf046.d.ts.map +1 -0
- package/dist/lint/post-synth/tf046.js +175 -0
- package/dist/lint/post-synth/tf047.d.ts +36 -0
- package/dist/lint/post-synth/tf047.d.ts.map +1 -0
- package/dist/lint/post-synth/tf047.js +94 -0
- package/dist/live-plan-reasons.d.ts +21 -0
- package/dist/live-plan-reasons.d.ts.map +1 -0
- package/dist/live-plan-reasons.js +47 -0
- package/dist/lock-rows.d.ts +36 -0
- package/dist/lock-rows.d.ts.map +1 -0
- package/dist/lock-rows.js +60 -0
- package/dist/lock-timeout.d.ts +22 -0
- package/dist/lock-timeout.d.ts.map +1 -0
- package/dist/lock-timeout.js +32 -0
- package/dist/lsp/option-keys.d.ts.map +1 -1
- package/dist/lsp/option-keys.js +2 -0
- package/dist/manifest.json +1 -1
- package/dist/module-catalog/index.d.ts +69 -0
- package/dist/module-catalog/index.d.ts.map +1 -0
- package/dist/module-catalog/index.js +110 -0
- package/dist/module-catalog/refresh-cli.d.ts +4 -0
- package/dist/module-catalog/refresh-cli.d.ts.map +1 -0
- package/dist/module-catalog/refresh-cli.js +64 -0
- package/dist/module-catalog/static-tier.d.ts +6 -0
- package/dist/module-catalog/static-tier.d.ts.map +1 -0
- package/dist/module-catalog/static-tier.js +1650 -0
- package/dist/module-catalog/types.d.ts +65 -0
- package/dist/module-catalog/types.d.ts.map +1 -0
- package/dist/module-catalog/types.js +33 -0
- package/dist/okf/index.md +3 -1
- package/dist/okf/rules/TF024.md +2 -2
- package/dist/okf/rules/TF046.md +11 -0
- package/dist/okf/rules/TF047.md +11 -0
- package/dist/op/activities/index.d.ts +6 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +5 -1
- package/dist/op/activities/live-detect.d.ts +4 -3
- package/dist/op/activities/live-detect.d.ts.map +1 -1
- package/dist/op/activities/live-detect.js +57 -32
- package/dist/op/activities/pin-rollout.d.ts +2 -0
- package/dist/op/activities/pin-rollout.d.ts.map +1 -1
- package/dist/op/activities/pin-rollout.js +1 -0
- package/dist/op/activities/terraform.d.ts +182 -5
- package/dist/op/activities/terraform.d.ts.map +1 -1
- package/dist/op/activities/terraform.js +224 -24
- package/dist/op/activity-contracts.d.ts +27 -0
- package/dist/op/activity-contracts.d.ts.map +1 -1
- package/dist/op/activity-contracts.js +19 -1
- package/dist/op/builders.d.ts +7 -1
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -0
- package/dist/pin/consumers.d.ts +35 -0
- package/dist/pin/consumers.d.ts.map +1 -0
- package/dist/pin/consumers.js +70 -0
- package/dist/pin/edit.d.ts.map +1 -1
- package/dist/pin/edit.js +35 -0
- package/dist/pin/rollout.d.ts +2 -0
- package/dist/pin/rollout.d.ts.map +1 -1
- package/dist/pin/rollout.js +14 -11
- package/dist/plan-shape.d.ts +30 -0
- package/dist/plan-shape.d.ts.map +1 -0
- package/dist/plan-shape.js +125 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +37 -18
- package/dist/registry/address.d.ts +63 -0
- package/dist/registry/address.d.ts.map +1 -0
- package/dist/registry/address.js +65 -0
- package/dist/registry/catalog-read.d.ts +14 -0
- package/dist/registry/catalog-read.d.ts.map +1 -0
- package/dist/registry/catalog-read.js +58 -0
- package/dist/registry/deprecated.d.ts +37 -0
- package/dist/registry/deprecated.d.ts.map +1 -0
- package/dist/registry/deprecated.js +43 -0
- package/dist/registry/docs.d.ts +96 -0
- package/dist/registry/docs.d.ts.map +1 -0
- package/dist/registry/docs.js +210 -0
- package/dist/registry/edge/edge.d.ts +46 -0
- package/dist/registry/edge/edge.d.ts.map +1 -0
- package/dist/registry/edge/edge.js +194 -0
- package/dist/registry/edge/index.d.ts +11 -0
- package/dist/registry/edge/index.d.ts.map +1 -0
- package/dist/registry/edge/index.js +10 -0
- package/dist/registry/edge/lambda-edge.d.ts +76 -0
- package/dist/registry/edge/lambda-edge.d.ts.map +1 -0
- package/dist/registry/edge/lambda-edge.js +95 -0
- package/dist/registry/edge/sigv4-web.d.ts +33 -0
- package/dist/registry/edge/sigv4-web.d.ts.map +1 -0
- package/dist/registry/edge/sigv4-web.js +56 -0
- package/dist/registry/edge/token-source.d.ts +73 -0
- package/dist/registry/edge/token-source.d.ts.map +1 -0
- package/dist/registry/edge/token-source.js +115 -0
- package/dist/registry/edge/worker.d.ts +45 -0
- package/dist/registry/edge/worker.d.ts.map +1 -0
- package/dist/registry/edge/worker.js +26 -0
- package/dist/registry/handler.d.ts +25 -0
- package/dist/registry/handler.d.ts.map +1 -0
- package/dist/registry/handler.js +75 -0
- package/dist/registry/headers-file.d.ts +21 -0
- package/dist/registry/headers-file.d.ts.map +1 -0
- package/dist/registry/headers-file.js +57 -0
- package/dist/registry/index.d.ts +31 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +30 -0
- package/dist/registry/mirror-paths.d.ts +9 -0
- package/dist/registry/mirror-paths.d.ts.map +1 -0
- package/dist/registry/mirror-paths.js +8 -0
- package/dist/registry/mirror.d.ts +130 -0
- package/dist/registry/mirror.d.ts.map +1 -0
- package/dist/registry/mirror.js +319 -0
- package/dist/registry/proxies.d.ts +73 -0
- package/dist/registry/proxies.d.ts.map +1 -0
- package/dist/registry/proxies.js +111 -0
- package/dist/registry/reader.d.ts +57 -0
- package/dist/registry/reader.d.ts.map +1 -0
- package/dist/registry/reader.js +198 -0
- package/dist/registry/semver.d.ts +16 -0
- package/dist/registry/semver.d.ts.map +1 -0
- package/dist/registry/semver.js +24 -0
- package/dist/registry/serve.d.ts +24 -0
- package/dist/registry/serve.d.ts.map +1 -0
- package/dist/registry/serve.js +64 -0
- package/dist/registry/source-forms.d.ts +79 -0
- package/dist/registry/source-forms.d.ts.map +1 -0
- package/dist/registry/source-forms.js +202 -0
- package/dist/registry/targets/artifactory.d.ts +28 -0
- package/dist/registry/targets/artifactory.d.ts.map +1 -0
- package/dist/registry/targets/artifactory.js +206 -0
- package/dist/registry/targets/fake-artifactory.d.ts +30 -0
- package/dist/registry/targets/fake-artifactory.d.ts.map +1 -0
- package/dist/registry/targets/fake-artifactory.js +142 -0
- package/dist/registry/targets/fake-gitlab.d.ts +39 -0
- package/dist/registry/targets/fake-gitlab.d.ts.map +1 -0
- package/dist/registry/targets/fake-gitlab.js +136 -0
- package/dist/registry/targets/fake-hcp.d.ts +50 -0
- package/dist/registry/targets/fake-hcp.d.ts.map +1 -0
- package/dist/registry/targets/fake-hcp.js +202 -0
- package/dist/registry/targets/fake-http.d.ts +31 -0
- package/dist/registry/targets/fake-http.d.ts.map +1 -0
- package/dist/registry/targets/fake-http.js +87 -0
- package/dist/registry/targets/fake-nexus.d.ts +27 -0
- package/dist/registry/targets/fake-nexus.d.ts.map +1 -0
- package/dist/registry/targets/fake-nexus.js +137 -0
- package/dist/registry/targets/gitlab.d.ts +53 -0
- package/dist/registry/targets/gitlab.d.ts.map +1 -0
- package/dist/registry/targets/gitlab.js +187 -0
- package/dist/registry/targets/hcp.d.ts +29 -0
- package/dist/registry/targets/hcp.d.ts.map +1 -0
- package/dist/registry/targets/hcp.js +310 -0
- package/dist/registry/targets/hosted.d.ts +62 -0
- package/dist/registry/targets/hosted.d.ts.map +1 -0
- package/dist/registry/targets/hosted.js +74 -0
- package/dist/registry/targets/index.d.ts +18 -0
- package/dist/registry/targets/index.d.ts.map +1 -0
- package/dist/registry/targets/index.js +17 -0
- package/dist/registry/targets/module-target.d.ts +68 -0
- package/dist/registry/targets/module-target.d.ts.map +1 -0
- package/dist/registry/targets/module-target.js +63 -0
- package/dist/registry/targets/nexus.d.ts +43 -0
- package/dist/registry/targets/nexus.d.ts.map +1 -0
- package/dist/registry/targets/nexus.js +250 -0
- package/dist/registry/targets/zip.d.ts +55 -0
- package/dist/registry/targets/zip.d.ts.map +1 -0
- package/dist/registry/targets/zip.js +278 -0
- package/dist/registry/writer.d.ts +101 -0
- package/dist/registry/writer.d.ts.map +1 -0
- package/dist/registry/writer.js +109 -0
- package/dist/report.d.ts +15 -6
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +28 -55
- package/dist/rules/record-store.ts +122 -0
- package/dist/rules/tf001.ts +2 -2
- package/dist/rules/tf024.ts +105 -22
- package/dist/rules/tf046.ts +204 -0
- package/dist/rules/tf047.ts +114 -0
- package/dist/skills/chant-terraform.md +1 -1
- package/dist/state/azure-blob.d.ts +9 -0
- package/dist/state/azure-blob.d.ts.map +1 -0
- package/dist/state/azure-blob.js +8 -0
- package/dist/state/backend.d.ts +158 -0
- package/dist/state/backend.d.ts.map +1 -0
- package/dist/state/backend.js +406 -0
- package/dist/state/exec.d.ts +39 -0
- package/dist/state/exec.d.ts.map +1 -0
- package/dist/state/exec.js +80 -0
- package/dist/state/gcs.d.ts +8 -0
- package/dist/state/gcs.d.ts.map +1 -0
- package/dist/state/gcs.js +7 -0
- package/dist/state/index.d.ts +15 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +14 -0
- package/dist/state/locks.d.ts +105 -0
- package/dist/state/locks.d.ts.map +1 -0
- package/dist/state/locks.js +130 -0
- package/dist/state/object-store.d.ts +8 -0
- package/dist/state/object-store.d.ts.map +1 -0
- package/dist/state/object-store.js +7 -0
- package/dist/state/root-states.d.ts +131 -0
- package/dist/state/root-states.d.ts.map +1 -0
- package/dist/state/root-states.js +241 -0
- package/dist/state/s3.d.ts +21 -0
- package/dist/state/s3.d.ts.map +1 -0
- package/dist/state/s3.js +19 -0
- package/dist/state/store.d.ts +51 -0
- package/dist/state/store.d.ts.map +1 -0
- package/dist/state/store.js +185 -0
- package/dist/state-ops.d.ts +255 -0
- package/dist/state-ops.d.ts.map +1 -0
- package/dist/state-ops.js +305 -0
- package/dist/state-reads.d.ts +57 -0
- package/dist/state-reads.d.ts.map +1 -0
- package/dist/state-reads.js +104 -0
- package/dist/terragrunt/affected.d.ts.map +1 -1
- package/dist/terragrunt/affected.js +2 -1
- package/dist/used-sources.d.ts +54 -0
- package/dist/used-sources.d.ts.map +1 -0
- package/dist/used-sources.js +157 -0
- package/package.json +24 -4
- package/src/__fixtures__/ACCEPTANCE.md +69 -7
- package/src/__fixtures__/compose/apply-failed-in-app.jsonl +12 -0
- package/src/__fixtures__/compose/state-app.json +66 -0
- package/src/__fixtures__/compose/state-edge.json +40 -0
- package/src/__fixtures__/compose/state-net.json +58 -0
- package/src/__fixtures__/compose/three-app.plan.json +184 -0
- package/src/__fixtures__/compose/three-composed.plan.json +509 -0
- package/src/__fixtures__/compose/three-edge.plan.json +144 -0
- package/src/__fixtures__/compose/three-net.plan.json +174 -0
- package/src/__fixtures__/compose/unknown-composed.plan.json +228 -0
- package/src/__fixtures__/compose/working-after-apply.json +145 -0
- package/src/__fixtures__/config-blocks/app/main.tf +61 -0
- package/src/__fixtures__/config-blocks/app/modules/network/main.tf +4 -0
- package/src/__fixtures__/config-blocks/app-json/main.tf.json +39 -0
- package/src/__fixtures__/config-files/shadow/main.tf +3 -0
- package/src/__fixtures__/config-files/shadow/main.tofu +3 -0
- package/src/__fixtures__/config-files/shadow/other.tf +3 -0
- package/src/__fixtures__/config-files/tf/main.tf +11 -0
- package/src/__fixtures__/config-files/tf-json/main.tf.json +18 -0
- package/src/__fixtures__/config-files/tofu/main.tofu +11 -0
- package/src/__fixtures__/config-files/tofu-json/main.tofu.json +18 -0
- package/src/__fixtures__/config-files/tofu-only/main.tofu +3 -0
- package/src/__fixtures__/drift-record/README.md +16 -0
- package/src/__fixtures__/drift-record/live-plan-sections.json +27 -0
- package/src/__fixtures__/drift-rows/live-root-plan.json +70 -0
- package/src/__fixtures__/drift-rows/refresh-plan.json +61 -0
- package/src/__fixtures__/instance-forms/app/main.tf +110 -0
- package/src/__fixtures__/instance-forms/app/modules/svc/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/main.tf +52 -0
- package/src/__fixtures__/instance-vars/app/modules/app/main.tf +12 -0
- package/src/__fixtures__/instance-vars/app/modules/edge/main.tf +3 -0
- package/src/__fixtures__/instance-vars/app/terraform.tfvars +1 -0
- package/src/__fixtures__/live-module-keyed/live-graph.json +259 -0
- package/src/__fixtures__/live-module-keyed/live-plan.json +87 -0
- package/src/__fixtures__/live-module-keyed/root/estate.chdf.hcl +1 -0
- package/src/__fixtures__/live-module-keyed/root/main.tf +21 -0
- package/src/__fixtures__/live-module-keyed/root/modules/app/main.tf +17 -0
- package/src/__fixtures__/live-module-keyed/root/modules/edge/main.tf +7 -0
- package/src/__fixtures__/live-module-keyed/root/versions.tf +30 -0
- package/src/__fixtures__/live-module-keyed/show-state.json +146 -0
- package/src/__fixtures__/pr-loop/README.md +50 -0
- package/src/__fixtures__/pr-loop/chant.config.json +13 -0
- package/src/__fixtures__/pr-loop/estate.component.ts +10 -0
- package/src/__fixtures__/pr-loop/roots/a/main.tf +11 -0
- package/src/__fixtures__/pr-loop/roots/app/main.tf +19 -0
- package/src/__fixtures__/pr-loop/roots/b/main.tf +7 -0
- package/src/__fixtures__/pr-loop/roots/dns/main.tf +3 -0
- package/src/__fixtures__/pr-loop/roots/net/main.tf +7 -0
- package/src/__fixtures__/report/applied.show.json +51 -0
- package/src/__fixtures__/report/generate.ts +63 -0
- package/src/__fixtures__/report/plan.show.json +200 -0
- package/src/__fixtures__/report/tf-apply/change-set.json +170 -0
- package/src/__fixtures__/report/tf-apply/report.json +338 -0
- package/src/__fixtures__/report/tf-plan/change-set.json +170 -0
- package/src/__fixtures__/report/tf-plan/report.json +320 -0
- package/src/__fixtures__/three-cloud-azure.graph.json +290 -0
- package/src/__fixtures__/used-sources/root/extra.tofu +1 -0
- package/src/__fixtures__/used-sources/root/main.tf +31 -0
- package/src/__fixtures__/used-sources/root/modules/net/main.tf +14 -0
- package/src/__fixtures__/used-sources/root/modules/net/versions.tf.json +7 -0
- package/src/__fixtures__/used-sources/unit/terragrunt.hcl +3 -0
- package/src/applied-state.ts +52 -0
- package/src/backend-config.test.ts +105 -0
- package/src/backend-config.ts +133 -0
- package/src/behaviour/kinds.ts +4 -0
- package/src/behaviour/predict.ts +5 -2
- package/src/behaviour/reference-catalog.plugin.test.ts +65 -0
- package/src/behaviour/reference-catalog.ts +16 -5
- package/src/behaviour/request.ts +3 -2
- package/src/change-set-instances.test.ts +98 -0
- package/src/changed-roots.ts +14 -16
- package/src/ci-setup.ts +42 -0
- package/src/commands.ts +4 -3
- package/src/components/capability-plugin.test.ts +198 -19
- package/src/components/capability-plugin.ts +383 -35
- package/src/components/change-set-graph.e2e.test.ts +11 -5
- package/src/components/compose-group.test.ts +486 -0
- package/src/components/compose-group.ts +489 -0
- package/src/components/demo-composed-roots.e2e.test.ts +440 -0
- package/src/components/member-outputs.test.ts +76 -0
- package/src/components/placeholder-preview.test.ts +147 -0
- package/src/components/placeholder-preview.ts +257 -0
- package/src/components/pr-loop-compose.tofu.e2e.test.ts +363 -0
- package/src/components/pr-loop.tofu.test.ts +113 -57
- package/src/components/release-inputs.ts +69 -0
- package/src/components/trace-facts.test.ts +21 -0
- package/src/components/trace-facts.ts +27 -0
- package/src/compose/apply-events.test.ts +62 -0
- package/src/compose/apply-events.ts +100 -0
- package/src/compose/estate.choudoufu.e2e.test.ts +112 -0
- package/src/compose/estate.test.ts +421 -0
- package/src/compose/estate.ts +443 -0
- package/src/compose/hcl.ts +311 -0
- package/src/compose/module-address.ts +49 -0
- package/src/compose/root.test.ts +416 -0
- package/src/compose/root.ts +592 -0
- package/src/compose/run.test.ts +440 -0
- package/src/compose/run.tofu.test.ts +176 -0
- package/src/compose/run.ts +527 -0
- package/src/compose/slice.test.ts +184 -0
- package/src/compose/slice.ts +290 -0
- package/src/compose/state-address.ts +157 -0
- package/src/compose/state-merge.test.ts +131 -0
- package/src/compose/state-merge.ts +148 -0
- package/src/compose.test.ts +248 -0
- package/src/compose.ts +627 -0
- package/src/composites/terraform-apply-op.test.ts +9 -0
- package/src/composites/terraform-apply-op.ts +7 -3
- package/src/cone.ts +138 -0
- package/src/config-file-listers.test.ts +265 -0
- package/src/config.ts +44 -9
- package/src/cost-hold.ts +29 -0
- package/src/describe-resources.engine-facts.test.ts +411 -0
- package/src/describe-resources.live-module-keyed.test.ts +290 -0
- package/src/describe-resources.ts +416 -70
- package/src/drift-read.ts +284 -0
- package/src/drift-record.test.ts +223 -0
- package/src/drift-record.ts +455 -0
- package/src/drift-rows.test.ts +135 -0
- package/src/drift-rows.ts +137 -0
- package/src/exit-codes.test.ts +23 -0
- package/src/exit-codes.ts +59 -0
- package/src/hcl/config-blocks.test.ts +174 -0
- package/src/hcl/edges.ts +21 -1
- package/src/hcl/files.ts +234 -0
- package/src/hcl/instance-forms.test.ts +94 -0
- package/src/hcl/instances.test.ts +137 -0
- package/src/hcl/instances.ts +422 -0
- package/src/hcl/parse.test.ts +139 -1
- package/src/hcl/parse.ts +149 -16
- package/src/hcl/roots.ts +6 -0
- package/src/hcl/suppressions.ts +8 -2
- package/src/index.ts +79 -0
- package/src/lint/audit-catalog.ts +29 -3
- package/src/lint/audit-lineage.ts +14 -2
- package/src/lint/post-synth/fixtures/TF024/live-with-record-store/main.tf +18 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF024/sidecar-with-backend/main.tf +10 -0
- package/src/lint/post-synth/fixtures/TF046/azurerm-no-resource-group/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/backend-config-only/main.tf +14 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/backend.tf +7 -0
- package/src/lint/post-synth/fixtures/TF046/derived-s3/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-local/main.tf +16 -0
- package/src/lint/post-synth/fixtures/TF046/explicit-s3-beside-local-backend/main.tf +15 -0
- package/src/lint/post-synth/fixtures/TF046/implied-local/main.tf +9 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/estate.chdf.hcl +1 -0
- package/src/lint/post-synth/fixtures/TF046/sidecar-implied/main.tf +5 -0
- package/src/lint/post-synth/fixtures/TF047/clear/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/refused/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-module/main.tf +12 -0
- package/src/lint/post-synth/fixtures/TF047/unknown-version/main.tf +12 -0
- package/src/lint/post-synth/index.ts +4 -0
- package/src/lint/post-synth/record-store.ts +122 -0
- package/src/lint/post-synth/tf001.ts +2 -2
- package/src/lint/post-synth/tf024.test.ts +26 -5
- package/src/lint/post-synth/tf024.ts +105 -22
- package/src/lint/post-synth/tf046.test.ts +137 -0
- package/src/lint/post-synth/tf046.ts +204 -0
- package/src/lint/post-synth/tf047.test.ts +56 -0
- package/src/lint/post-synth/tf047.ts +114 -0
- package/src/live-plan-reasons.ts +54 -0
- package/src/lock-rows.test.ts +70 -0
- package/src/lock-rows.ts +82 -0
- package/src/lock-timeout.ts +34 -0
- package/src/lsp/completions.test.ts +1 -1
- package/src/lsp/option-keys.ts +2 -0
- package/src/module-catalog/index.test.ts +93 -0
- package/src/module-catalog/index.ts +159 -0
- package/src/module-catalog/refresh-cli.ts +68 -0
- package/src/module-catalog/static-tier.ts +1659 -0
- package/src/module-catalog/types.ts +88 -0
- package/src/op/activities/choudoufu-plan-set-op.test.ts +111 -0
- package/src/op/activities/index.ts +9 -0
- package/src/op/activities/live-detect.ts +55 -26
- package/src/op/activities/pin-rollout.ts +3 -0
- package/src/op/activities/registry.test.ts +2 -1
- package/src/op/activities/terraform.test.ts +55 -0
- package/src/op/activities/terraform.ts +312 -27
- package/src/op/activity-contracts.test.ts +1 -0
- package/src/op/activity-contracts.ts +24 -1
- package/src/op/builders.ts +14 -0
- package/src/op-wave-step.test.ts +109 -0
- package/src/pin/__fixtures__/consumers/live/dev/main.tf +4 -0
- package/src/pin/__fixtures__/consumers/live/prod/main.tf +21 -0
- package/src/pin/__fixtures__/consumers/units/vpc/terragrunt.hcl +7 -0
- package/src/pin/bundle.test.ts +2 -0
- package/src/pin/consumers.test.ts +57 -0
- package/src/pin/consumers.ts +95 -0
- package/src/pin/edit.ts +30 -0
- package/src/pin/rollout.ts +16 -11
- package/src/plan-shape.test.ts +148 -0
- package/src/plan-shape.ts +128 -0
- package/src/plugin.ts +39 -19
- package/src/registry/__fixtures__/docs/documented/.terraform-docs.yml +3 -0
- package/src/registry/__fixtures__/docs/documented/README.md +1 -0
- package/src/registry/__fixtures__/docs/documented/main.tf +8 -0
- package/src/registry/__fixtures__/docs/json/main.tf.json +14 -0
- package/src/registry/__fixtures__/docs/vpc/README.md +3 -0
- package/src/registry/__fixtures__/docs/vpc/examples/complete/main.tf +5 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/README.md +1 -0
- package/src/registry/__fixtures__/docs/vpc/examples/simple/main.tf +4 -0
- package/src/registry/__fixtures__/docs/vpc/main.tf +14 -0
- package/src/registry/__fixtures__/docs/vpc/outputs.tf +9 -0
- package/src/registry/__fixtures__/docs/vpc/variables.tf +16 -0
- package/src/registry/__fixtures__/proxied-root/main.tf +12 -0
- package/src/registry/address.ts +100 -0
- package/src/registry/catalog-read.ts +56 -0
- package/src/registry/deprecated.test.ts +139 -0
- package/src/registry/deprecated.ts +76 -0
- package/src/registry/docs.test.ts +140 -0
- package/src/registry/docs.ts +299 -0
- package/src/registry/edge/bundle.test.ts +50 -0
- package/src/registry/edge/edge.test.ts +176 -0
- package/src/registry/edge/edge.ts +211 -0
- package/src/registry/edge/index.ts +10 -0
- package/src/registry/edge/lambda-edge.ts +127 -0
- package/src/registry/edge/sigv4-web.ts +79 -0
- package/src/registry/edge/token-source.test.ts +258 -0
- package/src/registry/edge/token-source.ts +169 -0
- package/src/registry/edge/worker.ts +61 -0
- package/src/registry/handler.ts +89 -0
- package/src/registry/headers-file.ts +61 -0
- package/src/registry/index.ts +31 -0
- package/src/registry/mirror-paths.ts +10 -0
- package/src/registry/mirror.test.ts +198 -0
- package/src/registry/mirror.ts +395 -0
- package/src/registry/proxies.test.ts +153 -0
- package/src/registry/proxies.ts +132 -0
- package/src/registry/reader.ts +225 -0
- package/src/registry/registry.test.ts +262 -0
- package/src/registry/semver.ts +34 -0
- package/src/registry/serve.ts +82 -0
- package/src/registry/source-forms.test.ts +87 -0
- package/src/registry/source-forms.ts +216 -0
- package/src/registry/targets/artifactory.test.ts +248 -0
- package/src/registry/targets/artifactory.ts +257 -0
- package/src/registry/targets/fake-artifactory.ts +158 -0
- package/src/registry/targets/fake-gitlab.ts +161 -0
- package/src/registry/targets/fake-hcp.ts +233 -0
- package/src/registry/targets/fake-http.ts +97 -0
- package/src/registry/targets/fake-nexus.ts +152 -0
- package/src/registry/targets/gitlab.test.ts +208 -0
- package/src/registry/targets/gitlab.ts +223 -0
- package/src/registry/targets/hcp.test.ts +230 -0
- package/src/registry/targets/hcp.ts +360 -0
- package/src/registry/targets/hosted.ts +117 -0
- package/src/registry/targets/index.ts +17 -0
- package/src/registry/targets/module-target.ts +131 -0
- package/src/registry/targets/nexus.test.ts +220 -0
- package/src/registry/targets/nexus.ts +314 -0
- package/src/registry/targets/zip.test.ts +65 -0
- package/src/registry/targets/zip.ts +287 -0
- package/src/registry/writer.ts +155 -0
- package/src/report.test.ts +82 -1
- package/src/report.ts +31 -54
- package/src/signal-only-root.test.ts +72 -0
- package/src/skills/chant-terraform.md +1 -1
- package/src/state/azure-blob.ts +8 -0
- package/src/state/backend-verdict.test.ts +187 -0
- package/src/state/backend.test.ts +127 -0
- package/src/state/backend.ts +488 -0
- package/src/state/exec.ts +111 -0
- package/src/state/gcs.ts +7 -0
- package/src/state/index.ts +15 -0
- package/src/state/locks.test.ts +84 -0
- package/src/state/locks.ts +201 -0
- package/src/state/object-store.ts +7 -0
- package/src/state/root-states-floci.e2e.test.ts +66 -0
- package/src/state/root-states.test.ts +462 -0
- package/src/state/root-states.ts +309 -0
- package/src/state/s3.ts +21 -0
- package/src/state/store.ts +225 -0
- package/src/state-ops.test.ts +182 -0
- package/src/state-ops.ts +462 -0
- package/src/state-reads.ts +116 -0
- package/src/terragrunt/affected.ts +2 -1
- package/src/used-sources.test.ts +75 -0
- package/src/used-sources.ts +204 -0
- package/src/workspace-kinds.test.ts +4 -17
- package/workspace-kinds.json +1 -24
- package/dist/workspace-member.d.ts +0 -30
- package/dist/workspace-member.d.ts.map +0 -1
- package/dist/workspace-member.js +0 -91
- package/schemas/pulumi.schema.json +0 -34
- package/src/testdata/pulumi-member/Pulumi.yaml +0 -2
- package/src/testdata/pulumi-member/stack-export.json +0 -53
- package/src/testdata/pulumi-unexported/Pulumi.yaml +0 -2
- package/src/workspace-member.test.ts +0 -89
- package/src/workspace-member.ts +0 -126
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deprecated-version check against a fixture catalog, the catalog reader
|
|
3
|
+
* with the registry's token (chant#4599), and the handler's page types.
|
|
4
|
+
*/
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { describe, expect, test } from "vitest";
|
|
7
|
+
import { buildReleaseCatalog } from "@intentius/chant/lifecycle/release-catalog";
|
|
8
|
+
import type { ReleaseRecord } from "@intentius/chant/lifecycle/release-ledger";
|
|
9
|
+
import { moduleConsumers } from "../pin/consumers";
|
|
10
|
+
import { catalogUrl, readRegistryCatalog } from "./catalog-read";
|
|
11
|
+
import { deprecatedPins } from "./deprecated";
|
|
12
|
+
import { registryContentType, registryHandler } from "./handler";
|
|
13
|
+
import { RegistryError } from "./reader";
|
|
14
|
+
|
|
15
|
+
const REPO = join(import.meta.dirname, "..", "pin", "__fixtures__", "consumers");
|
|
16
|
+
|
|
17
|
+
const rec = (releaseVersion: string, digest: string, timestamp: string): ReleaseRecord => ({
|
|
18
|
+
version: 1,
|
|
19
|
+
component: "acme/vpc/aws",
|
|
20
|
+
env: "registry",
|
|
21
|
+
digest,
|
|
22
|
+
releaseVersion,
|
|
23
|
+
gitSha: "a".repeat(40),
|
|
24
|
+
runId: "r",
|
|
25
|
+
timestamp,
|
|
26
|
+
actor: "ci",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const catalog = buildReleaseCatalog(
|
|
30
|
+
{
|
|
31
|
+
releases: [rec("1.0.0", "sha256:v1", "2026-10-01T00:00:00.000Z"), rec("1.1.0", "sha256:v11", "2026-10-02T00:00:00.000Z"), rec("2.0.0", "sha256:v2", "2026-10-03T00:00:00.000Z")],
|
|
32
|
+
statuses: [
|
|
33
|
+
{ component: "acme/vpc/aws", digest: "sha256:v1", status: "deprecated", reason: "drops flow logs", replacement: "1.1.0", actor: "ana", timestamp: "2026-10-04T00:00:00.000Z" },
|
|
34
|
+
{ component: "acme/vpc/aws", digest: "sha256:v2", status: "yanked", reason: "breaks routes", replacement: "1.1.0", actor: "ana", timestamp: "2026-10-04T00:00:00.000Z" },
|
|
35
|
+
],
|
|
36
|
+
fields: [{ component: "acme/vpc/aws", engine: "terraform", fields: { address: "registry.acme.dev/acme/vpc/aws" } }],
|
|
37
|
+
},
|
|
38
|
+
{ generatedAt: "2026-10-27T00:00:00.000Z" },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
describe("deprecatedPins", () => {
|
|
42
|
+
test("warns on a deprecated pin, refuses a yanked one, and passes an active one", async () => {
|
|
43
|
+
const consumers = await moduleConsumers(REPO, ["live/prod", "live/dev", "units/vpc"]);
|
|
44
|
+
const { warnings, refusals } = deprecatedPins(consumers, catalog);
|
|
45
|
+
expect(warnings.map((w) => [w.consumer.root, w.version, w.replacement])).toEqual([["live/prod", "1.0.0", "1.1.0"]]);
|
|
46
|
+
expect(warnings[0]!.message).toBe("live/prod/main.tf module.vpc pins acme/vpc/aws 1.0.0, which is deprecated: drops flow logs; move to 1.1.0");
|
|
47
|
+
// live/dev pins v2.0.0: the leading v is ignored, and its host's case too.
|
|
48
|
+
expect(refusals.map((r) => [r.consumer.root, r.version, r.status])).toEqual([["live/dev", "2.0.0", "yanked"]]);
|
|
49
|
+
expect(refusals[0]!.message).toContain("which is yanked: breaks routes; move to 1.1.0");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("leaves unpinned calls and modules the catalog does not list alone", async () => {
|
|
53
|
+
const consumers = await moduleConsumers(REPO, ["live/prod"]);
|
|
54
|
+
const { warnings, refusals } = deprecatedPins(consumers.filter((c) => c.address !== "module.vpc"), catalog);
|
|
55
|
+
expect(warnings).toEqual([]);
|
|
56
|
+
expect(refusals).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("matches a component by name when it has no terraform address, and a pin by digest", () => {
|
|
60
|
+
const byName = buildReleaseCatalog(
|
|
61
|
+
{
|
|
62
|
+
releases: [{ ...rec("3.1.0", "sha256:c31", "2026-10-01T00:00:00.000Z"), component: "oci://registry.acme.dev/modules/cache", releaseVersion: undefined }],
|
|
63
|
+
statuses: [{ component: "oci://registry.acme.dev/modules/cache", digest: "sha256:c31", status: "yanked", actor: "a", timestamp: "2026-10-02T00:00:00.000Z" }],
|
|
64
|
+
},
|
|
65
|
+
{ generatedAt: "2026-10-27T00:00:00.000Z" },
|
|
66
|
+
);
|
|
67
|
+
const call = { root: "r", file: "r/main.tf", address: "module.cache", kind: "terraform" as const, source: "oci://registry.acme.dev/modules/cache?digest=sha256:c31", module: "oci://registry.acme.dev/modules/cache", pin: "sha256:c31", at: "source" as const, param: "digest" as const };
|
|
68
|
+
const { refusals } = deprecatedPins([call], byName);
|
|
69
|
+
expect(refusals.map((r) => [r.version, r.reason])).toEqual([["sha256:c31", null]]);
|
|
70
|
+
expect(refusals[0]!.message).toBe("r/main.tf module.cache pins oci://registry.acme.dev/modules/cache sha256:c31, which is yanked");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("readRegistryCatalog", () => {
|
|
75
|
+
const body = JSON.stringify(catalog);
|
|
76
|
+
|
|
77
|
+
test("sends the registry's token and validates the answer", async () => {
|
|
78
|
+
const seen: Array<{ url: string; auth: string | null }> = [];
|
|
79
|
+
const got = await readRegistryCatalog("modules.acme.dev", {
|
|
80
|
+
token: "t0k",
|
|
81
|
+
fetch: async (url, init) => {
|
|
82
|
+
seen.push({ url, auth: new Headers(init?.headers).get("authorization") });
|
|
83
|
+
return new Response(body, { status: 200, headers: { "content-type": "application/json" } });
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
expect(seen).toEqual([{ url: "https://modules.acme.dev/catalog/index.json", auth: "Bearer t0k" }]);
|
|
87
|
+
expect(got).toEqual(catalog);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("reads the token the CLIs would send", async () => {
|
|
91
|
+
let auth: string | null = null;
|
|
92
|
+
await readRegistryCatalog("modules.acme.dev", {
|
|
93
|
+
credentials: { env: { TF_TOKEN_modules_acme_dev: "from-env" } },
|
|
94
|
+
fetch: async (_url, init) => ((auth = new Headers(init?.headers).get("authorization")), new Response(body)),
|
|
95
|
+
});
|
|
96
|
+
expect(auth).toBe("Bearer from-env");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a registry with no catalog gives null; a refusal names where the token goes and never a value", async () => {
|
|
100
|
+
expect(await readRegistryCatalog("https://cdn.acme.dev/registry", { token: false, fetch: async () => new Response("", { status: 404 }) })).toBeNull();
|
|
101
|
+
const refused = readRegistryCatalog("modules.acme.dev", { token: false, fetch: async () => new Response("", { status: 401 }) });
|
|
102
|
+
await expect(refused).rejects.toBeInstanceOf(RegistryError);
|
|
103
|
+
await expect(readRegistryCatalog("modules.acme.dev", { token: "secret-value", fetch: async () => new Response("", { status: 403 }) })).rejects.toThrow(/refused the token for modules\.acme\.dev/);
|
|
104
|
+
await expect(readRegistryCatalog("modules.acme.dev", { token: "secret-value", fetch: async () => new Response("", { status: 403 }) })).rejects.not.toThrow(/secret-value/);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("an answer that is not a catalog is an error", async () => {
|
|
108
|
+
await expect(readRegistryCatalog("modules.acme.dev", { token: false, fetch: async () => new Response("{\"contract\":1}") })).rejects.toThrow(/is not a release catalog/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("catalogUrl takes a host or a base URL", () => {
|
|
112
|
+
expect(catalogUrl("modules.acme.dev")).toBe("https://modules.acme.dev/catalog/index.json");
|
|
113
|
+
expect(catalogUrl("https://cdn.acme.dev/registry")).toBe("https://cdn.acme.dev/registry/catalog/index.json");
|
|
114
|
+
expect(catalogUrl("localhost:8080", "http")).toBe("http://localhost:8080/catalog/index.json");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("the handler serves catalog pages so a browser renders them", () => {
|
|
119
|
+
test.each([
|
|
120
|
+
["catalog/index.html", "text/html; charset=utf-8"],
|
|
121
|
+
["catalog/style.css", "text/css; charset=utf-8"],
|
|
122
|
+
["catalog/search.js", "text/javascript; charset=utf-8"],
|
|
123
|
+
["catalog/logo.SVG", "image/svg+xml"],
|
|
124
|
+
["catalog/index.json", "application/json"],
|
|
125
|
+
["v1/modules/acme/vpc/aws/versions", "application/json"],
|
|
126
|
+
["vpc-1.0.0.tar.gz", "application/gzip"],
|
|
127
|
+
["catalog.d/README", "application/octet-stream"],
|
|
128
|
+
["providers/x.zip", "application/octet-stream"],
|
|
129
|
+
])("%s is %s", (key, type) => {
|
|
130
|
+
expect(registryContentType(key)).toBe(type);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("on a request", async () => {
|
|
134
|
+
const handler = registryHandler(async (key) => (key === "catalog/index.html" ? { body: "<!doctype html>" } : {}));
|
|
135
|
+
const res = await handler(new Request("https://modules.acme.dev/catalog/index.html"));
|
|
136
|
+
expect(res.status).toBe(200);
|
|
137
|
+
expect(res.headers.get("content-type")).toBe("text/html; charset=utf-8");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deprecated-version check (chant#4599, terragucci#1289 bundle B): each
|
|
3
|
+
* module call (`moduleConsumers`, ../pin/consumers.ts) against the release
|
|
4
|
+
* catalog (`readRegistryCatalog`, ./catalog-read.ts). A call pinned to a
|
|
5
|
+
* deprecated version gets a warning, and one pinned to a yanked version a
|
|
6
|
+
* refusal, each naming the reason and the version to move to when the
|
|
7
|
+
* catalog has one.
|
|
8
|
+
*
|
|
9
|
+
* A catalog component is matched by the module address its `terraform`
|
|
10
|
+
* fields name (`terraformCatalogFields`, ./docs.ts), else by its name, both
|
|
11
|
+
* normalised as the scan normalises a call's source. A call's pin is matched
|
|
12
|
+
* to a version by the version (a leading `v` ignored) or by the digest. A
|
|
13
|
+
* call with no pin, or one naming a module or version the catalog does not
|
|
14
|
+
* list, is left alone: this check speaks only for what the catalog says.
|
|
15
|
+
*/
|
|
16
|
+
import type { CatalogComponent, CatalogVersion, ReleaseCatalog } from "@intentius/chant/lifecycle/release-catalog";
|
|
17
|
+
import { normalizeModuleSource, type ModuleConsumer } from "../pin/consumers";
|
|
18
|
+
|
|
19
|
+
/** One call on a version the catalog marks. */
|
|
20
|
+
export interface DeprecatedPin {
|
|
21
|
+
consumer: ModuleConsumer;
|
|
22
|
+
component: string;
|
|
23
|
+
/** The version as the catalog lists it (its version, or its digest when it has none). */
|
|
24
|
+
version: string;
|
|
25
|
+
status: "deprecated" | "yanked";
|
|
26
|
+
reason: string | null;
|
|
27
|
+
replacement: string | null;
|
|
28
|
+
/** One line for a person: where, what, why, and what to move to. */
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The check's result: warnings for deprecated pins, refusals for yanked ones. */
|
|
33
|
+
export interface DeprecatedPinsResult {
|
|
34
|
+
warnings: DeprecatedPin[];
|
|
35
|
+
refusals: DeprecatedPin[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const bare = (v: string): string => v.replace(/^v(?=\d)/, "");
|
|
39
|
+
|
|
40
|
+
function addressOf(c: CatalogComponent): string {
|
|
41
|
+
const tf = c.fields.terraform as { address?: unknown } | undefined;
|
|
42
|
+
return normalizeModuleSource(typeof tf?.address === "string" ? tf.address : c.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function versionFor(c: CatalogComponent, pin: string): CatalogVersion | undefined {
|
|
46
|
+
return c.versions.find((v) => (v.version !== null && bare(v.version) === bare(pin)) || v.digest === pin);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Check every call against the catalog. */
|
|
50
|
+
export function deprecatedPins(consumers: readonly ModuleConsumer[], catalog: ReleaseCatalog): DeprecatedPinsResult {
|
|
51
|
+
const byAddress = new Map<string, CatalogComponent>();
|
|
52
|
+
for (const c of catalog.components) byAddress.set(addressOf(c), c);
|
|
53
|
+
const out: DeprecatedPinsResult = { warnings: [], refusals: [] };
|
|
54
|
+
for (const consumer of consumers) {
|
|
55
|
+
if (consumer.pin === null) continue;
|
|
56
|
+
const component = byAddress.get(consumer.module);
|
|
57
|
+
if (!component) continue;
|
|
58
|
+
const v = versionFor(component, consumer.pin);
|
|
59
|
+
if (!v || v.status.state === "active") continue;
|
|
60
|
+
const version = v.version ?? v.digest;
|
|
61
|
+
const said = [`${consumer.file} ${consumer.address} pins ${component.name} ${version}, which is ${v.status.state}`];
|
|
62
|
+
if (v.status.reason) said.push(`: ${v.status.reason}`);
|
|
63
|
+
if (v.status.replacement) said.push(`; move to ${v.status.replacement}`);
|
|
64
|
+
const finding: DeprecatedPin = {
|
|
65
|
+
consumer,
|
|
66
|
+
component: component.name,
|
|
67
|
+
version,
|
|
68
|
+
status: v.status.state,
|
|
69
|
+
reason: v.status.reason,
|
|
70
|
+
replacement: v.status.replacement,
|
|
71
|
+
message: said.join(""),
|
|
72
|
+
};
|
|
73
|
+
(v.status.state === "yanked" ? out.refusals : out.warnings).push(finding);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The module docs extractor (chant#4598): a module without a terraform-docs
|
|
3
|
+
* config, one with it (runner faked: found, no binary, failing, and no
|
|
4
|
+
* runner at all), one written as `.tf.json`, and one with `examples/`; then
|
|
5
|
+
* the catalog fields and site sections built from them.
|
|
6
|
+
*/
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { buildReleaseCatalog } from "@intentius/chant/lifecycle/release-catalog";
|
|
10
|
+
import { renderCatalogSite } from "@intentius/chant/lifecycle/release-catalog-site";
|
|
11
|
+
import { moduleDocs, terraformCatalogFields, terraformCatalogSections, type TerraformDocsRunner } from "./docs";
|
|
12
|
+
|
|
13
|
+
const FIXTURES = join(import.meta.dirname, "__fixtures__", "docs");
|
|
14
|
+
|
|
15
|
+
describe("moduleDocs without a terraform-docs config", () => {
|
|
16
|
+
test("reads inputs, outputs, providers and the README from the HCL", async () => {
|
|
17
|
+
const docs = await moduleDocs(join(FIXTURES, "vpc"));
|
|
18
|
+
expect(docs.docsFrom).toBe("hcl");
|
|
19
|
+
expect(docs.config).toBeNull();
|
|
20
|
+
expect(docs.note).toBeNull();
|
|
21
|
+
expect(docs.markdown).toBeNull();
|
|
22
|
+
expect(docs.readme).toContain("A VPC with flow logs.");
|
|
23
|
+
expect(docs.inputs).toEqual([
|
|
24
|
+
{ name: "cidr", type: "string", required: true, description: "The VPC's IPv4 CIDR block.", sensitive: false, file: "variables.tf" },
|
|
25
|
+
{ name: "flow_log_key", type: "string", required: false, description: null, sensitive: true, file: "variables.tf" },
|
|
26
|
+
{ name: "tags", type: "map(string)", required: false, default: {}, description: "Tags for every resource.", sensitive: false, file: "variables.tf" },
|
|
27
|
+
]);
|
|
28
|
+
expect(docs.outputs).toEqual([
|
|
29
|
+
{ name: "arn", description: null, sensitive: true, file: "outputs.tf" },
|
|
30
|
+
{ name: "vpc_id", description: "The VPC's id.", sensitive: false, file: "outputs.tf" },
|
|
31
|
+
]);
|
|
32
|
+
expect(docs.providers).toEqual([{ name: "aws", source: "hashicorp/aws", version: ">= 5.0" }]);
|
|
33
|
+
expect(docs.requiredVersion).toBe(">= 1.5");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("never carries a sensitive default", async () => {
|
|
37
|
+
const docs = await moduleDocs(join(FIXTURES, "vpc"));
|
|
38
|
+
expect(JSON.stringify(docs)).not.toContain("not-a-real-key");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("a runner is not called when the module has no config", async () => {
|
|
42
|
+
let called = false;
|
|
43
|
+
const docs = await moduleDocs(join(FIXTURES, "vpc"), { terraformDocs: async () => ((called = true), "x") });
|
|
44
|
+
expect(called).toBe(false);
|
|
45
|
+
expect(docs.docsFrom).toBe("hcl");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("moduleDocs with examples/", () => {
|
|
50
|
+
test("lists each example with its README and files", async () => {
|
|
51
|
+
const docs = await moduleDocs(join(FIXTURES, "vpc"));
|
|
52
|
+
expect(docs.examples).toEqual([
|
|
53
|
+
{ name: "complete", path: "examples/complete", readme: null, files: ["main.tf"] },
|
|
54
|
+
{ name: "simple", path: "examples/simple", readme: "# Simple\n", files: ["main.tf"] },
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("moduleDocs with a .terraform-docs.yml", () => {
|
|
60
|
+
const dir = join(FIXTURES, "documented");
|
|
61
|
+
|
|
62
|
+
test("runs terraform-docs with that file and keeps its markdown", async () => {
|
|
63
|
+
const calls: Array<{ dir: string; config: string }> = [];
|
|
64
|
+
const runner: TerraformDocsRunner = async (args) => {
|
|
65
|
+
calls.push(args);
|
|
66
|
+
return "## Inputs\n\n| Name | Type |\n|---|---|\n| name | string |\n";
|
|
67
|
+
};
|
|
68
|
+
const docs = await moduleDocs(dir, { terraformDocs: runner });
|
|
69
|
+
expect(calls).toEqual([{ dir, config: ".terraform-docs.yml" }]);
|
|
70
|
+
expect(docs.docsFrom).toBe("terraform-docs");
|
|
71
|
+
expect(docs.markdown).toContain("| name | string |");
|
|
72
|
+
expect(docs.config).toBe(".terraform-docs.yml");
|
|
73
|
+
expect(docs.inputs.map((i) => i.name)).toEqual(["name"]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("falls back to the HCL when the binary is missing, and says so", async () => {
|
|
77
|
+
const docs = await moduleDocs(dir, { terraformDocs: async () => null });
|
|
78
|
+
expect(docs.docsFrom).toBe("hcl");
|
|
79
|
+
expect(docs.markdown).toBeNull();
|
|
80
|
+
expect(docs.note).toBe(".terraform-docs.yml found, and no terraform-docs binary is installed");
|
|
81
|
+
expect(docs.inputs.map((i) => i.name)).toEqual(["name"]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("falls back when the runner fails", async () => {
|
|
85
|
+
const docs = await moduleDocs(dir, { terraformDocs: async () => { throw new Error("exit 1: bad config\nmore"); } });
|
|
86
|
+
expect(docs.docsFrom).toBe("hcl");
|
|
87
|
+
expect(docs.note).toBe("terraform-docs failed with .terraform-docs.yml: exit 1: bad config");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("falls back when no runner is given", async () => {
|
|
91
|
+
const docs = await moduleDocs(dir);
|
|
92
|
+
expect(docs.docsFrom).toBe("hcl");
|
|
93
|
+
expect(docs.note).toBe(".terraform-docs.yml found, and no terraform-docs runner was given");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("moduleDocs on .tf.json files", () => {
|
|
98
|
+
test("reads the JSON syntax the same way", async () => {
|
|
99
|
+
const docs = await moduleDocs(join(FIXTURES, "json"));
|
|
100
|
+
expect(docs.inputs).toEqual([
|
|
101
|
+
{ name: "project", type: "string", required: true, description: "The project id.", sensitive: false, file: "main.tf.json" },
|
|
102
|
+
{ name: "region", type: "string", required: false, default: "us-central1", description: null, sensitive: false, file: "main.tf.json" },
|
|
103
|
+
]);
|
|
104
|
+
expect(docs.outputs).toEqual([{ name: "name", description: "The project id, echoed.", sensitive: false, file: "main.tf.json" }]);
|
|
105
|
+
expect(docs.providers).toEqual([{ name: "google", source: "hashicorp/google", version: "~> 6.0" }]);
|
|
106
|
+
expect(docs.readme).toBeNull();
|
|
107
|
+
expect(docs.examples).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("terraformCatalogFields and terraformCatalogSections", () => {
|
|
112
|
+
test("fill the catalog's terraform key and render on the version page", async () => {
|
|
113
|
+
const docs = await moduleDocs(join(FIXTURES, "vpc"));
|
|
114
|
+
const address = "registry.acme.dev/acme/vpc/aws";
|
|
115
|
+
const fields = terraformCatalogFields(docs, address);
|
|
116
|
+
expect(fields).toMatchObject({ address, docsFrom: "hcl", requiredVersion: ">= 1.5" });
|
|
117
|
+
expect((fields.inputs as Array<Record<string, unknown>>)[0]).not.toHaveProperty("file");
|
|
118
|
+
|
|
119
|
+
const catalog = buildReleaseCatalog(
|
|
120
|
+
{
|
|
121
|
+
releases: [{ version: 1, component: "acme/vpc/aws", env: "registry", digest: "sha256:v1", releaseVersion: "1.2.0", gitSha: "a".repeat(40), runId: "r", timestamp: "2026-10-01T00:00:00.000Z", actor: "ci" }],
|
|
122
|
+
fields: [
|
|
123
|
+
{ component: "acme/vpc/aws", engine: "terraform", fields: { address } },
|
|
124
|
+
{ component: "acme/vpc/aws", version: "1.2.0", engine: "terraform", fields },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{ generatedAt: "2026-10-27T00:00:00.000Z" },
|
|
128
|
+
);
|
|
129
|
+
const files = renderCatalogSite(catalog, terraformCatalogSections());
|
|
130
|
+
const page = new TextDecoder().decode(files.find((f) => f.path === "acme/vpc/aws/1.2.0/index.html")!.bytes);
|
|
131
|
+
expect(page).toContain("<h2>Inputs</h2>");
|
|
132
|
+
expect(page).toContain("<td>tags</td><td>map(string)</td><td>{}</td><td>no</td>");
|
|
133
|
+
expect(page).toContain("<td>flow_log_key</td><td>string</td><td>(sensitive)</td>");
|
|
134
|
+
expect(page).toContain("version = "1.2.0"");
|
|
135
|
+
expect(page).toContain("<h2>Requirements</h2>");
|
|
136
|
+
expect(page).toContain("<td>hashicorp/aws</td>");
|
|
137
|
+
const component = new TextDecoder().decode(files.find((f) => f.path === "acme/vpc/aws/index.html")!.bytes);
|
|
138
|
+
expect(component).toContain("source = "registry.acme.dev/acme/vpc/aws"");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The module docs extractor (chant#4598, terragucci#1289 bundle B): what a
|
|
3
|
+
* module's catalog page shows, read from the module's own directory.
|
|
4
|
+
*
|
|
5
|
+
* {@link moduleDocs} returns the README, the inputs (type, default,
|
|
6
|
+
* description, sensitivity, whether one is required), the outputs, the
|
|
7
|
+
* required providers and Terraform version, and the `examples/` tree. The
|
|
8
|
+
* structured fields always come from chant's own HCL parse
|
|
9
|
+
* (`parseTerraformRootDir`, ../hcl/parse.ts), `.tf` and `.tf.json` alike.
|
|
10
|
+
*
|
|
11
|
+
* When the module carries a `.terraform-docs.yml` (or `.terraform-docs.yaml`)
|
|
12
|
+
* and the caller passes a terraform-docs runner, the runner is called with
|
|
13
|
+
* that file and its markdown is kept as the page's body, since the module's
|
|
14
|
+
* author chose that layout. Without the file, without a runner, or when the
|
|
15
|
+
* runner finds no binary or fails, the page is built from the parse alone.
|
|
16
|
+
* `docsFrom` says which happened and `note` says why.
|
|
17
|
+
*
|
|
18
|
+
* {@link terraformCatalogFields} turns the docs into the release catalog's
|
|
19
|
+
* `terraform` fields (`@intentius/chant/lifecycle/release-catalog`), and
|
|
20
|
+
* {@link terraformCatalogSections} into the site's sections for a component
|
|
21
|
+
* and a version.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
import type { Hcl2Json } from "@intentius/chant/terraform/hcl2json";
|
|
26
|
+
import type { CatalogSiteSection, CatalogSiteSections } from "@intentius/chant/lifecycle/release-catalog-site";
|
|
27
|
+
import { OUTPUT_TYPE, TERRAFORM_TYPE, VARIABLE_TYPE, listTerraformFiles, parseTerraformRootDir, type TerraformEntity } from "../hcl/parse";
|
|
28
|
+
|
|
29
|
+
/** The terraform-docs config file names a module may carry, in the order they are looked for. */
|
|
30
|
+
export const TERRAFORM_DOCS_CONFIGS = [".terraform-docs.yml", ".terraform-docs.yaml"] as const;
|
|
31
|
+
|
|
32
|
+
/** One module input, a `variable` block. */
|
|
33
|
+
export interface ModuleInput {
|
|
34
|
+
name: string;
|
|
35
|
+
/** The type constraint as written, `string` or `map(object({...}))`, or null when none is declared. */
|
|
36
|
+
type: string | null;
|
|
37
|
+
/** True when the variable has no default. */
|
|
38
|
+
required: boolean;
|
|
39
|
+
/** The default as the parse reads it. Absent when required, and when the variable is sensitive. */
|
|
40
|
+
default?: unknown;
|
|
41
|
+
description: string | null;
|
|
42
|
+
sensitive: boolean;
|
|
43
|
+
/** The file that declares it. */
|
|
44
|
+
file: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** One module output. */
|
|
48
|
+
export interface ModuleOutput {
|
|
49
|
+
name: string;
|
|
50
|
+
description: string | null;
|
|
51
|
+
sensitive: boolean;
|
|
52
|
+
file: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One entry of `required_providers`. */
|
|
56
|
+
export interface ModuleProvider {
|
|
57
|
+
name: string;
|
|
58
|
+
source: string | null;
|
|
59
|
+
version: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** One directory under `examples/`. */
|
|
63
|
+
export interface ModuleExample {
|
|
64
|
+
name: string;
|
|
65
|
+
/** From the module's directory, `examples/<name>`. */
|
|
66
|
+
path: string;
|
|
67
|
+
readme: string | null;
|
|
68
|
+
/** Its configuration files, sorted. */
|
|
69
|
+
files: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Everything a catalog page shows about one module version. */
|
|
73
|
+
export interface ModuleDocs {
|
|
74
|
+
readme: string | null;
|
|
75
|
+
inputs: ModuleInput[];
|
|
76
|
+
outputs: ModuleOutput[];
|
|
77
|
+
providers: ModuleProvider[];
|
|
78
|
+
requiredVersion: string | null;
|
|
79
|
+
examples: ModuleExample[];
|
|
80
|
+
/** `terraform-docs` when the body is terraform-docs's markdown, `hcl` when the page is built from the parse alone. */
|
|
81
|
+
docsFrom: "terraform-docs" | "hcl";
|
|
82
|
+
/** terraform-docs's output, when `docsFrom` is `terraform-docs`. */
|
|
83
|
+
markdown: string | null;
|
|
84
|
+
/** The terraform-docs config file found, from the module's directory. */
|
|
85
|
+
config: string | null;
|
|
86
|
+
/** Why the parse was used when a config file was found. */
|
|
87
|
+
note: string | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Runs terraform-docs in `dir` with `config` (a path from `dir`) and returns
|
|
92
|
+
* its markdown, or null when no terraform-docs binary is installed. Injected,
|
|
93
|
+
* so a test fakes it and a product chooses how the binary is found.
|
|
94
|
+
*/
|
|
95
|
+
export type TerraformDocsRunner = (args: { dir: string; config: string }) => Promise<string | null>;
|
|
96
|
+
|
|
97
|
+
export interface ModuleDocsOptions {
|
|
98
|
+
/** The HCL parser; loaded on first use when absent. */
|
|
99
|
+
parser?: Hcl2Json;
|
|
100
|
+
/** Runs terraform-docs when the module has a config file. Without one the parse is used. */
|
|
101
|
+
terraformDocs?: TerraformDocsRunner;
|
|
102
|
+
/** The binary the module is written for (`terraform`, `tofu`), which decides the files read. */
|
|
103
|
+
binary?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const README_NAMES = ["README.md", "readme.md", "Readme.md", "README"];
|
|
107
|
+
|
|
108
|
+
function readme(dir: string): string | null {
|
|
109
|
+
for (const name of README_NAMES) {
|
|
110
|
+
const p = join(dir, name);
|
|
111
|
+
if (existsSync(p) && statSync(p).isFile()) return readFileSync(p, "utf-8");
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `${string}` as hcl2json writes an expression, or `string` as a `.tf.json` file does: the expression either way. */
|
|
117
|
+
function expression(v: unknown): string | null {
|
|
118
|
+
if (typeof v !== "string") return null;
|
|
119
|
+
const m = /^\$\{([\s\S]*)\}$/.exec(v.trim());
|
|
120
|
+
return (m ? m[1]! : v).trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const text = (v: unknown): string | null => (typeof v === "string" ? v : null);
|
|
124
|
+
|
|
125
|
+
function firstBody(v: unknown): Record<string, unknown> | undefined {
|
|
126
|
+
const b = Array.isArray(v) ? v[0] : v;
|
|
127
|
+
return b && typeof b === "object" && !Array.isArray(b) ? (b as Record<string, unknown>) : undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function providersOf(body: Record<string, unknown>): ModuleProvider[] {
|
|
131
|
+
const out: ModuleProvider[] = [];
|
|
132
|
+
const blocks = Array.isArray(body.required_providers) ? body.required_providers : body.required_providers ? [body.required_providers] : [];
|
|
133
|
+
for (const block of blocks) {
|
|
134
|
+
if (!block || typeof block !== "object") continue;
|
|
135
|
+
for (const [name, spec] of Object.entries(block as Record<string, unknown>)) {
|
|
136
|
+
// A bare string is the old `aws = "~> 4.0"` form: a version with no source.
|
|
137
|
+
if (typeof spec === "string") {
|
|
138
|
+
out.push({ name, source: null, version: spec });
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const s = firstBody(spec) ?? {};
|
|
142
|
+
out.push({ name, source: text(s.source), version: text(s.version) });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function examples(dir: string, binary: string | undefined): ModuleExample[] {
|
|
149
|
+
const root = join(dir, "examples");
|
|
150
|
+
if (!existsSync(root) || !statSync(root).isDirectory()) return [];
|
|
151
|
+
return readdirSync(root, { withFileTypes: true })
|
|
152
|
+
.filter((d) => d.isDirectory())
|
|
153
|
+
.map((d) => d.name)
|
|
154
|
+
.sort()
|
|
155
|
+
.map((name) => {
|
|
156
|
+
const path = join(root, name);
|
|
157
|
+
return { name, path: `examples/${name}`, readme: readme(path), files: [...listTerraformFiles(path, binary)].sort() };
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function fromTerraformDocs(dir: string, runner: TerraformDocsRunner | undefined): Promise<Pick<ModuleDocs, "docsFrom" | "markdown" | "config" | "note">> {
|
|
162
|
+
const config = TERRAFORM_DOCS_CONFIGS.find((name) => existsSync(join(dir, name))) ?? null;
|
|
163
|
+
if (!config) return { docsFrom: "hcl", markdown: null, config: null, note: null };
|
|
164
|
+
if (!runner) return { docsFrom: "hcl", markdown: null, config, note: `${config} found, and no terraform-docs runner was given` };
|
|
165
|
+
try {
|
|
166
|
+
const markdown = await runner({ dir, config });
|
|
167
|
+
if (markdown === null) return { docsFrom: "hcl", markdown: null, config, note: `${config} found, and no terraform-docs binary is installed` };
|
|
168
|
+
return { docsFrom: "terraform-docs", markdown, config, note: null };
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return { docsFrom: "hcl", markdown: null, config, note: `terraform-docs failed with ${config}: ${(e as Error).message.split("\n", 1)[0]}` };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Read one module's docs from `dir`. Throws what the HCL parse throws for a
|
|
176
|
+
* file it cannot read; a terraform-docs failure is never thrown, it falls
|
|
177
|
+
* back to the parse and says so in `note`.
|
|
178
|
+
*/
|
|
179
|
+
export async function moduleDocs(dir: string, options: ModuleDocsOptions = {}): Promise<ModuleDocs> {
|
|
180
|
+
const entities = [...(await parseTerraformRootDir(dir, "module", options.parser, options.binary ? { binary: options.binary } : undefined)).values()] as TerraformEntity[];
|
|
181
|
+
const inputs: ModuleInput[] = [];
|
|
182
|
+
const outputs: ModuleOutput[] = [];
|
|
183
|
+
const providers: ModuleProvider[] = [];
|
|
184
|
+
let requiredVersion: string | null = null;
|
|
185
|
+
for (const e of entities) {
|
|
186
|
+
const { address, body, file } = e.props;
|
|
187
|
+
if (e.entityType === VARIABLE_TYPE) {
|
|
188
|
+
const sensitive = body.sensitive === true;
|
|
189
|
+
const required = !("default" in body);
|
|
190
|
+
inputs.push({
|
|
191
|
+
name: address.slice("var.".length),
|
|
192
|
+
type: expression(body.type),
|
|
193
|
+
required,
|
|
194
|
+
...(!required && !sensitive ? { default: body.default } : {}),
|
|
195
|
+
description: text(body.description),
|
|
196
|
+
sensitive,
|
|
197
|
+
file,
|
|
198
|
+
});
|
|
199
|
+
} else if (e.entityType === OUTPUT_TYPE) {
|
|
200
|
+
outputs.push({ name: address.slice("output.".length), description: text(body.description), sensitive: body.sensitive === true, file });
|
|
201
|
+
} else if (e.entityType === TERRAFORM_TYPE) {
|
|
202
|
+
providers.push(...providersOf(body));
|
|
203
|
+
requiredVersion ??= text(body.required_version);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
inputs.sort((a, b) => a.name.localeCompare(b.name));
|
|
207
|
+
outputs.sort((a, b) => a.name.localeCompare(b.name));
|
|
208
|
+
providers.sort((a, b) => a.name.localeCompare(b.name));
|
|
209
|
+
return {
|
|
210
|
+
readme: readme(dir),
|
|
211
|
+
inputs,
|
|
212
|
+
outputs,
|
|
213
|
+
providers,
|
|
214
|
+
requiredVersion,
|
|
215
|
+
examples: examples(dir, options.binary),
|
|
216
|
+
...(await fromTerraformDocs(dir, options.terraformDocs)),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The release catalog's `terraform` fields for a module version: its
|
|
222
|
+
* address (`<host>/<namespace>/<name>/<system>`), the docs' structured
|
|
223
|
+
* parts, and the markdown when terraform-docs wrote it. Everything here is
|
|
224
|
+
* JSON, as the catalog's schema requires.
|
|
225
|
+
*/
|
|
226
|
+
export function terraformCatalogFields(docs: ModuleDocs, address: string): Record<string, unknown> {
|
|
227
|
+
return {
|
|
228
|
+
address,
|
|
229
|
+
inputs: docs.inputs.map(({ file: _file, ...i }) => i),
|
|
230
|
+
outputs: docs.outputs.map(({ file: _file, ...o }) => o),
|
|
231
|
+
providers: docs.providers,
|
|
232
|
+
requiredVersion: docs.requiredVersion,
|
|
233
|
+
examples: docs.examples.map((x) => ({ name: x.name, path: x.path, files: x.files })),
|
|
234
|
+
docsFrom: docs.docsFrom,
|
|
235
|
+
...(docs.readme !== null ? { readme: docs.readme } : {}),
|
|
236
|
+
...(docs.markdown !== null ? { markdown: docs.markdown } : {}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** The `terraform` fields as the site reads them back from the catalog. */
|
|
241
|
+
interface TerraformFields {
|
|
242
|
+
address?: string;
|
|
243
|
+
inputs?: Array<Omit<ModuleInput, "file">>;
|
|
244
|
+
outputs?: Array<Omit<ModuleOutput, "file">>;
|
|
245
|
+
providers?: ModuleProvider[];
|
|
246
|
+
requiredVersion?: string | null;
|
|
247
|
+
examples?: Array<{ name: string; path: string }>;
|
|
248
|
+
readme?: string;
|
|
249
|
+
markdown?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const show = (v: unknown): string => (v === undefined ? "" : typeof v === "string" ? JSON.stringify(v) : JSON.stringify(v) ?? "");
|
|
253
|
+
|
|
254
|
+
function versionSections(f: TerraformFields, version: string | null): CatalogSiteSection[] {
|
|
255
|
+
const out: CatalogSiteSection[] = [];
|
|
256
|
+
if (f.address) {
|
|
257
|
+
const pin = version ? `\n version = "${version}"` : "";
|
|
258
|
+
out.push({ title: "Usage", body: { kind: "code", text: `module "${f.address.split("/").at(-2) ?? "this"}" {\n source = "${f.address}"${pin}\n}` } });
|
|
259
|
+
}
|
|
260
|
+
if (f.inputs?.length) {
|
|
261
|
+
out.push({
|
|
262
|
+
title: "Inputs",
|
|
263
|
+
body: { kind: "table", columns: ["Name", "Type", "Default", "Required", "Description"], rows: f.inputs.map((i) => [i.name, i.type ?? "", i.sensitive ? "(sensitive)" : i.required ? "" : show(i.default), i.required ? "yes" : "no", i.description ?? ""]) },
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (f.outputs?.length) {
|
|
267
|
+
out.push({ title: "Outputs", body: { kind: "table", columns: ["Name", "Description", "Sensitive"], rows: f.outputs.map((o) => [o.name, o.description ?? "", o.sensitive ? "yes" : "no"]) } });
|
|
268
|
+
}
|
|
269
|
+
if (f.providers?.length || f.requiredVersion) {
|
|
270
|
+
const rows = (f.providers ?? []).map((p) => [p.name, p.source ?? "", p.version ?? ""]);
|
|
271
|
+
if (f.requiredVersion) rows.unshift(["terraform", "", f.requiredVersion]);
|
|
272
|
+
out.push({ title: "Requirements", body: { kind: "table", columns: ["Name", "Source", "Version"], rows } });
|
|
273
|
+
}
|
|
274
|
+
if (f.examples?.length) out.push({ title: "Examples", body: { kind: "table", columns: ["Example", "Path"], rows: f.examples.map((x) => [x.name, x.path]) } });
|
|
275
|
+
// terraform-docs's markdown is shown as text: the site renders no markdown, and escaping keeps a module's README from writing HTML into the page.
|
|
276
|
+
const body = f.markdown ?? f.readme;
|
|
277
|
+
if (body) out.push({ title: f.markdown ? "Documentation" : "README", body: { kind: "code", text: body } });
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The site sections (`renderCatalogSite`'s `sections`) for Terraform
|
|
283
|
+
* modules: usage, inputs, outputs, requirements, examples and the README or
|
|
284
|
+
* terraform-docs's markdown on each version page, read from the version's
|
|
285
|
+
* `terraform` fields, and the usage line on the component page from the
|
|
286
|
+
* component's. A component without `terraform` fields gets none.
|
|
287
|
+
*/
|
|
288
|
+
export function terraformCatalogSections(): CatalogSiteSections {
|
|
289
|
+
return {
|
|
290
|
+
component: (c) => {
|
|
291
|
+
const f = c.fields.terraform as TerraformFields | undefined;
|
|
292
|
+
return f?.address ? versionSections({ address: f.address }, c.latest && !c.latest.includes(":") ? c.latest : null) : [];
|
|
293
|
+
},
|
|
294
|
+
version: (_c, v) => {
|
|
295
|
+
const f = v.fields.terraform as TerraformFields | undefined;
|
|
296
|
+
return f ? versionSections(f, v.version) : [];
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|