@intentius/chant-lexicon-terraform 0.57.0

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 (131) hide show
  1. package/README.md +53 -0
  2. package/dist/codegen/docs-cli.d.ts +3 -0
  3. package/dist/codegen/docs-cli.d.ts.map +1 -0
  4. package/dist/codegen/docs.d.ts +11 -0
  5. package/dist/codegen/docs.d.ts.map +1 -0
  6. package/dist/codegen/generate-cli.d.ts +3 -0
  7. package/dist/codegen/generate-cli.d.ts.map +1 -0
  8. package/dist/codegen/generate.d.ts +28 -0
  9. package/dist/codegen/generate.d.ts.map +1 -0
  10. package/dist/codegen/package.d.ts +17 -0
  11. package/dist/codegen/package.d.ts.map +1 -0
  12. package/dist/composites/terraform-apply-op.d.ts +88 -0
  13. package/dist/composites/terraform-apply-op.d.ts.map +1 -0
  14. package/dist/composites/terraform-watch-op.d.ts +124 -0
  15. package/dist/composites/terraform-watch-op.d.ts.map +1 -0
  16. package/dist/config.d.ts +83 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/describe-resources.d.ts +127 -0
  19. package/dist/describe-resources.d.ts.map +1 -0
  20. package/dist/generated/index.d.ts +2 -0
  21. package/dist/generated/index.d.ts.map +1 -0
  22. package/dist/hcl/parse.d.ts +87 -0
  23. package/dist/hcl/parse.d.ts.map +1 -0
  24. package/dist/hcl/roots.d.ts +37 -0
  25. package/dist/hcl/roots.d.ts.map +1 -0
  26. package/dist/index.d.ts +12 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/integrity.json +12 -0
  29. package/dist/lint/audit-catalog.d.ts +12 -0
  30. package/dist/lint/audit-catalog.d.ts.map +1 -0
  31. package/dist/lint/audit-lineage.d.ts +21 -0
  32. package/dist/lint/audit-lineage.d.ts.map +1 -0
  33. package/dist/lint/post-synth/index.d.ts +3 -0
  34. package/dist/lint/post-synth/index.d.ts.map +1 -0
  35. package/dist/lint/post-synth/tf001.d.ts +17 -0
  36. package/dist/lint/post-synth/tf001.d.ts.map +1 -0
  37. package/dist/lint/rules/index.d.ts +5 -0
  38. package/dist/lint/rules/index.d.ts.map +1 -0
  39. package/dist/lint/rules/plan-before-apply.d.ts +30 -0
  40. package/dist/lint/rules/plan-before-apply.d.ts.map +1 -0
  41. package/dist/lsp/completions.d.ts +26 -0
  42. package/dist/lsp/completions.d.ts.map +1 -0
  43. package/dist/lsp/context.d.ts +70 -0
  44. package/dist/lsp/context.d.ts.map +1 -0
  45. package/dist/lsp/hover.d.ts +12 -0
  46. package/dist/lsp/hover.d.ts.map +1 -0
  47. package/dist/lsp/option-keys.d.ts +34 -0
  48. package/dist/lsp/option-keys.d.ts.map +1 -0
  49. package/dist/manifest.json +6 -0
  50. package/dist/meta.json +1 -0
  51. package/dist/okf/index.md +8 -0
  52. package/dist/okf/rules/TF001.md +11 -0
  53. package/dist/okf/rules/TF101.md +11 -0
  54. package/dist/op/activities/index.d.ts +22 -0
  55. package/dist/op/activities/index.d.ts.map +1 -0
  56. package/dist/op/activities/terraform.d.ts +213 -0
  57. package/dist/op/activities/terraform.d.ts.map +1 -0
  58. package/dist/op/builders.d.ts +56 -0
  59. package/dist/op/builders.d.ts.map +1 -0
  60. package/dist/package-cli.d.ts +3 -0
  61. package/dist/package-cli.d.ts.map +1 -0
  62. package/dist/plugin.d.ts +11 -0
  63. package/dist/plugin.d.ts.map +1 -0
  64. package/dist/rules/plan-before-apply.ts +143 -0
  65. package/dist/rules/tf001.ts +66 -0
  66. package/dist/serializer.d.ts +19 -0
  67. package/dist/serializer.d.ts.map +1 -0
  68. package/dist/skills/chant-terraform.md +92 -0
  69. package/dist/state-ownership.d.ts +27 -0
  70. package/dist/state-ownership.d.ts.map +1 -0
  71. package/dist/types/index.d.ts +2 -0
  72. package/dist/validate-cli.d.ts +3 -0
  73. package/dist/validate-cli.d.ts.map +1 -0
  74. package/dist/validate.d.ts +15 -0
  75. package/dist/validate.d.ts.map +1 -0
  76. package/package.json +75 -0
  77. package/src/__fixtures__/no-backend/main.tf +24 -0
  78. package/src/__fixtures__/show-state.json +68 -0
  79. package/src/__fixtures__/with-backend/main.tf +28 -0
  80. package/src/__fixtures__/with-module/main.tf +49 -0
  81. package/src/__fixtures__/with-module/modules/inner/main.tf +5 -0
  82. package/src/codegen/docs-cli.ts +4 -0
  83. package/src/codegen/docs.ts +50 -0
  84. package/src/codegen/generate-cli.ts +10 -0
  85. package/src/codegen/generate.ts +68 -0
  86. package/src/codegen/package.ts +50 -0
  87. package/src/composites/terraform-apply-op.acceptance.test.ts +138 -0
  88. package/src/composites/terraform-apply-op.test.ts +193 -0
  89. package/src/composites/terraform-apply-op.ts +166 -0
  90. package/src/composites/terraform-watch-op.test.ts +184 -0
  91. package/src/composites/terraform-watch-op.ts +204 -0
  92. package/src/config.ts +76 -0
  93. package/src/describe-resources.test.ts +342 -0
  94. package/src/describe-resources.ts +357 -0
  95. package/src/generated/index.d.ts +2 -0
  96. package/src/generated/index.ts +4 -0
  97. package/src/generated/lexicon-terraform.json +1 -0
  98. package/src/hcl/parse.ts +235 -0
  99. package/src/hcl/roots.ts +67 -0
  100. package/src/index.ts +65 -0
  101. package/src/lint/audit-catalog.ts +30 -0
  102. package/src/lint/audit-lineage.ts +21 -0
  103. package/src/lint/audit.test.ts +45 -0
  104. package/src/lint/post-synth/index.ts +7 -0
  105. package/src/lint/post-synth/post-synth.test.ts +95 -0
  106. package/src/lint/post-synth/tf001.ts +66 -0
  107. package/src/lint/rules/index.ts +7 -0
  108. package/src/lint/rules/plan-before-apply.test.ts +111 -0
  109. package/src/lint/rules/plan-before-apply.ts +143 -0
  110. package/src/lsp/completions.test.ts +120 -0
  111. package/src/lsp/completions.ts +101 -0
  112. package/src/lsp/context.test.ts +152 -0
  113. package/src/lsp/context.ts +349 -0
  114. package/src/lsp/hover.test.ts +82 -0
  115. package/src/lsp/hover.ts +44 -0
  116. package/src/lsp/option-keys.ts +106 -0
  117. package/src/op/activities/index.ts +48 -0
  118. package/src/op/activities/registry.test.ts +29 -0
  119. package/src/op/activities/terraform.test.ts +445 -0
  120. package/src/op/activities/terraform.ts +469 -0
  121. package/src/op/builders.test.ts +90 -0
  122. package/src/op/builders.ts +96 -0
  123. package/src/package-cli.ts +21 -0
  124. package/src/plugin.test.ts +271 -0
  125. package/src/plugin.ts +157 -0
  126. package/src/serializer.test.ts +26 -0
  127. package/src/serializer.ts +26 -0
  128. package/src/skills/chant-terraform.md +92 -0
  129. package/src/state-ownership.ts +32 -0
  130. package/src/validate-cli.ts +5 -0
  131. package/src/validate.ts +28 -0
@@ -0,0 +1,213 @@
1
+ /**
2
+ * terraform Op activities (#2086) — init, plan, apply and show against a root
3
+ * module named in the project's `terraform.roots` config namespace (#2083).
4
+ *
5
+ * Shaped after `lexicons/k3s/src/op/activities/k3s.ts`: `promisify(exec)` with
6
+ * the caller's `AbortSignal` forwarded so a local timeout or Ctrl-C kills the
7
+ * child, `safeHeartbeat` on an interval around the long calls, and every
8
+ * command and environment string produced by a pure exported function so a
9
+ * test can assert on it without running terraform.
10
+ *
11
+ * Two invariants hold for every call:
12
+ *
13
+ * - `TF_IN_AUTOMATION=1` is in the environment, which is what tells terraform
14
+ * it is not talking to a person.
15
+ * - `-input=false` is on every command that accepts it, so a missing variable
16
+ * fails the step instead of blocking on a prompt nobody will answer.
17
+ * `terraform show` is the one command that does not accept the flag
18
+ * (terraform answers `flag provided but not defined: -input`), so it
19
+ * carries the environment variable alone.
20
+ *
21
+ * `terraformApply` takes a saved plan file and nothing else. A bare apply
22
+ * re-plans at apply time, which is exactly the gap an approval gate exists to
23
+ * close, so it is refused rather than offered.
24
+ */
25
+ import type { TerraformConfig, TerraformRootConfig } from "../../config.js";
26
+ /** Plan file written into the root directory when a step names none. */
27
+ export declare const DEFAULT_PLAN_FILE = "chant.tfplan";
28
+ /** Binary used when the `terraform` namespace records no preference. */
29
+ export declare const DEFAULT_TERRAFORM_BINARY = "terraform";
30
+ /** Fields every activity here takes: which root, and where the project is. */
31
+ export interface TerraformRootArgs {
32
+ /**
33
+ * Key into `terraform.roots`. Not a directory — the directory, workspace,
34
+ * var files and backend config all come from the named entry, so an Op step
35
+ * cannot drift from what the project declared.
36
+ */
37
+ root: string;
38
+ /**
39
+ * Directory to start the `chant.config.*` search from. Default:
40
+ * `process.cwd()`, which is the project root under `chant run`.
41
+ */
42
+ cwd?: string;
43
+ }
44
+ export interface TerraformInitArgs extends TerraformRootArgs {
45
+ /** `-upgrade`: re-resolve provider and module versions within constraints. */
46
+ upgrade?: boolean;
47
+ /** `-reconfigure`: ignore any existing backend state and configure afresh. */
48
+ reconfigure?: boolean;
49
+ }
50
+ export interface TerraformPlanArgs extends TerraformRootArgs {
51
+ /** Plan file to write, relative to the root directory. Default: {@link DEFAULT_PLAN_FILE}. */
52
+ planFile?: string;
53
+ /** `-destroy`: plan the removal of everything the root manages. */
54
+ destroy?: boolean;
55
+ }
56
+ export interface TerraformApplyArgs extends TerraformRootArgs {
57
+ /**
58
+ * The saved plan file to apply, relative to the root directory — normally
59
+ * `plan.out.planFile`, the Plan step's own output. Required: this activity
60
+ * has no bare-apply mode.
61
+ */
62
+ planFile: string;
63
+ }
64
+ export interface TerraformShowArgs extends TerraformRootArgs {
65
+ /** Show this saved plan file. Omitted, the activity shows current state. */
66
+ planFile?: string;
67
+ }
68
+ /** What {@link terraformInit} resolved. */
69
+ export interface TerraformInitResult {
70
+ /** Absolute path of the root module directory that was initialized. */
71
+ dir: string;
72
+ /** Workspace the run selected via `TF_WORKSPACE`, when the root names one. */
73
+ workspace?: string;
74
+ }
75
+ /** Counts projected out of a plan's `resource_changes`. */
76
+ export interface PlanChangeCounts {
77
+ /** Resources to create. A replace counts here and in `destroys`, as terraform's own summary does. */
78
+ adds: number;
79
+ /** Resources to update in place. */
80
+ changes: number;
81
+ /** Resources to destroy. This is the number an approval gate exists for. */
82
+ destroys: number;
83
+ }
84
+ /** What {@link terraformPlan} resolved. Carries the plan itself, so no later step re-plans. */
85
+ export interface TerraformPlanResult extends PlanChangeCounts {
86
+ /** `true` when terraform reported exit 2 under `-detailed-exitcode`: the plan proposes changes. */
87
+ changed: boolean;
88
+ /** The written plan file, relative to `dir` — hand this straight to {@link terraformApply}. */
89
+ planFile: string;
90
+ /** Absolute path of the root module directory. */
91
+ dir: string;
92
+ /** `terraform show -json <planFile>`, parsed. */
93
+ json: unknown;
94
+ /** `terraform show -no-color <planFile>` — the human-readable plan. */
95
+ text: string;
96
+ }
97
+ /** What {@link terraformApply} resolved. */
98
+ export interface TerraformApplyResult {
99
+ /** The plan file that was applied. */
100
+ planFile: string;
101
+ /** Absolute path of the root module directory. */
102
+ dir: string;
103
+ /** Always `true` on success; the activity throws otherwise. */
104
+ applied: boolean;
105
+ }
106
+ /** What {@link terraformShow} resolved. */
107
+ export interface TerraformShowResult extends PlanChangeCounts {
108
+ /** Whether the output describes a saved plan or current state. */
109
+ source: "plan" | "state";
110
+ /** The `-json` output, parsed. */
111
+ json: unknown;
112
+ /** The `-no-color` output. */
113
+ text: string;
114
+ /** Absolute path of the root module directory. */
115
+ dir: string;
116
+ /** Present when `source` is `"plan"`. */
117
+ planFile?: string;
118
+ }
119
+ /**
120
+ * Quote a command-line argument for the shell `exec` runs it through, leaving
121
+ * ordinary paths and `key=value` pairs untouched so command strings stay
122
+ * readable in logs and in tests.
123
+ */
124
+ export declare function quoteArg(value: string): string;
125
+ /** Which CLI drives the roots: `terraform.binary`, defaulting to `terraform`. */
126
+ export declare function terraformBinary(config?: TerraformConfig): string;
127
+ /**
128
+ * Environment for every invocation. `TF_IN_AUTOMATION` suppresses the
129
+ * "run terraform apply next" hand-holding and is terraform's own signal that
130
+ * no human is watching; `TF_WORKSPACE` is how a workspace gets selected
131
+ * without a separate `terraform workspace select` round-trip.
132
+ */
133
+ export declare function terraformEnvironment(root?: Pick<TerraformRootConfig, "workspace">): Record<string, string>;
134
+ /** `terraform init`, with the root's `backendConfig` as `-backend-config=k=v` flags. */
135
+ export declare function terraformInitCommand(opts: {
136
+ binary: string;
137
+ backendConfig?: Record<string, string>;
138
+ upgrade?: boolean;
139
+ reconfigure?: boolean;
140
+ }): string;
141
+ /**
142
+ * `terraform plan`. `-detailed-exitcode` is what makes the run answerable:
143
+ * 0 means no changes, 2 means changes, and anything else is a failure. See
144
+ * {@link terraformPlan}.
145
+ */
146
+ export declare function terraformPlanCommand(opts: {
147
+ binary: string;
148
+ planFile: string;
149
+ varFiles?: string[];
150
+ destroy?: boolean;
151
+ }): string;
152
+ /** `terraform apply <planFile>` — a saved plan, never a bare apply. */
153
+ export declare function terraformApplyCommand(opts: {
154
+ binary: string;
155
+ planFile: string;
156
+ }): string;
157
+ /**
158
+ * `terraform show`. The one command here that takes no `-input` flag
159
+ * (terraform answers `flag provided but not defined: -input`), so automation
160
+ * rests on `TF_IN_AUTOMATION` alone. Show reads an artifact and prompts for
161
+ * nothing in the first place.
162
+ */
163
+ export declare function terraformShowCommand(opts: {
164
+ binary: string;
165
+ json: boolean;
166
+ planFile?: string;
167
+ }): string;
168
+ /**
169
+ * Project a plan's `resource_changes` into add/change/destroy counts, the same
170
+ * three terraform prints at the end of a plan. A replace (`["delete","create"]`)
171
+ * counts as one add and one destroy, exactly as terraform reports it.
172
+ */
173
+ export declare function countPlanChanges(planJson: unknown): PlanChangeCounts;
174
+ /**
175
+ * `terraform init` in the named root, with the root's `backendConfig` supplied
176
+ * as `-backend-config` flags. Uses the longInfra profile: init downloads
177
+ * providers and modules, a network call of unpredictable size.
178
+ */
179
+ export declare function terraformInit(args: TerraformInitArgs, signal?: AbortSignal): Promise<TerraformInitResult>;
180
+ /**
181
+ * `terraform plan -detailed-exitcode -out=<planFile>` in the named root, then
182
+ * `terraform show` over the written plan in both `-json` and `-no-color` form.
183
+ *
184
+ * The exit code is the answer, not an error condition: 0 is a plan with no
185
+ * changes, 2 is a plan with changes, and anything else, 1 included, is a
186
+ * failure thrown with terraform's own stderr attached. Both `show` renders
187
+ * come back with the result, so a gate, a report or an apply downstream reads
188
+ * the plan that ran instead of planning again against a moved world.
189
+ *
190
+ * Uses the longInfra profile: plan refreshes every resource against its
191
+ * provider.
192
+ */
193
+ export declare function terraformPlan(args: TerraformPlanArgs, signal?: AbortSignal): Promise<TerraformPlanResult>;
194
+ /**
195
+ * `terraform apply <planFile>` in the named root. A saved plan only: without
196
+ * one, apply re-plans at apply time and acts on something no gate ever saw,
197
+ * so a missing `planFile` is refused here rather than quietly widened into a
198
+ * bare apply.
199
+ *
200
+ * Uses the longInfra profile.
201
+ */
202
+ export declare function terraformApply(args: TerraformApplyArgs, signal?: AbortSignal): Promise<TerraformApplyResult>;
203
+ /**
204
+ * `terraform show` over current state, or over a saved plan when `planFile` is
205
+ * given. Returns the parsed `-json` output and the `-no-color` render, plus
206
+ * the add/change/destroy counts when the subject is a plan. State has no
207
+ * change set, so for state the three counts are zero.
208
+ *
209
+ * Uses the fastIdempotent profile: show reads an artifact and calls no
210
+ * provider.
211
+ */
212
+ export declare function terraformShow(args: TerraformShowArgs, signal?: AbortSignal): Promise<TerraformShowResult>;
213
+ //# sourceMappingURL=terraform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terraform.d.ts","sourceRoot":"","sources":["../../../src/op/activities/terraform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Typed step-builder wrappers for this lexicon's activities, copied from
3
+ * `lexicons/k3s/src/op/builders.ts` (chant #1288 Stage 2). `opts`'s type in
4
+ * each wrapper below IS the activity's own `*Args` interface, via
5
+ * `Omit`/`WithStepRefs`, never restated: rename a field on the activity and
6
+ * the builder's callers stop compiling.
7
+ *
8
+ * The `root` key is positional in every wrapper, because naming the root is
9
+ * the one thing a terraform step cannot be authored without.
10
+ *
11
+ * `id` routes to the step's `id` field rather than into `args`. That matters
12
+ * here more than anywhere else in the lexicon: `.out` throws without an id
13
+ * (`packages/core/src/op/builders.ts`), and `.out` is how an Apply step names
14
+ * the Plan step's `planFile` as a `StepOutputRef` instead of guessing the
15
+ * path a second time.
16
+ */
17
+ import { type ActivityStep, type NamedActivityStep, type WithStepRefs } from "@intentius/chant/op";
18
+ import type { TerraformInitArgs, TerraformPlanArgs, TerraformApplyArgs, TerraformShowArgs } from "./activities/terraform.js";
19
+ /** Extra opts every wrapper below accepts alongside its activity's own fields. */
20
+ type StepOpts = {
21
+ profile?: ActivityStep["profile"];
22
+ id?: string;
23
+ };
24
+ /**
25
+ * `terraform init` in the named root — the fully typed twin of the
26
+ * `terraformInit` activity. `opts` is {@link TerraformInitArgs} itself, minus
27
+ * the positional `root`. Defaults to the `longInfra` profile: init downloads
28
+ * providers.
29
+ */
30
+ export declare const terraformInit: (root: string, opts?: WithStepRefs<Omit<TerraformInitArgs, "root">> & StepOpts) => NamedActivityStep;
31
+ /**
32
+ * `terraform plan` in the named root, writing a saved plan. `opts` is
33
+ * {@link TerraformPlanArgs} itself, minus the positional `root`. Defaults to
34
+ * the `longInfra` profile: plan refreshes every resource against its provider.
35
+ *
36
+ * Give this step an `id` when a later step applies its plan — `plan.out.planFile`
37
+ * is the reference an Apply step takes.
38
+ */
39
+ export declare const terraformPlan: (root: string, opts?: WithStepRefs<Omit<TerraformPlanArgs, "root">> & StepOpts) => NamedActivityStep;
40
+ /**
41
+ * `terraform apply <planFile>` in the named root — the fully typed twin of the
42
+ * `terraformApply` activity. `opts` is {@link TerraformApplyArgs} itself,
43
+ * minus the positional `root`, so `planFile` stays required at the call site
44
+ * and the activity's bare-apply refusal is a compile error rather than a
45
+ * runtime one. Defaults to the `longInfra` profile.
46
+ */
47
+ export declare const terraformApply: (root: string, opts: WithStepRefs<Omit<TerraformApplyArgs, "root">> & StepOpts) => NamedActivityStep;
48
+ /**
49
+ * `terraform show` in the named root, over state or over a saved plan. `opts`
50
+ * is {@link TerraformShowArgs} itself, minus the positional `root`. Defaults
51
+ * to the `fastIdempotent` profile: show reads an artifact and calls no
52
+ * provider.
53
+ */
54
+ export declare const terraformShow: (root: string, opts?: WithStepRefs<Omit<TerraformShowArgs, "root">> & StepOpts) => NamedActivityStep;
55
+ export {};
56
+ //# sourceMappingURL=builders.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builders.d.ts","sourceRoot":"","sources":["../../src/op/builders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,kFAAkF;AAClF,KAAK,QAAQ,GAAG;IAAE,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,OAAO,YAAY,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,KAC9D,iBAGF,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,OAAO,YAAY,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,KAC9D,iBAGF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GACzB,MAAM,MAAM,EACZ,MAAM,YAAY,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,KAC9D,iBAGF,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,MAAM,MAAM,EACZ,OAAO,YAAY,CAAC,IAAI,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,KAC9D,iBAOF,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
3
+ //# sourceMappingURL=package-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-cli.d.ts","sourceRoot":"","sources":["../src/package-cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import type { LexiconPlugin } from "@intentius/chant/lexicon";
2
+ /**
3
+ * terraform lexicon plugin.
4
+ *
5
+ * There is no upstream schema to pin here: Terraform's resource surface lives
6
+ * in provider registries, one schema per provider, and this lexicon reads the
7
+ * HCL an estate already has rather than generating types for it. So `spec/`
8
+ * and `codegen/` stay near-empty and there is no `upstreamPin`.
9
+ */
10
+ export declare const terraformPlugin: LexiconPlugin;
11
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAwB9D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,aA4H7B,CAAC"}
@@ -0,0 +1,143 @@
1
+ import * as ts from "typescript";
2
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
3
+
4
+ /**
5
+ * TF101: terraformApply's planFile must reference a preceding terraformPlan step.
6
+ *
7
+ * Applying a plan file chosen by a string means the applied plan can be
8
+ * stale, hand-edited, or simply the wrong one — the entire point of the
9
+ * plan/apply split (review exactly what will change, then apply exactly
10
+ * that) is defeated the moment the path is spelled out in source instead of
11
+ * carried forward from the `terraformPlan` step that produced it.
12
+ *
13
+ * The two builders this rule is written against (`terraformPlan`,
14
+ * `terraformApply`) don't exist yet (#2086 ships them in parallel), so this
15
+ * matches purely on callee identifier name — same posture as
16
+ * `lexicons/k3s/src/lint/rules/token-literal.ts`.
17
+ *
18
+ * Accepts exactly the two authoring forms that produce a genuine step-output
19
+ * reference (`packages/core/src/op/step-output-ref.ts`):
20
+ * - `stepOutput(<ident>, "planFile")`
21
+ * - `<ident>.out.planFile`
22
+ * where `<ident>` is bound (`const <ident> = terraformPlan(...)`) in the same
23
+ * file. Flags a string literal, a template literal, or one of the two
24
+ * accepted reference forms pointing at an identifier that resolves to
25
+ * something other than a `terraformPlan` call. When `<ident>` cannot be
26
+ * resolved at all (not declared anywhere in the file), this rule stays
27
+ * silent — the same deliberately shallow "bail rather than guess" posture
28
+ * `validateStepOutputRefs` takes, since the runtime brand on a real
29
+ * `StepOutputRef` is invisible to a source-level AST match.
30
+ */
31
+ export const planBeforeApplyRule: LintRule = {
32
+ id: "TF101",
33
+ severity: "error",
34
+ category: "correctness",
35
+ description: "terraformApply's planFile must reference a preceding terraformPlan step",
36
+
37
+ check(context: LintContext): LintDiagnostic[] {
38
+ const diagnostics: LintDiagnostic[] = [];
39
+ const source = context.sourceFile;
40
+
41
+ /** Identifier name bound to a `terraformPlan(...)` call, to whether it truly is one. */
42
+ const bindings = new Map<string, boolean>();
43
+
44
+ const calleeName = (expr: ts.Expression): string | undefined =>
45
+ ts.isIdentifier(expr) ? expr.text : ts.isPropertyAccessExpression(expr) ? expr.name.text : undefined;
46
+
47
+ const collectBindings = (node: ts.Node) => {
48
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
49
+ const init = node.initializer;
50
+ if (ts.isCallExpression(init) || ts.isNewExpression(init)) {
51
+ bindings.set(node.name.text, calleeName(init.expression) === "terraformPlan");
52
+ }
53
+ }
54
+ ts.forEachChild(node, collectBindings);
55
+ };
56
+ collectBindings(source);
57
+
58
+ const flag = (node: ts.Node, message: string) => {
59
+ const { line, character } = source.getLineAndCharacterOfPosition(node.getStart(source));
60
+ diagnostics.push({
61
+ ruleId: "TF101",
62
+ severity: "error",
63
+ message,
64
+ file: context.filePath,
65
+ line: line + 1,
66
+ column: character + 1,
67
+ });
68
+ };
69
+
70
+ /**
71
+ * `stepOutput(<ident>, "planFile")` or `<ident>.out.planFile`, returning
72
+ * the referenced identifier's name, or `undefined` if `initializer`
73
+ * isn't one of those two shapes.
74
+ */
75
+ const referencedIdent = (initializer: ts.Expression): string | undefined => {
76
+ if (ts.isCallExpression(initializer) && calleeName(initializer.expression) === "stepOutput") {
77
+ const arg = initializer.arguments[0];
78
+ return arg && ts.isIdentifier(arg) ? arg.text : undefined;
79
+ }
80
+ if (
81
+ ts.isPropertyAccessExpression(initializer) &&
82
+ initializer.name.text === "planFile" &&
83
+ ts.isPropertyAccessExpression(initializer.expression) &&
84
+ initializer.expression.name.text === "out" &&
85
+ ts.isIdentifier(initializer.expression.expression)
86
+ ) {
87
+ return initializer.expression.expression.text;
88
+ }
89
+ return undefined;
90
+ };
91
+
92
+ const checkPlanFile = (obj: ts.ObjectLiteralExpression) => {
93
+ for (const prop of obj.properties) {
94
+ if (!ts.isPropertyAssignment(prop)) continue;
95
+ const name = ts.isIdentifier(prop.name)
96
+ ? prop.name.text
97
+ : ts.isStringLiteral(prop.name)
98
+ ? prop.name.text
99
+ : undefined;
100
+ if (name !== "planFile") continue;
101
+
102
+ const value = prop.initializer;
103
+ if (ts.isStringLiteral(value) || ts.isNoSubstitutionTemplateLiteral(value) || ts.isTemplateExpression(value)) {
104
+ flag(
105
+ value,
106
+ "`planFile` is a literal path in source. Pass the preceding `terraformPlan` step's output " +
107
+ '(`stepOutput(plan, "planFile")` or `plan.out.planFile`), not a hardcoded path.',
108
+ );
109
+ continue;
110
+ }
111
+
112
+ const ident = referencedIdent(value);
113
+ if (ident === undefined) continue; // not one of the two accepted forms — out of this rule's reach
114
+ const isPlan = bindings.get(ident);
115
+ if (isPlan === false) {
116
+ flag(
117
+ value,
118
+ `\`planFile\` references "${ident}", which is not bound to a \`terraformPlan(...)\` call. ` +
119
+ "Apply only a plan this Op itself produced.",
120
+ );
121
+ }
122
+ // isPlan === true: paired correctly.
123
+ // isPlan === undefined: `ident` isn't declared anywhere in this file — cannot resolve
124
+ // statically, so stay silent rather than guess.
125
+ }
126
+ };
127
+
128
+ const visit = (node: ts.Node) => {
129
+ if (ts.isCallExpression(node) && calleeName(node.expression) === "terraformApply") {
130
+ // The options object may be the first argument (`terraformApply({ planFile })`)
131
+ // or follow a positional one (`terraformApply("app", { planFile })`, the
132
+ // k3s-shaped builder signature); inspect every object-literal argument.
133
+ for (const arg of node.arguments) {
134
+ if (ts.isObjectLiteralExpression(arg)) checkPlanFile(arg);
135
+ }
136
+ }
137
+ ts.forEachChild(node, visit);
138
+ };
139
+
140
+ visit(source);
141
+ return diagnostics;
142
+ },
143
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * TF001: a root module keeps its state on local disk.
3
+ *
4
+ * A `terraform` block with no `backend "<type>"` and no `cloud {}` falls back
5
+ * to the local backend, which puts `terraform.tfstate` in the working
6
+ * directory. That file holds every attribute of every managed resource,
7
+ * secrets included, and it is not shared, not locked, and not versioned. The
8
+ * first apply from a second machine or a CI runner starts from an empty state
9
+ * and proposes to create the estate again.
10
+ *
11
+ * One diagnostic per root, fired from the root's `terraform` block. A root
12
+ * with no `terraform` block at all is not flagged: it declares no version
13
+ * constraints either, and the missing block is a different finding.
14
+ */
15
+
16
+ import type {
17
+ PostSynthCheck,
18
+ PostSynthContext,
19
+ PostSynthDiagnostic,
20
+ } from "@intentius/chant/lint/post-synth";
21
+ import { isResourceDeclarable } from "@intentius/chant/declarable";
22
+ import { TERRAFORM_TYPE, type BlockBody } from "../../hcl/parse";
23
+
24
+ /** `backend`/`cloud` are blocks, so hcl2json encodes them as a value under the key. */
25
+ function hasBlock(body: BlockBody, key: string): boolean {
26
+ const value = body[key];
27
+ if (value === undefined || value === null) return false;
28
+ if (Array.isArray(value)) return value.length > 0;
29
+ return typeof value === "object" ? Object.keys(value as object).length > 0 : true;
30
+ }
31
+
32
+ export const tf001: PostSynthCheck = {
33
+ id: "TF001",
34
+ description: "Root module declares no remote backend",
35
+
36
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
37
+ const diagnostics: PostSynthDiagnostic[] = [];
38
+ /** Root name to the entity key of the first `terraform` block seen for it. */
39
+ const flagged = new Set<string>();
40
+
41
+ for (const [name, entity] of ctx.entities) {
42
+ if (entity.entityType !== TERRAFORM_TYPE) continue;
43
+ if (!isResourceDeclarable(entity)) continue;
44
+ const props = entity.props as { root?: unknown; body?: unknown };
45
+ const root = typeof props.root === "string" ? props.root : "";
46
+ if (flagged.has(root)) continue;
47
+
48
+ const body = (typeof props.body === "object" && props.body !== null ? props.body : {}) as BlockBody;
49
+ if (hasBlock(body, "backend") || hasBlock(body, "cloud")) continue;
50
+
51
+ flagged.add(root);
52
+ diagnostics.push({
53
+ checkId: "TF001",
54
+ severity: "warning",
55
+ message:
56
+ `Root module "${root}" declares no remote backend. State falls back to a local ` +
57
+ "terraform.tfstate, which is unshared, unlocked and holds every resource attribute in " +
58
+ 'plaintext. Add a `backend "<type>"` or a `cloud {}` block to the terraform block.',
59
+ entity: name,
60
+ lexicon: "terraform",
61
+ });
62
+ }
63
+
64
+ return diagnostics;
65
+ },
66
+ };
@@ -0,0 +1,19 @@
1
+ import type { Serializer } from "@intentius/chant";
2
+ /**
3
+ * terraform serializer.
4
+ *
5
+ * A stub, deliberately. The entities this lexicon carries come from `.tf`
6
+ * files that already exist on disk (`buildRoots()` parses the roots named in
7
+ * `terraform.roots`), so there is nothing for chant to write back: emitting
8
+ * HCL here would put a second, generated copy of the estate beside the
9
+ * authored one. The lexicon's value is what the entities let the rest of
10
+ * chant do with the root (post-synth checks, `chant audit`, the Op surface),
11
+ * not a rendered artifact.
12
+ *
13
+ * `name` and `rulePrefix` are the two members the `Serializer` contract
14
+ * requires, and both are load-bearing: `name` is the key `build()` files this
15
+ * lexicon's output under, and `rulePrefix` is what every `TF*` id is checked
16
+ * against.
17
+ */
18
+ export declare const terraformSerializer: Serializer;
19
+ //# sourceMappingURL=serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../src/serializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAOjC,CAAC"}
@@ -0,0 +1,92 @@
1
+ ---
2
+ skill: chant-terraform
3
+ description: Read an existing Terraform root module into chant's build and audit, and drive it with the init/plan/apply Ops
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Terraform as an Existing Estate
8
+
9
+ ## What this lexicon covers
10
+
11
+ There is no generated resource surface here, and nothing is written back over the HCL. Terraform's own `.tf` files stay the only source of truth, and `terraform apply` keeps working exactly as before. What chant adds is: one entity per HCL block, read at build time, so the post-synth checks and `chant audit` have something to see; and Op activities that drive `init`/`plan`/`apply`/`show` against a saved plan.
12
+
13
+ ## Naming your roots
14
+
15
+ Add the `terraform` namespace to `chant.config.ts`. Importing the package brings the key into `ChantConfig`.
16
+
17
+ ```ts
18
+ import type { ChantConfig } from "@intentius/chant/config";
19
+ import "@intentius/chant-lexicon-terraform";
20
+
21
+ export default {
22
+ lexicons: ["terraform"],
23
+ terraform: {
24
+ binary: "terraform", // or "tofu" — the two are wire-compatible for everything this lexicon does
25
+ roots: {
26
+ app: { dir: "./terraform/app", workspace: "prod", varFiles: ["prod.tfvars"] },
27
+ },
28
+ },
29
+ } satisfies ChantConfig;
30
+ ```
31
+
32
+ `dir` is the only required field, resolved against the project root (where `chant.config.ts` lives), not against the cwd a step happens to run from. The root's name (`app` above) is the entity-key prefix and the string every Op step and builder takes as `root` — keep it stable once other declarations reference it.
33
+
34
+ ## What a build produces
35
+
36
+ Every block of every configured root becomes one entity, keyed `<root>/<address>`: `terraform { }` -> `Terraform::Terraform`, `provider "x" {}` -> `Terraform::Provider`, `resource "x" "y" {}` -> `Terraform::Resource`, `data "x" "y" {}` -> `Terraform::Data`, `module "x" {}` -> `Terraform::Module`, `variable "x" {}` -> `Terraform::Variable`, `output "x" {}` -> `Terraform::Output`, `locals {}` -> `Terraform::Locals`. Each carries `props.address`, `props.body` (the block, verbatim), `props.file` and `props.root`. A root whose `dir` doesn't exist, or whose HCL the parser refuses, warns and contributes no entities — the rest of the build is unaffected.
37
+
38
+ ## The one thing to check before anything else
39
+
40
+ `TF001` fires once per root whose `terraform` block declares neither a `backend "<type>"` nor a `cloud {}`. That root's state is a local `terraform.tfstate`: unshared, unlocked, holding every resource attribute in plaintext. Add a real backend before anything else touches this root:
41
+
42
+ ```hcl
43
+ terraform {
44
+ backend "s3" {
45
+ bucket = "acme-tfstate"
46
+ key = "app/terraform.tfstate"
47
+ region = "us-east-1"
48
+ }
49
+ }
50
+ ```
51
+
52
+ `TF101` is the second rule, and it's about the Ops below: a `terraformApply` step's `planFile` must reference the output of a preceding `terraformPlan` step (`plan.out.planFile`), never a literal path. Applying an unreviewed plan, or a plan somebody hand-edited on disk, is exactly what a saved-plan discipline exists to prevent.
53
+
54
+ ## Driving a root with an Op
55
+
56
+ `TerraformApplyOp` is Init, Plan, an optional approval Gate, then Apply — Init and Plan on the `longInfra` profile, the Gate's `show` step on `fastIdempotent`:
57
+
58
+ ```ts
59
+ import { TerraformApplyOp } from "@intentius/chant-lexicon-terraform";
60
+
61
+ export const { op } = TerraformApplyOp({
62
+ name: "app-apply",
63
+ root: "app",
64
+ gate: "on-destroy", // default — "always" gates every apply, "never" drops the Gate phase entirely
65
+ });
66
+ ```
67
+
68
+ `gate: "never"` is what makes the Op runnable under the local executor (`chant run`); any other mode emits a Gate phase, and a gate is durable-runtime work the local executor refuses. The Gate phase always shows the saved plan first and reports its `destroys` count as a `Destroys` search attribute, because `GateStep` carries no condition to branch on at build time — the approver sees what's at stake before approving, rather than the Op deciding for them.
69
+
70
+ Terraform has no automatic rollback, so `compensate: true` with no command throws at build time, naming the Op, rather than warning once an apply has already half-run:
71
+
72
+ ```ts
73
+ export const { op } = TerraformApplyOp({
74
+ name: "app-apply-gated",
75
+ root: "app",
76
+ gate: "always",
77
+ compensate: { command: "terraform destroy -auto-approve" },
78
+ });
79
+ ```
80
+
81
+ Reach for the four builders directly (`terraformInit`, `terraformPlan`, `terraformApply`, `terraformShow`) when a phase shape other than Init/Plan/Gate/Apply is needed — `plan.out.planFile` is how a later step references an earlier Plan step's saved output, and it only resolves when the Plan step carries an `id`.
82
+
83
+ ## Where a running estate's ownership answer lives
84
+
85
+ Terraform's own state file is the ownership answer for what it manages — this lexicon doesn't add a second one. See the lexicon's "Live Observation" doc page for how a scheduled watch reads that state.
86
+
87
+ ## Rules
88
+
89
+ | Rule | Severity | What it catches |
90
+ |---|---|---|
91
+ | TF001 | error (post-synth) | root module declares no remote backend |
92
+ | TF101 | error | `terraformApply`'s `planFile` isn't a preceding `terraformPlan` step's output |
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Terraform's ownership channel (#2087): the state file.
3
+ *
4
+ * Its own module, and a tiny one, for the same reason cedar's
5
+ * `src/avp/ownership.ts` is: `plugin.ts` declares `ownershipChannel` eagerly
6
+ * while `describeResources` is behind a dynamic import, so the keys cannot
7
+ * live in the reader without dragging the reader (and `node:child_process`
8
+ * with it) into every plugin load.
9
+ *
10
+ * The design record is the module doc of `./describe-resources.ts` and the
11
+ * reader-facing version is `docs/pages/observation.mdx`. The short form:
12
+ * chant elsewhere answers "is this mine?" from a marker it stamped onto the
13
+ * live resource; terraform answers it from the state file, which already
14
+ * records exactly which addresses a configuration manages. Nothing is ever
15
+ * stamped, so nothing is ever written back over an estate's own tags.
16
+ */
17
+ import type { ChannelKeys } from "@intentius/chant/ownership";
18
+ /**
19
+ * Where the verdict is read from, rather than what is written.
20
+ *
21
+ * {@link ChannelKeys} names tag or label keys everywhere else, because
22
+ * everywhere else the marker is a key/value pair on the resource. These three
23
+ * name the state file, the configured root that owns that state, and the
24
+ * workspace the root selects.
25
+ */
26
+ export declare const TERRAFORM_STATE_OWNERSHIP_KEYS: ChannelKeys;
27
+ //# sourceMappingURL=state-ownership.d.ts.map