@quadnix/octo 0.1.0 → 0.2.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 (221) hide show
  1. package/README.md +108 -21
  2. package/dist/app.type.d.ts +141 -22
  3. package/dist/app.type.js.map +1 -1
  4. package/dist/decorators/model.decorator.d.ts +3 -5
  5. package/dist/decorators/model.decorator.js +1 -6
  6. package/dist/decorators/model.decorator.js.map +1 -1
  7. package/dist/decorators/on-event.decorator.d.ts +2 -2
  8. package/dist/decorators/on-event.decorator.js.map +1 -1
  9. package/dist/decorators/resource.decorator.d.ts +50 -1
  10. package/dist/decorators/resource.decorator.js +38 -1
  11. package/dist/decorators/resource.decorator.js.map +1 -1
  12. package/dist/errors/schema.error.d.ts +8 -1
  13. package/dist/errors/schema.error.js +5 -1
  14. package/dist/errors/schema.error.js.map +1 -1
  15. package/dist/errors/terraform.error.d.ts +4 -3
  16. package/dist/errors/terraform.error.js +4 -3
  17. package/dist/errors/terraform.error.js.map +1 -1
  18. package/dist/errors/transaction.error.d.ts +32 -2
  19. package/dist/errors/transaction.error.js +22 -0
  20. package/dist/errors/transaction.error.js.map +1 -1
  21. package/dist/events/action.event.d.ts +8 -2
  22. package/dist/events/action.event.js +2 -2
  23. package/dist/events/action.event.js.map +1 -1
  24. package/dist/events/command.event.d.ts +43 -0
  25. package/dist/events/command.event.js +33 -0
  26. package/dist/events/command.event.js.map +1 -0
  27. package/dist/events/event.model.d.ts +7 -5
  28. package/dist/events/event.model.js +5 -3
  29. package/dist/events/event.model.js.map +1 -1
  30. package/dist/events/hook/commit.hook.event.d.ts +1 -1
  31. package/dist/events/hook/commit.hook.event.js.map +1 -1
  32. package/dist/events/hook/hook.event.d.ts +1 -1
  33. package/dist/events/hook/hook.event.js.map +1 -1
  34. package/dist/events/hook/model.hook.event.d.ts +1 -1
  35. package/dist/events/hook/model.hook.event.js.map +1 -1
  36. package/dist/events/hook/resource.hook.event.d.ts +1 -1
  37. package/dist/events/hook/resource.hook.event.js.map +1 -1
  38. package/dist/events/index.d.ts +2 -0
  39. package/dist/events/index.js +2 -0
  40. package/dist/events/index.js.map +1 -1
  41. package/dist/events/mode-result.event.d.ts +1 -1
  42. package/dist/events/mode-result.event.js.map +1 -1
  43. package/dist/events/mode.event.d.ts +8 -4
  44. package/dist/events/mode.event.js.map +1 -1
  45. package/dist/events/module.event.d.ts +7 -3
  46. package/dist/events/module.event.js +3 -2
  47. package/dist/events/module.event.js.map +1 -1
  48. package/dist/events/registration.event.d.ts +1 -1
  49. package/dist/events/registration.event.js.map +1 -1
  50. package/dist/events/serialization.event.d.ts +1 -1
  51. package/dist/events/serialization.event.js.map +1 -1
  52. package/dist/events/state.event.d.ts +102 -0
  53. package/dist/events/state.event.js +69 -0
  54. package/dist/events/state.event.js.map +1 -0
  55. package/dist/events/terraform.event.d.ts +37 -1
  56. package/dist/events/terraform.event.js +24 -0
  57. package/dist/events/terraform.event.js.map +1 -1
  58. package/dist/events/transaction.event.d.ts +3 -3
  59. package/dist/events/transaction.event.js +1 -1
  60. package/dist/events/transaction.event.js.map +1 -1
  61. package/dist/functions/container/container.js +1 -1
  62. package/dist/functions/container/container.js.map +1 -1
  63. package/dist/functions/container/test-container.js +2 -2
  64. package/dist/functions/container/test-container.js.map +1 -1
  65. package/dist/functions/dependency/dependency.d.ts +9 -22
  66. package/dist/functions/dependency/dependency.js +5 -1
  67. package/dist/functions/dependency/dependency.js.map +1 -1
  68. package/dist/functions/diff/diff.d.ts +14 -7
  69. package/dist/functions/diff/diff.js.map +1 -1
  70. package/dist/functions/node/node-action.abstract.d.ts +8 -10
  71. package/dist/functions/node/node-action.abstract.js +10 -16
  72. package/dist/functions/node/node-action.abstract.js.map +1 -1
  73. package/dist/functions/node/node.abstract.js +21 -10
  74. package/dist/functions/node/node.abstract.js.map +1 -1
  75. package/dist/functions/node/node.interface.d.ts +2 -1
  76. package/dist/functions/schema/schema.js +1 -1
  77. package/dist/functions/schema/schema.js.map +1 -1
  78. package/dist/index.d.ts +8 -6
  79. package/dist/index.js +6 -4
  80. package/dist/index.js.map +1 -1
  81. package/dist/index.type.d.ts +5 -0
  82. package/dist/index.type.js +2 -0
  83. package/dist/index.type.js.map +1 -0
  84. package/dist/main.d.ts +4 -3
  85. package/dist/main.js +27 -11
  86. package/dist/main.js.map +1 -1
  87. package/dist/models/account/account.model.d.ts +6 -0
  88. package/dist/models/account/account.model.js +16 -0
  89. package/dist/models/account/account.model.js.map +1 -1
  90. package/dist/models/app/app.model.d.ts +0 -10
  91. package/dist/models/app/app.model.js +0 -30
  92. package/dist/models/app/app.model.js.map +1 -1
  93. package/dist/models/deployment/deployment.schema.d.ts +4 -0
  94. package/dist/models/deployment/deployment.schema.js +4 -0
  95. package/dist/models/deployment/deployment.schema.js.map +1 -1
  96. package/dist/models/environment/environment.model.d.ts +2 -2
  97. package/dist/models/environment/environment.model.js +2 -2
  98. package/dist/models/execution/execution.model.d.ts +1 -1
  99. package/dist/models/execution/execution.model.js +1 -1
  100. package/dist/models/image/image.model.d.ts +4 -3
  101. package/dist/models/image/image.model.js +7 -6
  102. package/dist/models/image/image.model.js.map +1 -1
  103. package/dist/models/model.abstract.js +1 -1
  104. package/dist/models/model.abstract.js.map +1 -1
  105. package/dist/models/pipeline/pipeline.model.d.ts +1 -1
  106. package/dist/models/pipeline/pipeline.model.js +1 -1
  107. package/dist/models/server/server.model.d.ts +2 -2
  108. package/dist/models/server/server.model.js +2 -2
  109. package/dist/models/service/service.model.d.ts +28 -11
  110. package/dist/models/service/service.model.js +56 -16
  111. package/dist/models/service/service.model.js.map +1 -1
  112. package/dist/models/service/service.schema.d.ts +46 -2
  113. package/dist/models/service/service.schema.js +54 -2
  114. package/dist/models/service/service.schema.js.map +1 -1
  115. package/dist/modes/commit.mode.d.ts +2 -2
  116. package/dist/modes/commit.mode.js +2 -2
  117. package/dist/modes/generate.mode.d.ts +2 -1
  118. package/dist/modes/generate.mode.js +3 -2
  119. package/dist/modes/generate.mode.js.map +1 -1
  120. package/dist/modes/run-action.mode.d.ts +1 -1
  121. package/dist/modes/run-action.mode.js +1 -1
  122. package/dist/modes/validate.mode.d.ts +24 -0
  123. package/dist/modes/validate.mode.js +89 -11
  124. package/dist/modes/validate.mode.js.map +1 -1
  125. package/dist/modules/module.container.d.ts +7 -1
  126. package/dist/modules/module.container.js +41 -5
  127. package/dist/modules/module.container.js.map +1 -1
  128. package/dist/modules/test-module.container.js +35 -83
  129. package/dist/modules/test-module.container.js.map +1 -1
  130. package/dist/overlays/overlay.abstract.js +1 -1
  131. package/dist/overlays/overlay.abstract.js.map +1 -1
  132. package/dist/resources/resource-action.interface.d.ts +4 -2
  133. package/dist/resources/resource.abstract.d.ts +50 -8
  134. package/dist/resources/resource.abstract.js +63 -32
  135. package/dist/resources/resource.abstract.js.map +1 -1
  136. package/dist/resources/resource.interface.d.ts +0 -2
  137. package/dist/resources/resource.schema.d.ts +11 -1
  138. package/dist/resources/resource.schema.js +11 -1
  139. package/dist/resources/resource.schema.js.map +1 -1
  140. package/dist/services/event/event.service.d.ts +2 -2
  141. package/dist/services/event/event.service.js.map +1 -1
  142. package/dist/services/input/input.service.d.ts +30 -2
  143. package/dist/services/input/input.service.js +71 -4
  144. package/dist/services/input/input.service.js.map +1 -1
  145. package/dist/services/serialization/model/model-serialization.service.d.ts +4 -6
  146. package/dist/services/serialization/model/model-serialization.service.js +20 -17
  147. package/dist/services/serialization/model/model-serialization.service.js.map +1 -1
  148. package/dist/services/serialization/resource/resource-serialization.service.d.ts +7 -6
  149. package/dist/services/serialization/resource/resource-serialization.service.js +25 -12
  150. package/dist/services/serialization/resource/resource-serialization.service.js.map +1 -1
  151. package/dist/services/state-management/local.state-provider.d.ts +25 -9
  152. package/dist/services/state-management/local.state-provider.js +127 -21
  153. package/dist/services/state-management/local.state-provider.js.map +1 -1
  154. package/dist/services/state-management/remote.state-provider.d.ts +216 -0
  155. package/dist/services/state-management/remote.state-provider.js +645 -0
  156. package/dist/services/state-management/remote.state-provider.js.map +1 -0
  157. package/dist/services/state-management/state-management.service.d.ts +83 -40
  158. package/dist/services/state-management/state-management.service.js +130 -26
  159. package/dist/services/state-management/state-management.service.js.map +1 -1
  160. package/dist/services/state-management/state-provider.interface.d.ts +50 -9
  161. package/dist/services/state-management/state-provider.interface.js +14 -1
  162. package/dist/services/state-management/state-provider.interface.js.map +1 -1
  163. package/dist/services/state-management/terraform-state.service.d.ts +32 -9
  164. package/dist/services/state-management/terraform-state.service.js +64 -21
  165. package/dist/services/state-management/terraform-state.service.js.map +1 -1
  166. package/dist/services/state-management/test.state-provider.d.ts +11 -10
  167. package/dist/services/state-management/test.state-provider.js +35 -14
  168. package/dist/services/state-management/test.state-provider.js.map +1 -1
  169. package/dist/services/terraform/terraform.service.d.ts +57 -3
  170. package/dist/services/terraform/terraform.service.js +95 -8
  171. package/dist/services/terraform/terraform.service.js.map +1 -1
  172. package/dist/services/terraform/terraform.type.js +1 -1
  173. package/dist/services/terraform/terraform.type.js.map +1 -1
  174. package/dist/services/transaction/transaction.service.d.ts +29 -5
  175. package/dist/services/transaction/transaction.service.js +198 -34
  176. package/dist/services/transaction/transaction.service.js.map +1 -1
  177. package/dist/services/validation/validation.service.d.ts +2 -2
  178. package/dist/{functions → utilities}/diff/diff.utility.d.ts +2 -1
  179. package/dist/{functions → utilities}/diff/diff.utility.js +4 -3
  180. package/dist/utilities/diff/diff.utility.js.map +1 -0
  181. package/dist/utilities/file/file.utility.d.ts +6 -7
  182. package/dist/utilities/file/file.utility.js +13 -68
  183. package/dist/utilities/file/file.utility.js.map +1 -1
  184. package/dist/utilities/hcl/hcl.utility.d.ts +83 -16
  185. package/dist/utilities/hcl/hcl.utility.js +200 -27
  186. package/dist/utilities/hcl/hcl.utility.js.map +1 -1
  187. package/dist/utilities/http/http.utility.d.ts +57 -0
  188. package/dist/utilities/http/http.utility.js +98 -0
  189. package/dist/utilities/http/http.utility.js.map +1 -0
  190. package/dist/utilities/string/string.utility.d.ts +23 -3
  191. package/dist/utilities/string/string.utility.js +29 -3
  192. package/dist/utilities/string/string.utility.js.map +1 -1
  193. package/dist/utilities/terraform/terraform.utility.d.ts +70 -21
  194. package/dist/utilities/terraform/terraform.utility.js +255 -66
  195. package/dist/utilities/terraform/terraform.utility.js.map +1 -1
  196. package/dist/utilities/test-helpers/test-classes.d.ts +19 -0
  197. package/dist/utilities/test-helpers/test-classes.js +22 -0
  198. package/dist/utilities/test-helpers/test-classes.js.map +1 -1
  199. package/dist/utilities/test-helpers/test-models.d.ts +8 -1
  200. package/dist/utilities/test-helpers/test-models.js +25 -12
  201. package/dist/utilities/test-helpers/test-models.js.map +1 -1
  202. package/dist/utilities/test-helpers/test-modes.d.ts +1 -1
  203. package/dist/utilities/test-helpers/test-modes.js +36 -13
  204. package/dist/utilities/test-helpers/test-modes.js.map +1 -1
  205. package/dist/utilities/test-helpers/test-modules.d.ts +6 -0
  206. package/dist/utilities/test-helpers/test-modules.js +6 -0
  207. package/dist/utilities/test-helpers/test-modules.js.map +1 -1
  208. package/dist/utilities/test-helpers/test-overlays.d.ts +3 -1
  209. package/dist/utilities/test-helpers/test-overlays.js +8 -2
  210. package/dist/utilities/test-helpers/test-overlays.js.map +1 -1
  211. package/dist/utilities/test-helpers/test-resources.d.ts +4 -2
  212. package/dist/utilities/test-helpers/test-resources.js +22 -20
  213. package/dist/utilities/test-helpers/test-resources.js.map +1 -1
  214. package/package.json +7 -2
  215. package/dist/functions/diff/diff.utility.js.map +0 -1
  216. package/dist/services/state-management/file-app-lock.utility.d.ts +0 -43
  217. package/dist/services/state-management/file-app-lock.utility.js +0 -124
  218. package/dist/services/state-management/file-app-lock.utility.js.map +0 -1
  219. package/dist/services/state-management/local-encryption.state-provider.d.ts +0 -71
  220. package/dist/services/state-management/local-encryption.state-provider.js +0 -116
  221. package/dist/services/state-management/local-encryption.state-provider.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { createReadStream, createWriteStream } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
+ import { Readable } from 'node:stream';
4
+ import { pipeline } from 'node:stream/promises';
5
+ import { TransactionError } from '../../errors/index.js';
6
+ /**
7
+ * The one place octo speaks HTTP.
8
+ *
9
+ * A response is handed back whatever its status, so the caller decides what a refusal means instead
10
+ * of catching one. Only a request that never reached a server throws.
11
+ *
12
+ * @internal
13
+ */
14
+ export class HttpUtility {
15
+ /**
16
+ * Downloads `url` into `filePath`, streaming it so its size is not bounded by memory. False when the
17
+ * server refused it or the download was cut short, which can leave part of the file behind.
18
+ *
19
+ * @internal
20
+ * @throws {@link TransactionError} When the host could not be reached at all.
21
+ */
22
+ static async downloadToFile(url, filePath) {
23
+ const response = await this.send(url);
24
+ if (!response.ok || !response.body) {
25
+ return false;
26
+ }
27
+ try {
28
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(filePath));
29
+ return true;
30
+ }
31
+ catch (error) {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * What a refused request said went wrong, as one line. A service that answers a list - a failed
37
+ * validation usually does - is read as a sentence, and a body that is not JSON as its status.
38
+ *
39
+ * @internal
40
+ */
41
+ static async readErrorMessage(response) {
42
+ let body;
43
+ try {
44
+ body = (await response.json());
45
+ }
46
+ catch (error) {
47
+ return `${response.status} ${response.statusText}`;
48
+ }
49
+ const message = Array.isArray(body.message) ? body.message.join(', ') : body.message;
50
+ return message || `${response.status} ${response.statusText}`;
51
+ }
52
+ /**
53
+ * A body may be a stream, which is sent as it is read rather than held in memory. A stream's length
54
+ * is unknown to fetch, so a server that needs one is given `Content-Length` in `headers`.
55
+ *
56
+ * @internal
57
+ * @throws {@link TransactionError} When the host could not be reached at all, which is a different
58
+ * thing from being refused by it and is the caller's to report differently.
59
+ */
60
+ static async send(url, { body, headers = {}, method = 'GET' } = {}) {
61
+ try {
62
+ // `duplex` is what lets the body be a stream; Node's fetch refuses one without it.
63
+ return await fetch(url, { body, duplex: 'half', headers, method });
64
+ }
65
+ catch (error) {
66
+ throw new TransactionError(`Cannot reach "${new URL(url).origin}"!`);
67
+ }
68
+ }
69
+ /**
70
+ * {@link HttpUtility.send} with a JSON body, which is every request octo makes that carries one
71
+ * other than an upload.
72
+ *
73
+ * @internal
74
+ */
75
+ static async sendJson(url, { body, headers = {}, method = 'GET' } = {}) {
76
+ return this.send(url, {
77
+ body: body === undefined ? undefined : JSON.stringify(body),
78
+ headers: { ...headers, 'Content-Type': 'application/json' },
79
+ method,
80
+ });
81
+ }
82
+ /**
83
+ * PUTs the file at `filePath` to `url`, streaming it so its size is not bounded by memory, with its
84
+ * `Content-Length`, which a presigned URL is usually signed with.
85
+ *
86
+ * @internal
87
+ * @throws {@link TransactionError} When the host could not be reached at all.
88
+ */
89
+ static async uploadFile(url, filePath, headers = {}) {
90
+ const { size } = await stat(filePath);
91
+ return this.send(url, {
92
+ body: Readable.toWeb(createReadStream(filePath)),
93
+ headers: { ...headers, 'Content-Length': String(size) },
94
+ method: 'PUT',
95
+ });
96
+ }
97
+ }
98
+ //# sourceMappingURL=http.utility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.utility.js","sourceRoot":"","sources":["../../../src/utilities/http/http.utility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAEhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IACtB;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,GAAW,EAAE,QAAgB;QACvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAA0B,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC;YACnG,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,QAAkB;QAC9C,IAAI,IAAqC,CAAC;QAC1C,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAoC,CAAC;QACpE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QACrD,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QACrF,OAAO,OAAO,IAAI,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;IAChE,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,CACf,GAAW,EACX,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,GAAG,KAAK,KAA6E,EAAE;QAEnH,IAAI,CAAC;YACH,mFAAmF;YACnF,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAiB,CAAC,CAAC;QACpF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,gBAAgB,CAAC,iBAAiB,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,QAAQ,CACnB,GAAW,EACX,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,GAAG,KAAK,KAA4E,EAAE;QAElH,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACpB,IAAI,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC3D,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC3D,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,GAAW,EAAE,QAAgB,EAAE,UAAkC,EAAE;QACzF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;YACpB,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAmB;YAClE,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;YACvD,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -1,10 +1,30 @@
1
1
  /**
2
- * @internal
2
+ * String helpers for module and resource authors: hashing values into stable ids, fitting names into
3
+ * a provider's length limit, and making names safe for terraform.
4
+ *
5
+ * @group Utilities/String
3
6
  */
4
7
  export declare class StringUtility {
5
8
  /**
6
- * Produces a short deterministic hash of a value, with object keys sorted recursively,
7
- * so the hash does not change when key insertion order does.
9
+ * Shortens a name to at most `maxLength` characters, replacing what is cut with a digest of the whole
10
+ * name. Two names that differ anywhere still differ after shortening, and the same name always
11
+ * shortens the same way. A name already within the limit is returned as it is.
12
+ *
13
+ * Use it where a provider limits a name that is built by joining others, e.g. a distribution name and
14
+ * an origin id, so that cutting the tail alone could make two names collide.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * StringUtility.boundedName('my-distribution-with-a-very-long-origin-id', 20);
19
+ * // The first 11 characters, a dash and 8 hex characters: 20 in all.
20
+ * ```
21
+ */
22
+ static boundedName(name: string, maxLength: number): string;
23
+ /**
24
+ * Produces a 16-character hex hash of any value, with object keys sorted recursively, so the hash
25
+ * does not change when key insertion order does. Hash several parts as an array,
26
+ * `deterministicHash([bucketName, accessPrefix])`, which keeps `['ab', 'c']` and `['a', 'bc']` apart.
27
+ * Take a prefix of it where a shorter id is enough.
8
28
  */
9
29
  static deterministicHash(value: unknown): string;
10
30
  /**
@@ -1,11 +1,37 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  /**
3
- * @internal
3
+ * String helpers for module and resource authors: hashing values into stable ids, fitting names into
4
+ * a provider's length limit, and making names safe for terraform.
5
+ *
6
+ * @group Utilities/String
4
7
  */
5
8
  export class StringUtility {
6
9
  /**
7
- * Produces a short deterministic hash of a value, with object keys sorted recursively,
8
- * so the hash does not change when key insertion order does.
10
+ * Shortens a name to at most `maxLength` characters, replacing what is cut with a digest of the whole
11
+ * name. Two names that differ anywhere still differ after shortening, and the same name always
12
+ * shortens the same way. A name already within the limit is returned as it is.
13
+ *
14
+ * Use it where a provider limits a name that is built by joining others, e.g. a distribution name and
15
+ * an origin id, so that cutting the tail alone could make two names collide.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * StringUtility.boundedName('my-distribution-with-a-very-long-origin-id', 20);
20
+ * // The first 11 characters, a dash and 8 hex characters: 20 in all.
21
+ * ```
22
+ */
23
+ static boundedName(name, maxLength) {
24
+ if (name.length <= maxLength) {
25
+ return name;
26
+ }
27
+ const digest = StringUtility.deterministicHash(name).substring(0, 8);
28
+ return `${name.substring(0, maxLength - digest.length - 1)}-${digest}`;
29
+ }
30
+ /**
31
+ * Produces a 16-character hex hash of any value, with object keys sorted recursively, so the hash
32
+ * does not change when key insertion order does. Hash several parts as an array,
33
+ * `deterministicHash([bucketName, accessPrefix])`, which keeps `['ab', 'c']` and `['a', 'bc']` apart.
34
+ * Take a prefix of it where a shorter id is enough.
9
35
  */
10
36
  static deterministicHash(value) {
11
37
  const stableStringify = (v) => {
@@ -1 +1 @@
1
- {"version":3,"file":"string.utility.js","sourceRoot":"","sources":["../../../src/utilities/string/string.utility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,OAAO,aAAa;IACxB;;;OAGG;IACH,MAAM,CAAC,iBAAiB,CAAC,KAAc;QACrC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAU,EAAE;YAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrB,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YACjD,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9D,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAE,CAA6B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACxH,CAAC,CAAC;QACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,8BAA8B,CAAC,KAAa;QACjD,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,qBAAqB,CAAC,KAAa;QACxC,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;CACF"}
1
+ {"version":3,"file":"string.utility.js","sourceRoot":"","sources":["../../../src/utilities/string/string.utility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACxB;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,WAAW,CAAC,IAAY,EAAE,SAAiB;QAChD,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrE,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;IACzE,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,iBAAiB,CAAC,KAAc;QACrC,MAAM,eAAe,GAAG,CAAC,CAAU,EAAU,EAAE;YAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACxC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YAC3B,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrB,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YACjD,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC,CAAC,IAAI,EAAE,CAAC;YAC9D,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,eAAe,CAAE,CAA6B,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QACxH,CAAC,CAAC;QACF,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,8BAA8B,CAAC,KAAa;QACjD,OAAO,KAAK,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,qBAAqB,CAAC,KAAa;QACxC,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;CACF"}
@@ -1,11 +1,8 @@
1
- import { type ExecFileOptionsWithStringEncoding } from 'node:child_process';
2
1
  import type { TerraformOutputs } from '../../modes/commit.mode.js';
3
2
  import type { TerraformPlan } from '../../modes/validate.mode.js';
4
3
  interface TerraformRawResult {
5
4
  readonly args: string[];
6
5
  readonly exitCode: number;
7
- readonly stderr: string;
8
- readonly stdout: string;
9
6
  }
10
7
  /**
11
8
  * The result of planning every module folder under a terragrunt directory.
@@ -39,6 +36,10 @@ interface TerraformPlanResult {
39
36
  * @group Utilities/Terraform
40
37
  */
41
38
  export declare class TerraformUtility {
39
+ /**
40
+ * The id of the last process any instance started, so ids never repeat within one octo process.
41
+ */
42
+ private static lastProcessId;
42
43
  private onceArgs;
43
44
  private readonly options;
44
45
  /**
@@ -47,35 +48,35 @@ export declare class TerraformUtility {
47
48
  */
48
49
  private readonly requestedTimeoutInMs;
49
50
  /**
50
- * @param options Overrides for the binaries invoked and the per-command timeout. `terragruntBinary`
51
- * defaults to `terragrunt`.
51
+ * @param options Overrides for the binaries invoked, the per-command timeout, and the state
52
+ * check-in interval. `terragruntBinary` defaults to `terragrunt`.
52
53
  *
53
54
  * `timeoutInMs` defaults to 60 minutes for the commands that only read - `plan`, `output`, `show`.
54
55
  * `apply` and `destroy` run unbounded unless it is given: a timeout there turns a slow apply into
55
56
  * one killed part way through a mutation, and how long a stack legitimately takes is a fact about
56
57
  * that stack rather than something a default can know. What bounds them instead is the caller -
57
58
  * a CI job's own timeout, or a person at a terminal.
59
+ *
60
+ * `stateCheckInIntervalInMs` is how often terraform state is checked into the state provider while
61
+ * a command runs, 10 seconds by default. Terraform rewrites it as each resource completes.
58
62
  */
59
63
  constructor(options?: {
64
+ stateCheckInIntervalInMs?: number;
60
65
  terraformBinary?: string;
61
66
  terragruntBinary?: string;
62
67
  timeoutInMs?: number;
63
68
  });
64
69
  /**
65
- * Runs `terragrunt run-all apply` over every generated module folder.
70
+ * Runs `terragrunt run --all -- apply` over every generated module folder.
66
71
  *
67
72
  * **This changes real infrastructure**, and runs with `-auto-approve`. Use
68
73
  * {@link TerraformUtility.findPlannedDestroys} beforehand if a destroy needs confirming.
69
74
  *
70
- * @param onOutput Called with each chunk terragrunt writes, as it writes it. An apply can run for
71
- * tens of minutes, and the buffered result only arrives at the end - without this the caller has
72
- * nothing to show for the whole run. The chunks are also still accumulated into the result.
75
+ * @param terragruntDir The directory octo generated the module folders into.
73
76
  */
74
- apply(terragruntDir: string, { onOutput }?: {
75
- onOutput?: (chunk: string, stream: 'stderr' | 'stdout') => void;
76
- }): Promise<TerraformRawResult>;
77
+ apply(terragruntDir: string): Promise<TerraformRawResult>;
77
78
  /**
78
- * Runs `terragrunt run-all destroy` over every generated module folder.
79
+ * Runs `terragrunt run --all -- destroy` over every generated module folder.
79
80
  *
80
81
  * **This tears down real infrastructure**, and runs with `-auto-approve`.
81
82
  */
@@ -100,8 +101,9 @@ export declare class TerraformUtility {
100
101
  /**
101
102
  * Reads the outputs of every generated module folder.
102
103
  *
104
+ * @param terragruntDir The directory octo generated the module folders into.
103
105
  * @param options Pass `{ json: true }` to get the outputs parsed and keyed by `moduleId`, which is
104
- * the shape {@link Octo.commit} takes. Without it, terragrunt's raw output is returned.
106
+ * the shape {@link Octo.commit} takes. Without it, terragrunt's output goes out only as events.
105
107
  */
106
108
  output(terragruntDir: string, options: {
107
109
  json: true;
@@ -115,9 +117,10 @@ export declare class TerraformUtility {
115
117
  /**
116
118
  * Plans every generated module folder.
117
119
  *
120
+ * @param terragruntDir The directory octo generated the module folders into.
118
121
  * @param options Pass `{ json: true }` to get the plans parsed and keyed by `moduleId`, which is
119
122
  * the shape {@link Octo.validate} takes - along with the `errors` of any folder that could not be
120
- * planned. Without it, terragrunt's raw output is returned.
123
+ * planned. Without it, terragrunt's output goes out only as events.
121
124
  */
122
125
  plan(terragruntDir: string, options: {
123
126
  json: true;
@@ -133,15 +136,21 @@ export declare class TerraformUtility {
133
136
  */
134
137
  setArgsOnce(args: string[]): this;
135
138
  /**
136
- * Runs `terragrunt run-all validate` over every generated module folder, checking the generated
139
+ * Runs `terragrunt run --all -- validate` over every generated module folder, checking the generated
137
140
  * HCL is well-formed. Unrelated to {@link Octo.validate}, which compares plans against intent.
138
141
  */
139
142
  validate(terragruntDir: string): Promise<TerraformRawResult>;
140
143
  /**
141
- * @param onOutput Receives each chunk as the child writes it. `execFile` keeps accumulating into
142
- * its own buffers regardless, so the resolved result is unchanged whether or not this is passed.
144
+ * Runs `file` with `args`, handing each chunk it writes to `onChunk` as it arrives. Keeps its whole
145
+ * stdout only when `keepStdout` asks for it, and the last {@link STDERR_TAIL_LENGTH} characters of its
146
+ * stderr, so a process writing for an hour holds no more memory than one writing for a second.
143
147
  */
144
- protected execFileAsync(file: string, args: string[], options: ExecFileOptionsWithStringEncoding, onOutput?: (chunk: string, stream: 'stderr' | 'stdout') => void): Promise<{
148
+ protected spawnAsync(file: string, args: string[], options: {
149
+ cwd?: string;
150
+ env: NodeJS.ProcessEnv;
151
+ keepStdout: boolean;
152
+ timeout?: number;
153
+ }, onChunk: (text: string, stream: 'stderr' | 'stdout') => void): Promise<{
145
154
  stderr: string;
146
155
  stdout: string;
147
156
  }>;
@@ -173,6 +182,15 @@ export declare class TerraformUtility {
173
182
  * The `terragrunt run --all` invocation itself, wrapped in its started/finished pair.
174
183
  */
175
184
  private invokeRunAll;
185
+ /**
186
+ * Moves the terraform state of every move the last generate recorded out of its old module folder
187
+ * into its new one, with `terraform state mv`, and copies the moved resources' outputs along, since
188
+ * folders depending on the new one read them there. An address the old folder's state does not hold,
189
+ * or the new one's already holds, is skipped, which is what a move already made leaves behind.
190
+ * Records in `originalStates` what each state file held before its first move, `undefined` where none
191
+ * existed.
192
+ */
193
+ private moveStates;
176
194
  /**
177
195
  * Runs `terragrunt output -json` in one module folder.
178
196
  *
@@ -191,6 +209,17 @@ export declare class TerraformUtility {
191
209
  * abort every other folder's result. The failure is carried so `validate` can report it verbatim.
192
210
  */
193
211
  private readPlan;
212
+ /**
213
+ * The directory with every symbolic link on its path resolved, or the directory as given when it does
214
+ * not exist. Terragrunt drops `dependency` blocks when its working directory is reached through a
215
+ * symbolic link, as macOS's temporary directory is.
216
+ */
217
+ private resolveRealPath;
218
+ /**
219
+ * Writes back what each state file held before {@link TerraformUtility.moveStates}, deleting one that
220
+ * did not exist.
221
+ */
222
+ private restoreStates;
194
223
  /**
195
224
  * Runs one terragrunt command in a single module folder (`cwd` = that folder).
196
225
  */
@@ -200,17 +229,37 @@ export declare class TerraformUtility {
200
229
  * the terraform state of every folder checked out around it.
201
230
  */
202
231
  private runAll;
232
+ /**
233
+ * Runs `binary` with `args` in `dir`. Its stdout is returned only when it is asked for JSON, which is
234
+ * the output octo reads; every other process's output goes out only as events.
235
+ */
236
+ private runIn;
237
+ /**
238
+ * Runs one process. Every terraform and terragrunt process octo starts goes through here, and goes out
239
+ * as events: a {@link TerraformProcessStartedEvent}, a {@link TerraformProcessOutputEvent} for each
240
+ * chunk it writes, and a {@link TerraformProcessExitedEvent}, all named after `command`.
241
+ *
242
+ * @throws {@link TerraformCommandError} When the process cannot start or exits non-zero.
243
+ */
244
+ private runProcess;
203
245
  /**
204
246
  * Wraps whatever a failed spawn threw into a {@link TerraformCommandError}.
205
247
  */
206
248
  private toTerraformCommandError;
207
249
  /**
208
- * Checks every module folder's terraform state out of Octo's state provider, runs `body`, and
209
- * checks it back in.
250
+ * Checks every module folder's terraform state out of Octo's state provider, moves the state of every
251
+ * resource generate moved between folders, runs `body`, and checks the state back in.
252
+ *
253
+ * Only a command that changes infrastructure (`keepMoves`) checks the moves in. Any other runs on them
254
+ * and puts back what the state files held before, so a move nobody applied never reaches the provider.
210
255
  *
211
256
  * The check-in runs on the way out of a failure as much as a success, because what terraform has
212
257
  * already built is real whether or not the command that built it returned cleanly. A timer checks
213
258
  * in through the command as well, bounding what a killed runner loses.
259
+ *
260
+ * A failed command keeps its own error when the check-in after it fails too; the check-in's error
261
+ * is printed on stderr. The folder's state is ahead of the provider's then, and the next run keeps
262
+ * it and checks it in.
214
263
  */
215
264
  private withTerraformState;
216
265
  }