@quadnix/octo-build 0.0.17 → 0.1.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 (195) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +25 -3
  3. package/dist/commands/apply/apply.command.d.ts +25 -0
  4. package/dist/commands/apply/apply.command.js +136 -0
  5. package/dist/commands/apply/apply.command.js.map +1 -0
  6. package/dist/commands/apply/index.d.ts +1 -0
  7. package/dist/commands/apply/index.js +2 -0
  8. package/dist/commands/apply/index.js.map +1 -0
  9. package/dist/commands/build/build.command.d.ts +1 -1
  10. package/dist/commands/build/build.command.js +4 -4
  11. package/dist/commands/build/build.command.js.map +1 -1
  12. package/dist/commands/build/main.d.ts +22 -0
  13. package/dist/commands/build/main.js +22 -0
  14. package/dist/commands/build/main.js.map +1 -1
  15. package/dist/commands/build/parsers/build-file.parser.d.ts +6 -0
  16. package/dist/commands/build/parsers/build-file.parser.js +6 -0
  17. package/dist/commands/build/parsers/build-file.parser.js.map +1 -1
  18. package/dist/commands/build/streams/stream-manager.d.ts +6 -0
  19. package/dist/commands/build/streams/stream-manager.js +7 -1
  20. package/dist/commands/build/streams/stream-manager.js.map +1 -1
  21. package/dist/commands/commit/commit.command.d.ts +22 -0
  22. package/dist/commands/commit/commit.command.js +94 -0
  23. package/dist/commands/commit/commit.command.js.map +1 -0
  24. package/dist/commands/commit/index.d.ts +1 -0
  25. package/dist/commands/commit/index.js +2 -0
  26. package/dist/commands/commit/index.js.map +1 -0
  27. package/dist/commands/create-anchor/create-anchor.command.js +1 -1
  28. package/dist/commands/create-anchor/create-anchor.command.js.map +1 -1
  29. package/dist/commands/create-anchor/generator-octo-anchor-template/generators/app/index.d.ts +18 -3
  30. package/dist/commands/create-anchor/generator-octo-anchor-template/generators/app/index.js +29 -11
  31. package/dist/commands/create-anchor/generator-octo-anchor-template/generators/app/index.js.map +1 -1
  32. package/dist/commands/create-anchor/generator-octo-anchor-template/generators/app/templates/anchors/anchor/anchor.schema.ts.ejs +9 -1
  33. package/dist/commands/create-app/create-app.command.d.ts +1 -1
  34. package/dist/commands/create-app/create-app.command.js +7 -3
  35. package/dist/commands/create-app/create-app.command.js.map +1 -1
  36. package/dist/commands/create-app/generator-octo-app-template/generators/app/index.d.ts +17 -1
  37. package/dist/commands/create-app/generator-octo-app-template/generators/app/index.js +59 -12
  38. package/dist/commands/create-app/generator-octo-app-template/generators/app/index.js.map +1 -1
  39. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/README.md.ejs +60 -10
  40. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/gitignore.ejs +13 -0
  41. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/index.ts.ejs +19 -0
  42. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/package.json.ejs +9 -1
  43. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/src/aws-dynamodb-service/octo.yaml.ejs +111 -0
  44. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/src/aws-ecs-server/octo.yaml.ejs +14 -0
  45. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/src/aws-s3-website/octo.yaml.ejs +14 -0
  46. package/dist/commands/create-app/generator-octo-app-template/generators/app/templates/tsconfig.json.ejs +0 -3
  47. package/dist/commands/{create-resource/create-resource.command.d.ts → create-external-resource/create-external-resource.command.d.ts} +1 -1
  48. package/dist/commands/create-external-resource/create-external-resource.command.js +64 -0
  49. package/dist/commands/create-external-resource/create-external-resource.command.js.map +1 -0
  50. package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/index.d.ts +17 -2
  51. package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/index.js +36 -35
  52. package/dist/commands/create-external-resource/generator-octo-external-resource-template/generators/app/index.js.map +1 -0
  53. package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/templates/resources/resource/actions/add.resource.action.ts.ejs +7 -16
  54. package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/templates/resources/resource/actions/delete.resource.action.ts.ejs +21 -3
  55. package/dist/commands/create-external-resource/generator-octo-external-resource-template/generators/app/templates/resources/resource/actions/update.resource.action.ts.ejs +63 -0
  56. package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/templates/resources/resource/index.ts.ejs +1 -2
  57. package/dist/commands/create-external-resource/generator-octo-external-resource-template/generators/app/templates/resources/resource/resource.ts.ejs +28 -0
  58. package/dist/commands/create-external-resource/index.d.ts +1 -0
  59. package/dist/commands/create-external-resource/index.js +2 -0
  60. package/dist/commands/create-external-resource/index.js.map +1 -0
  61. package/dist/commands/create-model/create-model.command.js +1 -1
  62. package/dist/commands/create-model/create-model.command.js.map +1 -1
  63. package/dist/commands/create-model/generator-octo-model-template/generators/app/index.d.ts +17 -2
  64. package/dist/commands/create-model/generator-octo-model-template/generators/app/index.js +26 -9
  65. package/dist/commands/create-model/generator-octo-model-template/generators/app/index.js.map +1 -1
  66. package/dist/commands/create-model/generator-octo-model-template/generators/app/templates/models/model/model.ts.ejs +3 -1
  67. package/dist/commands/create-model/generator-octo-model-template/generators/app/templates/models/model/schema.ts.ejs +6 -1
  68. package/dist/commands/create-module/create-module.command.js +1 -1
  69. package/dist/commands/create-module/create-module.command.js.map +1 -1
  70. package/dist/commands/create-module/generator-octo-module-template/generators/app/index.d.ts +17 -2
  71. package/dist/commands/create-module/generator-octo-module-template/generators/app/index.js +27 -10
  72. package/dist/commands/create-module/generator-octo-module-template/generators/app/index.js.map +1 -1
  73. package/dist/commands/create-overlay/create-overlay.command.js +4 -3
  74. package/dist/commands/create-overlay/create-overlay.command.js.map +1 -1
  75. package/dist/commands/create-overlay/generator-octo-overlay-template/generators/app/index.d.ts +17 -2
  76. package/dist/commands/create-overlay/generator-octo-overlay-template/generators/app/index.js +26 -9
  77. package/dist/commands/create-overlay/generator-octo-overlay-template/generators/app/index.js.map +1 -1
  78. package/dist/commands/{create-cdk/create-cdk.command.d.ts → create-terraform-resource/create-terraform-resource.command.d.ts} +3 -3
  79. package/dist/commands/{create-resource/create-resource.command.js → create-terraform-resource/create-terraform-resource.command.js} +17 -11
  80. package/dist/commands/create-terraform-resource/create-terraform-resource.command.js.map +1 -0
  81. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/index.d.ts +25 -0
  82. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/index.js +71 -0
  83. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/index.js.map +1 -0
  84. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/templates/resources/resource/index.schema.ts.ejs +43 -0
  85. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/templates/resources/resource/index.ts.ejs +1 -0
  86. package/dist/commands/create-terraform-resource/generator-octo-terraform-resource-template/generators/app/templates/resources/resource/resource.ts.ejs +83 -0
  87. package/dist/commands/create-terraform-resource/index.d.ts +1 -0
  88. package/dist/commands/create-terraform-resource/index.js +2 -0
  89. package/dist/commands/create-terraform-resource/index.js.map +1 -0
  90. package/dist/commands/generate/generate.command.d.ts +14 -0
  91. package/dist/commands/generate/generate.command.js +35 -0
  92. package/dist/commands/generate/generate.command.js.map +1 -0
  93. package/dist/commands/generate/index.d.ts +1 -0
  94. package/dist/commands/generate/index.js +2 -0
  95. package/dist/commands/generate/index.js.map +1 -0
  96. package/dist/commands/get-response/get-response.command.d.ts +18 -0
  97. package/dist/commands/get-response/get-response.command.js +39 -0
  98. package/dist/commands/get-response/get-response.command.js.map +1 -0
  99. package/dist/commands/get-response/index.d.ts +1 -0
  100. package/dist/commands/get-response/index.js +2 -0
  101. package/dist/commands/get-response/index.js.map +1 -0
  102. package/dist/commands/lock/index.d.ts +1 -0
  103. package/dist/commands/lock/index.js +2 -0
  104. package/dist/commands/lock/index.js.map +1 -0
  105. package/dist/commands/lock/lock.command.d.ts +13 -0
  106. package/dist/commands/lock/lock.command.js +28 -0
  107. package/dist/commands/lock/lock.command.js.map +1 -0
  108. package/dist/commands/run-action/index.d.ts +1 -0
  109. package/dist/commands/run-action/index.js +2 -0
  110. package/dist/commands/run-action/index.js.map +1 -0
  111. package/dist/commands/run-action/run-action.command.d.ts +12 -0
  112. package/dist/commands/run-action/run-action.command.js +46 -0
  113. package/dist/commands/run-action/run-action.command.js.map +1 -0
  114. package/dist/commands/unlock/index.d.ts +1 -0
  115. package/dist/commands/unlock/index.js +2 -0
  116. package/dist/commands/unlock/index.js.map +1 -0
  117. package/dist/commands/unlock/unlock.command.d.ts +18 -0
  118. package/dist/commands/unlock/unlock.command.js +51 -0
  119. package/dist/commands/unlock/unlock.command.js.map +1 -0
  120. package/dist/commands/validate/index.d.ts +1 -0
  121. package/dist/commands/validate/index.js +2 -0
  122. package/dist/commands/validate/index.js.map +1 -0
  123. package/dist/commands/validate/validate.command.d.ts +17 -0
  124. package/dist/commands/validate/validate.command.js +68 -0
  125. package/dist/commands/validate/validate.command.js.map +1 -0
  126. package/dist/index.js +20 -8
  127. package/dist/index.js.map +1 -1
  128. package/dist/resources/octo-v1.schema.json +86 -19
  129. package/dist/utilities/definition/yaml/yaml-definition.utility.d.ts +98 -10
  130. package/dist/utilities/definition/yaml/yaml-definition.utility.js +197 -66
  131. package/dist/utilities/definition/yaml/yaml-definition.utility.js.map +1 -1
  132. package/dist/utilities/octo/octo.utility.d.ts +48 -0
  133. package/dist/utilities/octo/octo.utility.js +144 -0
  134. package/dist/utilities/octo/octo.utility.js.map +1 -0
  135. package/dist/utilities/process/process.utility.d.ts +8 -0
  136. package/dist/utilities/process/process.utility.js +8 -0
  137. package/dist/utilities/process/process.utility.js.map +1 -1
  138. package/dist/utilities/project-index/project-index.utility.d.ts +27 -0
  139. package/dist/utilities/project-index/project-index.utility.js +71 -0
  140. package/dist/utilities/project-index/project-index.utility.js.map +1 -0
  141. package/dist/utilities/string/string.utility.d.ts +22 -0
  142. package/dist/utilities/string/string.utility.js +27 -0
  143. package/dist/utilities/string/string.utility.js.map +1 -1
  144. package/package.json +9 -8
  145. package/dist/commands/create-cdk/create-cdk.command.js +0 -70
  146. package/dist/commands/create-cdk/create-cdk.command.js.map +0 -1
  147. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/index.d.ts +0 -10
  148. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/index.js +0 -107
  149. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/index.js.map +0 -1
  150. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/README.md.ejs +0 -23
  151. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/eslint.config.js.ejs +0 -337
  152. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/gitignore.ejs +0 -82
  153. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/gitkeep.ejs +0 -0
  154. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/package.json.ejs +0 -57
  155. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/prettierrc.ejs +0 -5
  156. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/anchors/app/app.anchor.schema.ts.ejs +0 -19
  157. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/anchors/app/app.anchor.ts.ejs +0 -17
  158. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/factories/dummy-credentials.factory.ts.ejs +0 -28
  159. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/dummy-app.module.spec.ts.ejs +0 -90
  160. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/dummy-app.module.ts.ejs +0 -16
  161. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/index.schema.ts.ejs +0 -10
  162. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/index.ts.ejs +0 -3
  163. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/models/app/actions/add-dummy-app.model.action.ts.ejs +0 -59
  164. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/modules/app/dummy-app/models/app/index.ts.ejs +0 -1
  165. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/resources/http-fetch/actions/add-http-fetch.resource.action.ts.ejs +0 -55
  166. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/resources/http-fetch/http-fetch.resource.ts.ejs +0 -12
  167. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/resources/http-fetch/index.schema.ts.ejs +0 -35
  168. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/resources/http-fetch/index.ts.ejs +0 -3
  169. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/src/utilities/dummy/dummy.utility.ts.ejs +0 -11
  170. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/tsconfig.build.json.ejs +0 -11
  171. package/dist/commands/create-cdk/generator-octo-cdk-template/generators/app/templates/tsconfig.json.ejs +0 -39
  172. package/dist/commands/create-cdk/index.d.ts +0 -1
  173. package/dist/commands/create-cdk/index.js +0 -2
  174. package/dist/commands/create-cdk/index.js.map +0 -1
  175. package/dist/commands/create-resource/create-resource.command.js.map +0 -1
  176. package/dist/commands/create-resource/generator-octo-resource-template/generators/app/index.js.map +0 -1
  177. package/dist/commands/create-resource/generator-octo-resource-template/generators/app/templates/resources/resource/actions/update-tags.resource.action.ts.ejs +0 -86
  178. package/dist/commands/create-resource/generator-octo-resource-template/generators/app/templates/resources/resource/actions/validate.resource.action.ts.ejs +0 -40
  179. package/dist/commands/create-resource/generator-octo-resource-template/generators/app/templates/resources/resource/resource.ts.ejs +0 -12
  180. package/dist/commands/create-resource/index.d.ts +0 -1
  181. package/dist/commands/create-resource/index.js +0 -2
  182. package/dist/commands/create-resource/index.js.map +0 -1
  183. package/dist/commands/run/index.d.ts +0 -1
  184. package/dist/commands/run/index.js +0 -2
  185. package/dist/commands/run/index.js.map +0 -1
  186. package/dist/commands/run/run.command.d.ts +0 -11
  187. package/dist/commands/run/run.command.js +0 -96
  188. package/dist/commands/run/run.command.js.map +0 -1
  189. package/dist/commands/state/index.d.ts +0 -1
  190. package/dist/commands/state/index.js +0 -2
  191. package/dist/commands/state/index.js.map +0 -1
  192. package/dist/commands/state/state.command.d.ts +0 -14
  193. package/dist/commands/state/state.command.js +0 -79
  194. package/dist/commands/state/state.command.js.map +0 -1
  195. /package/dist/commands/{create-resource/generator-octo-resource-template → create-external-resource/generator-octo-external-resource-template}/generators/app/templates/resources/resource/index.schema.ts.ejs +0 -0
@@ -81,6 +81,9 @@
81
81
  {
82
82
  "additionalProperties": false,
83
83
  "properties": {
84
+ "reportDir": {
85
+ "type": "string"
86
+ },
84
87
  "type": {
85
88
  "const": "HtmlReportEventListener",
86
89
  "type": "string"
@@ -92,6 +95,13 @@
92
95
  {
93
96
  "additionalProperties": false,
94
97
  "properties": {
98
+ "colorize": {
99
+ "type": "boolean"
100
+ },
101
+ "level": {
102
+ "enum": ["debug", "error", "fatal", "info", "silent", "trace", "warn"],
103
+ "type": "string"
104
+ },
95
105
  "type": {
96
106
  "const": "LoggingEventListener",
97
107
  "type": "string"
@@ -149,31 +159,88 @@
149
159
  "type": "object"
150
160
  }
151
161
  ]
152
- },
153
- "transactionOptions": {
154
- "additionalProperties": false,
155
- "properties": {
156
- "yieldModelDiffs": {
157
- "default": false,
158
- "type": "boolean"
162
+ }
163
+ },
164
+ "required": ["stateProvider"],
165
+ "type": "object"
166
+ },
167
+ "tags": {
168
+ "items": {
169
+ "additionalProperties": false,
170
+ "properties": {
171
+ "scope": {
172
+ "additionalProperties": false,
173
+ "properties": {
174
+ "moduleId": {
175
+ "type": "string"
176
+ }
159
177
  },
160
- "yieldModelTransaction": {
161
- "default": false,
162
- "type": "boolean"
178
+ "type": "object"
179
+ },
180
+ "tags": {
181
+ "additionalProperties": {
182
+ "type": "string"
183
+ },
184
+ "type": "object"
185
+ }
186
+ },
187
+ "required": ["tags"],
188
+ "type": "object"
189
+ },
190
+ "type": "array"
191
+ },
192
+ "terraform": {
193
+ "additionalProperties": false,
194
+ "properties": {
195
+ "providers": {
196
+ "items": {
197
+ "additionalProperties": false,
198
+ "properties": {
199
+ "accountId": {
200
+ "type": "string"
201
+ },
202
+ "accountName": {
203
+ "type": "string"
204
+ },
205
+ "providerType": {
206
+ "type": "string"
207
+ },
208
+ "regionId": {
209
+ "type": "string"
210
+ },
211
+ "setRegionAttribute": {
212
+ "type": "boolean"
213
+ },
214
+ "spec": {
215
+ "type": "object"
216
+ }
163
217
  },
164
- "yieldResourceDiffs": {
165
- "default": false,
166
- "type": "boolean"
218
+ "required": ["accountId", "providerType", "regionId"],
219
+ "type": "object"
220
+ },
221
+ "type": "array"
222
+ },
223
+ "requiredProviders": {
224
+ "additionalProperties": {
225
+ "additionalProperties": false,
226
+ "properties": {
227
+ "minVersion": {
228
+ "type": "string"
229
+ },
230
+ "source": {
231
+ "type": "string"
232
+ }
167
233
  },
168
- "yieldResourceTransaction": {
169
- "default": false,
170
- "type": "boolean"
171
- }
234
+ "required": ["source"],
235
+ "type": "object"
172
236
  },
173
237
  "type": "object"
238
+ },
239
+ "requiredVersion": {
240
+ "type": "string"
174
241
  }
175
242
  },
176
- "required": ["stateProvider"],
243
+ "required": ["providers", "requiredProviders", "requiredVersion"],
177
244
  "type": "object"
178
245
  },
179
246
  "version": {
@@ -181,7 +248,7 @@
181
248
  "type": "number"
182
249
  }
183
250
  },
184
- "required": ["imports", "modules", "settings", "version"],
251
+ "required": ["imports", "modules", "settings", "terraform", "version"],
185
252
  "title": "Octo V1 Definition Schema",
186
253
  "type": "object"
187
254
  }
@@ -1,10 +1,34 @@
1
- import { type Constructable, LocalEncryptionStateProvider, LocalStateProvider, TestStateProvider } from '@quadnix/octo';
1
+ import { type Constructable, LocalEncryptionStateProvider, LocalStateProvider, type Octo, TestStateProvider } from '@quadnix/octo';
2
2
  import type { HtmlReportEventListener } from '@quadnix/octo-event-listeners/html-report';
3
3
  import type { LoggingEventListener } from '@quadnix/octo-event-listeners/logging';
4
+ /**
5
+ * Reads `octo.yml` and turns each section into the values {@link Octo} is booted with.
6
+ */
4
7
  export declare class YamlDefinitionUtility {
5
8
  private readonly definition;
9
+ /**
10
+ * The directory holding the definition file. Everything the definition names by relative path is
11
+ * resolved against this, not against `process.cwd()`: `octo run-action` is invoked by terraform
12
+ * from inside a terragrunt cache directory, so the cwd there is neither the project nor stable.
13
+ */
14
+ private readonly definitionDir;
15
+ /**
16
+ * @param definitionFilePath Path to the definition file.
17
+ * @throws If the file is unreadable, or fails schema validation.
18
+ */
6
19
  constructor(definitionFilePath: string);
20
+ /**
21
+ * Substitutes `${env.KEY}` references in a value with what the definition's `env` block declares.
22
+ *
23
+ * Arrays and objects are walked, because a module input is as nested as its schema: a reference
24
+ * can sit inside a list of images, or a key of an object, as readily as at the top level.
25
+ *
26
+ * @throws If a value holds one of its own ancestors, which no substitution over it terminates on.
27
+ */
7
28
  resolveEnvironmentValue<T>(value: unknown): T;
29
+ /**
30
+ * Imports the hook callbacks the definition names, in the shape {@link Octo.registerHooks} takes.
31
+ */
8
32
  resolveHooks(): Promise<{
9
33
  postCommitHooks?: {
10
34
  handle: any;
@@ -29,19 +53,35 @@ export declare class YamlDefinitionUtility {
29
53
  handle: any;
30
54
  }[];
31
55
  }>;
56
+ /**
57
+ * Imports every module class the definition's `imports` block names.
58
+ */
32
59
  resolveImports(): Promise<{
33
60
  className: Constructable<any>;
34
61
  }[]>;
35
- resolveListeners(): Promise<{
62
+ /**
63
+ * @param logDestination Where the logging listener writes. `run-action` passes `stderr`, because
64
+ * terraform's `data "external"` protocol requires that command's stdout to carry only the response
65
+ * JSON.
66
+ */
67
+ resolveListeners({ logDestination }?: {
68
+ logDestination?: 'stderr' | 'stdout';
69
+ }): Promise<{
36
70
  args?: unknown[];
37
71
  metadata?: Record<string, string>;
38
72
  type: Constructable<HtmlReportEventListener | LoggingEventListener>;
39
73
  }[]>;
74
+ /**
75
+ * Pairs each `modules` entry with its imported class, and resolves its inputs.
76
+ */
40
77
  resolveModules(imports: Awaited<ReturnType<YamlDefinitionUtility['resolveImports']>>): {
41
78
  moduleClass: Constructable<any>;
42
79
  moduleId: string;
43
80
  moduleInputs: Record<string, unknown>;
44
81
  }[];
82
+ /**
83
+ * Builds the {@link IStateProvider} the definition asks for.
84
+ */
45
85
  resolveStateProvider(): {
46
86
  type: LocalEncryptionStateProvider;
47
87
  } | {
@@ -49,14 +89,62 @@ export declare class YamlDefinitionUtility {
49
89
  } | {
50
90
  type: TestStateProvider;
51
91
  };
52
- resolveTransactionOptions(): {
53
- appLockId: string | undefined;
54
- enableResourceCapture: boolean;
55
- enableResourceValidation: boolean;
56
- yieldModelDiffs: boolean;
57
- yieldModelTransaction: boolean;
58
- yieldResourceDiffs: boolean;
59
- yieldResourceTransaction: boolean;
92
+ /**
93
+ * The tag registrations the definition declares, in the shape {@link Octo.registerTags} takes.
94
+ *
95
+ * A `scope` naming a `moduleId` reaches that module's resources; an absent or empty one reaches
96
+ * every resource of the run.
97
+ */
98
+ resolveTags(): Parameters<Octo['registerTags']>[0];
99
+ /**
100
+ * The terraform version, required providers, and provider blocks the definition declares.
101
+ */
102
+ resolveTerraformOptions(): {
103
+ providers: {
104
+ accountId: string;
105
+ accountName: string | undefined;
106
+ providerType: string;
107
+ regionId: string;
108
+ setRegionAttribute: boolean | undefined;
109
+ spec: Record<string, unknown>;
110
+ }[];
111
+ requiredProviders: Record<string, {
112
+ minVersion?: string;
113
+ source: string;
114
+ }>;
115
+ requiredVersion: string;
60
116
  };
117
+ /**
118
+ * Substitutes one `${env.KEY}` reference with what the definition's `env` block declares for that
119
+ * key, read in the kind the block gives it. Anything holding no reference is returned as it came.
120
+ */
121
+ private resolveEnvironmentReference;
122
+ /**
123
+ * Resolves a definition-file import specifier to something `import()` can load.
124
+ *
125
+ * A relative path resolves against the **definition file's own directory**. It cannot resolve
126
+ * against the current working directory: terraform invokes `octo run-action` from inside a
127
+ * terragrunt cache directory, so a definition saying `./dist/index.js` - which is what every
128
+ * generated app says - would be looked for under the cache and never found.
129
+ *
130
+ * A bare specifier resolves against the **user's project root**, not octo-build's own install
131
+ * directory - node would otherwise resolve it from wherever octo-build happens to live. A flat
132
+ * `node_modules` hides that; a linked store does not, and there the user's own dependencies are
133
+ * invisible.
134
+ */
135
+ private resolveImportPath;
136
+ /**
137
+ * Walks a value, substituting every `${env.KEY}` reference it holds at any depth.
138
+ *
139
+ * `ancestors` is the chain currently being walked rather than everything already seen. A YAML
140
+ * anchor used in two places points one node at two parents, which resolves twice and is no cycle;
141
+ * a node reappearing beneath itself is one, and walking it never ends.
142
+ *
143
+ * @throws If a value holds one of its own ancestors.
144
+ */
145
+ private resolveNestedEnvironmentValue;
146
+ /**
147
+ * Checks the definition against `octo-v1.schema.json`.
148
+ */
61
149
  private validateDefinition;
62
150
  }
@@ -1,13 +1,22 @@
1
1
  import { existsSync, readFileSync } from 'fs';
2
+ import { createRequire } from 'module';
2
3
  import { dirname, join, resolve } from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { LocalEncryptionStateProvider, LocalStateProvider, TestStateProvider } from '@quadnix/octo';
4
+ import { fileURLToPath, pathToFileURL } from 'url';
5
+ import { LocalEncryptionStateProvider, LocalStateProvider, TestStateProvider, } from '@quadnix/octo';
5
6
  import { Ajv2020 as Ajv } from 'ajv/dist/2020.js';
6
7
  import { load } from 'js-yaml';
7
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ /**
10
+ * Reads `octo.yml` and turns each section into the values {@link Octo} is booted with.
11
+ */
8
12
  export class YamlDefinitionUtility {
13
+ /**
14
+ * @param definitionFilePath Path to the definition file.
15
+ * @throws If the file is unreadable, or fails schema validation.
16
+ */
9
17
  constructor(definitionFilePath) {
10
18
  definitionFilePath = resolve(process.cwd(), definitionFilePath);
19
+ this.definitionDir = dirname(definitionFilePath);
11
20
  if (!existsSync(definitionFilePath)) {
12
21
  throw new Error('Cannot find definition file: ' + definitionFilePath);
13
22
  }
@@ -20,32 +29,20 @@ export class YamlDefinitionUtility {
20
29
  }
21
30
  this.validateDefinition();
22
31
  }
32
+ /**
33
+ * Substitutes `${env.KEY}` references in a value with what the definition's `env` block declares.
34
+ *
35
+ * Arrays and objects are walked, because a module input is as nested as its schema: a reference
36
+ * can sit inside a list of images, or a key of an object, as readily as at the top level.
37
+ *
38
+ * @throws If a value holds one of its own ancestors, which no substitution over it terminates on.
39
+ */
23
40
  resolveEnvironmentValue(value) {
24
- if (typeof value !== 'string') {
25
- return value;
26
- }
27
- const pattern = value.match(/^\$\{env\.(.+)}$/);
28
- if (!pattern || pattern.length < 2) {
29
- return value;
30
- }
31
- const environmentKey = this.definition.env.find((e) => e.key === pattern[1]);
32
- if (!environmentKey) {
33
- throw new Error(`Key "${pattern[1]}" not found under "env"!`);
34
- }
35
- let resolvedValue = process.env[pattern[1]] || environmentKey.value;
36
- const { kind } = environmentKey;
37
- switch (kind) {
38
- case 'boolean': {
39
- return Boolean(resolvedValue);
40
- }
41
- case 'number': {
42
- return Number(resolvedValue);
43
- }
44
- default: {
45
- return String(resolvedValue);
46
- }
47
- }
41
+ return this.resolveNestedEnvironmentValue(value, new Set());
48
42
  }
43
+ /**
44
+ * Imports the hook callbacks the definition names, in the shape {@link Octo.registerHooks} takes.
45
+ */
49
46
  async resolveHooks() {
50
47
  const hooks = {
51
48
  postCommitHooks: [],
@@ -60,8 +57,8 @@ export class YamlDefinitionUtility {
60
57
  }
61
58
  for (const hookDef of this.definition.hooks) {
62
59
  const { importPath, method } = hookDef;
63
- const resolvedImport = importPath.startsWith('.') || importPath.startsWith('/') ? resolve(process.cwd(), importPath) : importPath;
64
60
  try {
61
+ const resolvedImport = this.resolveImportPath(importPath);
65
62
  const imported = await import(resolvedImport);
66
63
  if (!imported[method]) {
67
64
  throw new Error(`Unable to find export "${method}" in path "${resolvedImport}"!`);
@@ -77,11 +74,14 @@ export class YamlDefinitionUtility {
77
74
  }
78
75
  }
79
76
  catch (error) {
80
- throw new Error(`Failed to import path "${resolvedImport}": ${error?.message || String(error)}`);
77
+ throw new Error(`Failed to import path "${importPath}": ${error?.message || String(error)}`);
81
78
  }
82
79
  }
83
80
  return hooks;
84
81
  }
82
+ /**
83
+ * Imports every module class the definition's `imports` block names.
84
+ */
85
85
  async resolveImports() {
86
86
  const imports = [];
87
87
  if (!this.definition.imports?.length) {
@@ -89,8 +89,8 @@ export class YamlDefinitionUtility {
89
89
  }
90
90
  for (const importDef of this.definition.imports) {
91
91
  const { className, importPath } = importDef;
92
- const resolvedImport = importPath.startsWith('.') || importPath.startsWith('/') ? resolve(process.cwd(), importPath) : importPath;
93
92
  try {
93
+ const resolvedImport = this.resolveImportPath(importPath);
94
94
  const imported = await import(resolvedImport);
95
95
  if (!imported[className]) {
96
96
  throw new Error(`Unable to find export "${className}" in path "${resolvedImport}"!`);
@@ -100,32 +100,51 @@ export class YamlDefinitionUtility {
100
100
  }
101
101
  }
102
102
  catch (error) {
103
- throw new Error(`Failed to import path "${resolvedImport}": ${error?.message || String(error)}`);
103
+ throw new Error(`Failed to import path "${importPath}": ${error?.message || String(error)}`);
104
104
  }
105
105
  }
106
106
  return imports;
107
107
  }
108
- async resolveListeners() {
108
+ /**
109
+ * @param logDestination Where the logging listener writes. `run-action` passes `stderr`, because
110
+ * terraform's `data "external"` protocol requires that command's stdout to carry only the response
111
+ * JSON.
112
+ */
113
+ async resolveListeners({ logDestination = 'stdout' } = {}) {
109
114
  const listeners = [];
110
115
  if (!this.definition.settings?.listeners?.length) {
111
116
  return listeners;
112
117
  }
113
118
  for (const listenerDef of this.definition.settings.listeners) {
114
119
  if (listenerDef.type === 'HtmlReportEventListener') {
115
- if (process.env.OCTO_DISABLE_HTML_REPORT_EVENT_LISTENER?.toLowerCase() !== 'true') {
116
- const imported = await import('@quadnix/octo-event-listeners/html-report');
117
- listeners.push({ type: imported.HtmlReportEventListener });
118
- }
120
+ const imported = await import('@quadnix/octo-event-listeners/html-report');
121
+ listeners.push({
122
+ args: [{ reportDir: listenerDef.reportDir }],
123
+ type: imported.HtmlReportEventListener,
124
+ });
119
125
  }
120
126
  else if (listenerDef.type === 'LoggingEventListener') {
121
- if (process.env.OCTO_DISABLE_LOGGING_EVENT_LISTENER?.toLowerCase() !== 'true') {
122
- const imported = await import('@quadnix/octo-event-listeners/logging');
123
- listeners.push({ type: imported.LoggingEventListener });
124
- }
127
+ const imported = await import('@quadnix/octo-event-listeners/logging');
128
+ // Default to whether the destination is a terminal, so a redirected log carries no escape
129
+ // codes. An explicit `colorize:` in the definition still wins.
130
+ const destinationStream = logDestination === 'stderr' ? process.stderr : process.stdout;
131
+ listeners.push({
132
+ args: [
133
+ {
134
+ colorize: listenerDef.colorize ?? Boolean(destinationStream.isTTY),
135
+ destination: logDestination,
136
+ level: listenerDef.level ?? 'info',
137
+ },
138
+ ],
139
+ type: imported.LoggingEventListener,
140
+ });
125
141
  }
126
142
  }
127
143
  return listeners;
128
144
  }
145
+ /**
146
+ * Pairs each `modules` entry with its imported class, and resolves its inputs.
147
+ */
129
148
  resolveModules(imports) {
130
149
  const modules = [];
131
150
  if (!this.definition.modules?.length) {
@@ -145,16 +164,22 @@ export class YamlDefinitionUtility {
145
164
  }
146
165
  return modules;
147
166
  }
167
+ /**
168
+ * Builds the {@link IStateProvider} the definition asks for.
169
+ */
148
170
  resolveStateProvider() {
149
171
  const stateProvider = this.definition.settings.stateProvider;
172
+ // Anchored to the definition file for the same reason imports are: `run-action` runs with
173
+ // terragrunt's cache directory as its cwd, and a state path resolved from there would point at
174
+ // an empty directory - the action would read no prior state and write its result nowhere useful.
150
175
  if (stateProvider.type === 'LocalEncryptionStateProvider') {
151
176
  return {
152
- type: new LocalEncryptionStateProvider(stateProvider.statePath, stateProvider.encryptionKey),
177
+ type: new LocalEncryptionStateProvider(resolve(this.definitionDir, stateProvider.statePath), stateProvider.encryptionKey),
153
178
  };
154
179
  }
155
180
  else if (stateProvider.type === 'LocalStateProvider') {
156
181
  return {
157
- type: new LocalStateProvider(stateProvider.statePath),
182
+ type: new LocalStateProvider(resolve(this.definitionDir, stateProvider.statePath)),
158
183
  };
159
184
  }
160
185
  else if (stateProvider.type === 'TestStateProvider') {
@@ -166,40 +191,146 @@ export class YamlDefinitionUtility {
166
191
  throw new Error(`Invalid state provider!`);
167
192
  }
168
193
  }
169
- resolveTransactionOptions() {
170
- const tx = this.definition.settings.transactionOptions || {};
171
- const options = {
172
- appLockId: undefined,
173
- enableResourceCapture: false,
174
- enableResourceValidation: false,
175
- yieldModelDiffs: tx.yieldModelDiffs || false,
176
- yieldModelTransaction: tx.yieldModelTransaction || false,
177
- yieldResourceDiffs: tx.yieldResourceDiffs || false,
178
- yieldResourceTransaction: tx.yieldResourceTransaction || false,
194
+ /**
195
+ * The tag registrations the definition declares, in the shape {@link Octo.registerTags} takes.
196
+ *
197
+ * A `scope` naming a `moduleId` reaches that module's resources; an absent or empty one reaches
198
+ * every resource of the run.
199
+ */
200
+ resolveTags() {
201
+ const tags = [];
202
+ if (!this.definition.tags?.length) {
203
+ return tags;
204
+ }
205
+ for (const tagDefinition of this.definition.tags) {
206
+ const resolvedTags = {};
207
+ for (const [key, value] of Object.entries(tagDefinition.tags)) {
208
+ // A tag value is always a string, so an `${env.KEY}` declared as a number or a boolean is
209
+ // rendered rather than handed to the resource as the kind it was declared with.
210
+ resolvedTags[key] = String(this.resolveEnvironmentValue(value));
211
+ }
212
+ tags.push({ scope: tagDefinition.scope || {}, tags: resolvedTags });
213
+ }
214
+ return tags;
215
+ }
216
+ /**
217
+ * The terraform version, required providers, and provider blocks the definition declares.
218
+ */
219
+ resolveTerraformOptions() {
220
+ const terraform = this.definition.terraform;
221
+ const providers = terraform.providers.map((p) => {
222
+ const spec = {};
223
+ for (const [key, value] of Object.entries(p.spec || {})) {
224
+ spec[key] = this.resolveEnvironmentValue(value);
225
+ }
226
+ return {
227
+ accountId: this.resolveEnvironmentValue(p.accountId),
228
+ accountName: p.accountName ? this.resolveEnvironmentValue(p.accountName) : undefined,
229
+ providerType: p.providerType,
230
+ regionId: this.resolveEnvironmentValue(p.regionId),
231
+ // Passed through as-is: undefined lets octo apply its per-provider-type default.
232
+ setRegionAttribute: p.setRegionAttribute,
233
+ spec,
234
+ };
235
+ });
236
+ return {
237
+ providers,
238
+ requiredProviders: terraform.requiredProviders,
239
+ requiredVersion: this.resolveEnvironmentValue(terraform.requiredVersion),
179
240
  };
180
- if (process.env.OCTO_APP_LOCK_ID) {
181
- options.appLockId = process.env.OCTO_APP_LOCK_ID;
241
+ }
242
+ /**
243
+ * Substitutes one `${env.KEY}` reference with what the definition's `env` block declares for that
244
+ * key, read in the kind the block gives it. Anything holding no reference is returned as it came.
245
+ */
246
+ resolveEnvironmentReference(value) {
247
+ if (typeof value !== 'string') {
248
+ return value;
182
249
  }
183
- if (process.env.OCTO_ENABLE_RESOURCE_CAPTURE) {
184
- options.enableResourceCapture = process.env.OCTO_ENABLE_RESOURCE_CAPTURE.toLowerCase() === 'true';
250
+ const pattern = value.match(/^\$\{env\.(.+)}$/);
251
+ if (!pattern || pattern.length < 2) {
252
+ return value;
185
253
  }
186
- if (process.env.OCTO_ENABLE_RESOURCE_VALIDATION) {
187
- options.enableResourceValidation = process.env.OCTO_ENABLE_RESOURCE_VALIDATION.toLowerCase() === 'true';
254
+ // `env` is optional in the schema, so a definition can reference `${env.X}` without declaring
255
+ // one. Treat that as an empty block and let the "not found" error below report it.
256
+ const environmentKey = (this.definition.env ?? []).find((e) => e.key === pattern[1]);
257
+ if (!environmentKey) {
258
+ throw new Error(`Key "${pattern[1]}" not found under "env"!`);
188
259
  }
189
- if (process.env.OCTO_YIELD_MODEL_DIFFS) {
190
- options.yieldModelDiffs = process.env.OCTO_YIELD_MODEL_DIFFS.toLowerCase() === 'true';
260
+ let resolvedValue = process.env[pattern[1]] || environmentKey.value;
261
+ const { kind } = environmentKey;
262
+ switch (kind) {
263
+ case 'boolean': {
264
+ // A shell always supplies a string, and `Boolean("false")` is `true`. Parse the string form
265
+ // rather than coercing it, and reject anything that is not plainly a boolean.
266
+ if (typeof resolvedValue === 'boolean') {
267
+ return resolvedValue;
268
+ }
269
+ const normalizedValue = String(resolvedValue).trim().toLowerCase();
270
+ if (normalizedValue === 'true') {
271
+ return true;
272
+ }
273
+ else if (normalizedValue === 'false') {
274
+ return false;
275
+ }
276
+ else {
277
+ throw new Error(`Value "${resolvedValue}" for key "${pattern[1]}" is not a boolean!`);
278
+ }
279
+ }
280
+ case 'number': {
281
+ return Number(resolvedValue);
282
+ }
283
+ default: {
284
+ return String(resolvedValue);
285
+ }
191
286
  }
192
- if (process.env.OCTO_YIELD_MODEL_TRANSACTION) {
193
- options.yieldModelTransaction = process.env.OCTO_YIELD_MODEL_TRANSACTION.toLowerCase() === 'true';
287
+ }
288
+ /**
289
+ * Resolves a definition-file import specifier to something `import()` can load.
290
+ *
291
+ * A relative path resolves against the **definition file's own directory**. It cannot resolve
292
+ * against the current working directory: terraform invokes `octo run-action` from inside a
293
+ * terragrunt cache directory, so a definition saying `./dist/index.js` - which is what every
294
+ * generated app says - would be looked for under the cache and never found.
295
+ *
296
+ * A bare specifier resolves against the **user's project root**, not octo-build's own install
297
+ * directory - node would otherwise resolve it from wherever octo-build happens to live. A flat
298
+ * `node_modules` hides that; a linked store does not, and there the user's own dependencies are
299
+ * invisible.
300
+ */
301
+ resolveImportPath(importPath) {
302
+ if (importPath.startsWith('.') || importPath.startsWith('/')) {
303
+ return resolve(this.definitionDir, importPath);
194
304
  }
195
- if (process.env.OCTO_YIELD_RESOURCE_DIFFS) {
196
- options.yieldResourceDiffs = process.env.OCTO_YIELD_RESOURCE_DIFFS.toLowerCase() === 'true';
305
+ const requireFromProject = createRequire(join(this.definitionDir, 'package.json'));
306
+ return pathToFileURL(requireFromProject.resolve(importPath)).href;
307
+ }
308
+ /**
309
+ * Walks a value, substituting every `${env.KEY}` reference it holds at any depth.
310
+ *
311
+ * `ancestors` is the chain currently being walked rather than everything already seen. A YAML
312
+ * anchor used in two places points one node at two parents, which resolves twice and is no cycle;
313
+ * a node reappearing beneath itself is one, and walking it never ends.
314
+ *
315
+ * @throws If a value holds one of its own ancestors.
316
+ */
317
+ resolveNestedEnvironmentValue(value, ancestors) {
318
+ if (value === null || typeof value !== 'object') {
319
+ return this.resolveEnvironmentReference(value);
197
320
  }
198
- if (process.env.OCTO_YIELD_RESOURCE_TRANSACTION) {
199
- options.yieldResourceTransaction = process.env.OCTO_YIELD_RESOURCE_TRANSACTION.toLowerCase() === 'true';
321
+ if (ancestors.has(value)) {
322
+ throw new Error('Definition holds a value that contains itself!');
200
323
  }
201
- return options;
324
+ ancestors.add(value);
325
+ const resolvedValue = Array.isArray(value)
326
+ ? value.map((v) => this.resolveNestedEnvironmentValue(v, ancestors))
327
+ : Object.fromEntries(Object.entries(value).map(([key, v]) => [key, this.resolveNestedEnvironmentValue(v, ancestors)]));
328
+ ancestors.delete(value);
329
+ return resolvedValue;
202
330
  }
331
+ /**
332
+ * Checks the definition against `octo-v1.schema.json`.
333
+ */
203
334
  validateDefinition() {
204
335
  if (!this.definition?.version || typeof this.definition?.version !== 'number') {
205
336
  throw new Error(`Definition version "${this.definition?.version}" must be a number!`);