alchemy 0.60.2 → 0.62.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 (174) hide show
  1. package/README.md +1 -2
  2. package/bin/alchemy.js +1480 -1498
  3. package/bin/commands/init.ts +94 -25
  4. package/bin/services/execute-alchemy.ts +32 -23
  5. package/lib/alchemy.d.ts +2 -2
  6. package/lib/alchemy.d.ts.map +1 -1
  7. package/lib/alchemy.js +1 -0
  8. package/lib/alchemy.js.map +1 -1
  9. package/lib/aws/client-props.d.ts +149 -0
  10. package/lib/aws/client-props.d.ts.map +1 -0
  11. package/lib/aws/client-props.js +1 -0
  12. package/lib/aws/client-props.js.map +1 -0
  13. package/lib/aws/credentials.d.ts +68 -5
  14. package/lib/aws/credentials.d.ts.map +1 -1
  15. package/lib/aws/credentials.js +148 -0
  16. package/lib/aws/credentials.js.map +1 -1
  17. package/lib/aws/ec2/internet-gateway-attachment.d.ts +57 -3
  18. package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -1
  19. package/lib/aws/ec2/internet-gateway-attachment.js +57 -3
  20. package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -1
  21. package/lib/aws/ec2/internet-gateway.d.ts +68 -40
  22. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -1
  23. package/lib/aws/ec2/internet-gateway.js +68 -40
  24. package/lib/aws/ec2/internet-gateway.js.map +1 -1
  25. package/lib/aws/ec2/nat-gateway.d.ts +99 -18
  26. package/lib/aws/ec2/nat-gateway.d.ts.map +1 -1
  27. package/lib/aws/ec2/nat-gateway.js +113 -37
  28. package/lib/aws/ec2/nat-gateway.js.map +1 -1
  29. package/lib/aws/ec2/route-table-association.d.ts +1 -1
  30. package/lib/aws/ec2/route-table-association.js +1 -1
  31. package/lib/aws/ec2/route-table.d.ts +64 -1
  32. package/lib/aws/ec2/route-table.d.ts.map +1 -1
  33. package/lib/aws/ec2/route-table.js +64 -1
  34. package/lib/aws/ec2/route-table.js.map +1 -1
  35. package/lib/aws/ec2/route.d.ts +60 -1
  36. package/lib/aws/ec2/route.d.ts.map +1 -1
  37. package/lib/aws/ec2/route.js +60 -1
  38. package/lib/aws/ec2/route.js.map +1 -1
  39. package/lib/aws/ec2/security-group.d.ts +96 -3
  40. package/lib/aws/ec2/security-group.d.ts.map +1 -1
  41. package/lib/aws/ec2/security-group.js +96 -3
  42. package/lib/aws/ec2/security-group.js.map +1 -1
  43. package/lib/aws/ec2/subnet.d.ts +61 -3
  44. package/lib/aws/ec2/subnet.d.ts.map +1 -1
  45. package/lib/aws/ec2/subnet.js +61 -3
  46. package/lib/aws/ec2/subnet.js.map +1 -1
  47. package/lib/aws/ec2/utils.d.ts +10 -2
  48. package/lib/aws/ec2/utils.d.ts.map +1 -1
  49. package/lib/aws/ec2/utils.js +68 -7
  50. package/lib/aws/ec2/utils.js.map +1 -1
  51. package/lib/aws/ec2/vpc.d.ts +50 -2
  52. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  53. package/lib/aws/ec2/vpc.js +50 -2
  54. package/lib/aws/ec2/vpc.js.map +1 -1
  55. package/lib/aws/index.d.ts +2 -0
  56. package/lib/aws/index.d.ts.map +1 -1
  57. package/lib/aws/index.js +2 -0
  58. package/lib/aws/index.js.map +1 -1
  59. package/lib/cloudflare/api-response.js +2 -2
  60. package/lib/cloudflare/api-response.js.map +1 -1
  61. package/lib/cloudflare/api.d.ts +19 -0
  62. package/lib/cloudflare/api.d.ts.map +1 -1
  63. package/lib/cloudflare/api.js +4 -0
  64. package/lib/cloudflare/api.js.map +1 -1
  65. package/lib/cloudflare/assets.d.ts.map +1 -1
  66. package/lib/cloudflare/assets.js +3 -4
  67. package/lib/cloudflare/assets.js.map +1 -1
  68. package/lib/cloudflare/bucket.d.ts +5 -3
  69. package/lib/cloudflare/bucket.d.ts.map +1 -1
  70. package/lib/cloudflare/bucket.js +28 -20
  71. package/lib/cloudflare/bucket.js.map +1 -1
  72. package/lib/cloudflare/bundle/plugin-compat-warning.js +7 -7
  73. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -1
  74. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
  75. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  76. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  77. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  78. package/lib/cloudflare/d1-migrations.js +3 -2
  79. package/lib/cloudflare/d1-migrations.js.map +1 -1
  80. package/lib/cloudflare/index.d.ts +2 -1
  81. package/lib/cloudflare/index.d.ts.map +1 -1
  82. package/lib/cloudflare/index.js +2 -1
  83. package/lib/cloudflare/index.js.map +1 -1
  84. package/lib/cloudflare/kv-namespace.d.ts +2 -2
  85. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  86. package/lib/cloudflare/kv-namespace.js.map +1 -1
  87. package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
  88. package/lib/cloudflare/miniflare/build-worker-options.js +16 -3
  89. package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
  90. package/lib/cloudflare/miniflare/miniflare-controller.d.ts.map +1 -1
  91. package/lib/cloudflare/miniflare/miniflare-controller.js +3 -0
  92. package/lib/cloudflare/miniflare/miniflare-controller.js.map +1 -1
  93. package/lib/cloudflare/oauth.js +3 -3
  94. package/lib/cloudflare/oauth.js.map +1 -1
  95. package/lib/cloudflare/quick-tunnel.d.ts +17 -0
  96. package/lib/cloudflare/quick-tunnel.d.ts.map +1 -0
  97. package/lib/cloudflare/quick-tunnel.js +26 -0
  98. package/lib/cloudflare/quick-tunnel.js.map +1 -0
  99. package/lib/cloudflare/sveltekit/plugin.d.ts +2 -2
  100. package/lib/cloudflare/sveltekit/plugin.d.ts.map +1 -1
  101. package/lib/cloudflare/sveltekit/plugin.js +5 -4
  102. package/lib/cloudflare/sveltekit/plugin.js.map +1 -1
  103. package/lib/cloudflare/website.d.ts.map +1 -1
  104. package/lib/cloudflare/website.js +26 -97
  105. package/lib/cloudflare/website.js.map +1 -1
  106. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  107. package/lib/cloudflare/worker-bundle.js +11 -9
  108. package/lib/cloudflare/worker-bundle.js.map +1 -1
  109. package/lib/cloudflare/worker-tail.d.ts.map +1 -1
  110. package/lib/cloudflare/worker-tail.js +6 -6
  111. package/lib/cloudflare/worker-tail.js.map +1 -1
  112. package/lib/resource.d.ts +1 -0
  113. package/lib/resource.d.ts.map +1 -1
  114. package/lib/resource.js +13 -2
  115. package/lib/resource.js.map +1 -1
  116. package/lib/scope.d.ts +38 -1
  117. package/lib/scope.d.ts.map +1 -1
  118. package/lib/scope.js +44 -19
  119. package/lib/scope.js.map +1 -1
  120. package/lib/state/cloudflare-state-store.d.ts.map +1 -1
  121. package/lib/state/cloudflare-state-store.js +2 -1
  122. package/lib/state/cloudflare-state-store.js.map +1 -1
  123. package/lib/util/idempotent-spawn.d.ts +24 -0
  124. package/lib/util/idempotent-spawn.d.ts.map +1 -0
  125. package/lib/util/idempotent-spawn.js +237 -0
  126. package/lib/util/idempotent-spawn.js.map +1 -0
  127. package/lib/util/index.d.ts +2 -0
  128. package/lib/util/index.d.ts.map +1 -0
  129. package/lib/util/index.js +2 -0
  130. package/lib/util/index.js.map +1 -0
  131. package/lib/util/safe-fetch.d.ts +24 -0
  132. package/lib/util/safe-fetch.d.ts.map +1 -1
  133. package/lib/util/safe-fetch.js +72 -0
  134. package/lib/util/safe-fetch.js.map +1 -1
  135. package/package.json +9 -5
  136. package/src/alchemy.ts +3 -2
  137. package/src/aws/client-props.ts +156 -0
  138. package/src/aws/credentials.ts +165 -4
  139. package/src/aws/ec2/internet-gateway-attachment.ts +59 -4
  140. package/src/aws/ec2/internet-gateway.ts +70 -41
  141. package/src/aws/ec2/nat-gateway.ts +123 -33
  142. package/src/aws/ec2/route-table-association.ts +1 -1
  143. package/src/aws/ec2/route-table.ts +66 -2
  144. package/src/aws/ec2/route.ts +62 -2
  145. package/src/aws/ec2/security-group.ts +98 -4
  146. package/src/aws/ec2/subnet.ts +63 -4
  147. package/src/aws/ec2/utils.ts +73 -7
  148. package/src/aws/ec2/vpc.ts +52 -3
  149. package/src/aws/index.ts +2 -0
  150. package/src/cloudflare/api-response.ts +2 -2
  151. package/src/cloudflare/api.ts +23 -0
  152. package/src/cloudflare/assets.ts +3 -4
  153. package/src/cloudflare/bucket.ts +39 -29
  154. package/src/cloudflare/bundle/plugin-compat-warning.ts +7 -7
  155. package/src/cloudflare/cloudflare-env-proxy.ts +2 -2
  156. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  157. package/src/cloudflare/d1-migrations.ts +4 -5
  158. package/src/cloudflare/index.ts +2 -1
  159. package/src/cloudflare/kv-namespace.ts +26 -27
  160. package/src/cloudflare/miniflare/build-worker-options.ts +16 -3
  161. package/src/cloudflare/miniflare/miniflare-controller.ts +4 -0
  162. package/src/cloudflare/oauth.ts +3 -3
  163. package/src/cloudflare/quick-tunnel.ts +30 -0
  164. package/src/cloudflare/sveltekit/plugin.ts +5 -5
  165. package/src/cloudflare/website.ts +30 -111
  166. package/src/cloudflare/worker-bundle.ts +11 -9
  167. package/src/cloudflare/worker-tail.ts +6 -8
  168. package/src/resource.ts +19 -2
  169. package/src/scope.ts +111 -20
  170. package/src/state/cloudflare-state-store.ts +2 -1
  171. package/src/util/idempotent-spawn.ts +303 -0
  172. package/src/util/index.ts +1 -0
  173. package/src/util/safe-fetch.ts +114 -0
  174. package/templates/rwsdk/package.json +1 -1
@@ -52,4 +52,76 @@ async function dispatcher() {
52
52
  }
53
53
  return agent.value;
54
54
  }
55
+ /**
56
+ * Fetch with exponential backoff retry logic for 404 responses, expecting 200 OK.
57
+ * This is a convenience wrapper around fetchAndExpectStatus with expectedStatus=200.
58
+ *
59
+ * @param input URL or Request object
60
+ * @param init RequestInit options
61
+ * @param maxAttempts Maximum number of retry attempts (default: 10)
62
+ * @param maxWaitTime Maximum total wait time in milliseconds (default: 30000)
63
+ * @returns The successful Response
64
+ */
65
+ export async function fetchAndExpectOK(input, init, maxAttempts = 10, maxWaitTime = 30000) {
66
+ return fetchAndExpectStatus(input, init, 200, maxAttempts, maxWaitTime);
67
+ }
68
+ /**
69
+ * Fetch and expect a specific status code with retry logic.
70
+ * Cloudflare's control plane is eventually consistent, so resources may
71
+ * return 404 immediately after creation before becoming available.
72
+ *
73
+ * @param input URL or Request object
74
+ * @param init RequestInit options
75
+ * @param expectedStatus Expected HTTP status code (default: 200)
76
+ * @param maxAttempts Maximum number of retry attempts (default: 10)
77
+ * @param maxWaitTime Maximum total wait time in milliseconds (default: 30000)
78
+ * @returns The successful Response
79
+ */
80
+ export async function fetchAndExpectStatus(input, init, expectedStatus = 200, maxAttempts = 20, maxWaitTime = 30000) {
81
+ let attempt = 0;
82
+ let lastError;
83
+ while (attempt < maxAttempts) {
84
+ try {
85
+ const response = await safeFetch(input, init);
86
+ // If we get the expected status, return it
87
+ if (response.status === expectedStatus) {
88
+ return response;
89
+ }
90
+ // If we get a 500, 404 when expecting something else, retry with backoff
91
+ if (response.status >= 500 ||
92
+ (response.status === 404 && expectedStatus !== 404)) {
93
+ attempt++;
94
+ if (attempt >= maxAttempts) {
95
+ throw new Error(`Expected status ${expectedStatus} but got 404 after ${maxAttempts} attempts. This may indicate eventual consistency issues.`);
96
+ }
97
+ const backoffTime = Math.min(attempt * 1500, maxWaitTime / maxAttempts);
98
+ console.log(`Expected status ${expectedStatus} but got 404, retrying in ${backoffTime}ms (attempt ${attempt}/${maxAttempts})...`);
99
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
100
+ continue;
101
+ }
102
+ // For other status codes, throw immediately
103
+ throw new Error(`Expected status ${expectedStatus} but got ${response.status}: ${response.statusText}`);
104
+ }
105
+ catch (error) {
106
+ if (error instanceof Error) {
107
+ lastError = error;
108
+ // If it's not an HTTP error, retry with backoff
109
+ if (!error.message.startsWith("Expected status") &&
110
+ !error.message.startsWith("HTTP ")) {
111
+ attempt++;
112
+ if (attempt >= maxAttempts) {
113
+ break;
114
+ }
115
+ const backoffTime = Math.min(attempt * 1500, maxWaitTime / maxAttempts);
116
+ console.log(`Network error, retrying in ${backoffTime}ms (attempt ${attempt}/${maxAttempts}): ${error.message}`);
117
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
118
+ continue;
119
+ }
120
+ }
121
+ // For status code mismatches, throw immediately
122
+ throw error;
123
+ }
124
+ }
125
+ throw lastError || new Error(`Failed after ${maxAttempts} attempts`);
126
+ }
55
127
  //# sourceMappingURL=safe-fetch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../../src/util/safe-fetch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAA2B,EAC3B,UAAuB,EAAE,EACzB,OAAO,GAAG,EAAE;IAEZ,IAAI,SAAc,CAAC;IACnB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE;gBACtB,GAAG,OAAO;gBACV,oEAAoE;gBACpE,UAAU,EAAE,MAAM,UAAU,EAAE;aAChB,CAAC,CAAC;QACpB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACxB,SAAS,GAAG,GAAG,CAAC;YAChB,MAAM,WAAW,GACf,uBAAuB,CAAC,GAAG,CAAC,IAAI,uBAAuB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAErE,IAAI,CAAC,WAAW,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;gBACxC,MAAM,GAAG,CAAC;YACZ,CAAC;YAED,MAAM,CAAC,IAAI,CAAC,SAAS,OAAO,IAAI,OAAO,QAAQ,GAAG,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YACnE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,sBAAsB;QAChF,CAAC;IACH,CAAC;IACD,MAAM,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,uBAAuB,CAAC,GAAQ;IACvC,OAAO,CACL,GAAG,EAAE,IAAI,KAAK,gBAAgB;QAC9B,GAAG,EAAE,IAAI,KAAK,YAAY;QAC1B,GAAG,EAAE,IAAI,KAAK,yBAAyB;QACvC,GAAG,EAAE,IAAI,KAAK,OAAO;QACrB,GAAG,EAAE,IAAI,KAAK,YAAY,CAC3B,CAAC;AACJ,CAAC;AAED,IAAI,KAIS,CAAC;AAEd,KAAK,UAAU,UAAU;IACvB,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IACD,IAAI,CAAC;QACH,uCAAuC;QACvC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEzC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,gEAAgE;YAChE,KAAK,GAAG;gBACN,KAAK,EAAE,IAAI,KAAK,CAAC;oBACf,UAAU,EAAE,CAAC,EAAE,gDAAgD;oBAC/D,2DAA2D;iBAC5D,CAAC;aACH,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"safe-fetch.js","sourceRoot":"","sources":["../../src/util/safe-fetch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.60.2",
3
+ "version": "0.62.0",
4
4
  "license": "Apache-2.0",
5
5
  "author": "Sam Goodwin <sam@alchemy.run>",
6
6
  "homepage": "https://alchemy.run",
@@ -149,6 +149,10 @@
149
149
  "bun": "./src/state/index.ts",
150
150
  "import": "./lib/state/index.js"
151
151
  },
152
+ "./util": {
153
+ "bun": "./src/util/index.ts",
154
+ "import": "./lib/util/index.js"
155
+ },
152
156
  "./web/astro": {
153
157
  "bun": "./src/web/astro.ts",
154
158
  "import": "./lib/web/astro.js"
@@ -168,7 +172,7 @@
168
172
  },
169
173
  "dependencies": {
170
174
  "@aws-sdk/credential-providers": "^3.0.0",
171
- "@cloudflare/unenv-preset": "^2.4.0",
175
+ "@cloudflare/unenv-preset": "^2.6.1",
172
176
  "@cloudflare/workers-shared": "^0.17.5",
173
177
  "@iarna/toml": "^2.2.5",
174
178
  "@smithy/node-config-provider": "^4.0.0",
@@ -180,9 +184,10 @@
180
184
  "execa": "^9.6.0",
181
185
  "fast-json-patch": "^3.1.1",
182
186
  "fast-xml-parser": "^5.2.5",
187
+ "find-process": "^2.0.0",
183
188
  "glob": "^10.0.0",
189
+ "picocolors": "^1.1.1",
184
190
  "jszip": "^3.0.0",
185
- "kleur": "^4.1.5",
186
191
  "libsodium-wrappers": "^0.7.15",
187
192
  "miniflare": "^4.20250712.0",
188
193
  "neverthrow": "^8.2.0",
@@ -191,7 +196,7 @@
191
196
  "pathe": "^2.0.3",
192
197
  "signal-exit": "^4.1.0",
193
198
  "turndown": "^7.0.0",
194
- "unenv": "2.0.0-rc.15",
199
+ "unenv": "2.0.0-rc.19",
195
200
  "ws": "^8.18.3",
196
201
  "yaml": "^2.0.0"
197
202
  },
@@ -259,7 +264,6 @@
259
264
  "nitro-cloudflare-dev": "^0.2.2",
260
265
  "nitropack": "^2.12.4",
261
266
  "openpgp": "^6.1.0",
262
- "picocolors": "^1.1.1",
263
267
  "prettier": "^3.5.3",
264
268
  "rwsdk": "^0.1.28",
265
269
  "trpc-cli": "^0.10.2",
package/src/alchemy.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  type PendingResource,
16
16
  } from "./resource.ts";
17
17
  import { isRuntime } from "./runtime/global.ts";
18
- import { DEFAULT_STAGE, Scope } from "./scope.ts";
18
+ import { DEFAULT_STAGE, Scope, type ProviderCredentials } from "./scope.ts";
19
19
  import { secret } from "./secret.ts";
20
20
  import type { StateStoreType } from "./state.ts";
21
21
  import type { LoggerApi } from "./util/cli.ts";
@@ -193,6 +193,7 @@ If this is a mistake, you can disable this check by setting the ALCHEMY_CI_STATE
193
193
  });
194
194
  onExit((code) => {
195
195
  root.cleanup().then(() => {
196
+ code = code === 130 ? 0 : (code ?? 0);
196
197
  process.exit(code);
197
198
  });
198
199
  return true;
@@ -419,7 +420,7 @@ export interface ScopeOptions extends AlchemyOptions {
419
420
  enter: boolean;
420
421
  }
421
422
 
422
- export interface RunOptions extends AlchemyOptions {
423
+ export interface RunOptions extends AlchemyOptions, ProviderCredentials {
423
424
  /**
424
425
  * @default false
425
426
  */
@@ -0,0 +1,156 @@
1
+ import type { Secret } from "../secret.ts";
2
+
3
+ /**
4
+ * AWS client properties for credential configuration.
5
+ *
6
+ * This interface defines the properties that can be used to configure AWS credentials
7
+ * at different levels of your application. It supports the three-tier credential resolution
8
+ * system (global → scope → resource) and enables multi-account and multi-region deployments.
9
+ *
10
+ * These properties can be specified at:
11
+ * 1. Resource level - directly in resource creation parameters
12
+ * 2. Scope level - in alchemy.run() options with `aws` property
13
+ * 3. Global level - through environment variables (AWS_REGION, AWS_PROFILE, etc.)
14
+ *
15
+ * The credential resolution follows a precedence order where resource-level credentials
16
+ * override scope-level credentials, which override global environment variables.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Resource-level credential overrides
21
+ * const vpc = await Vpc("main-vpc", {
22
+ * cidrBlock: "10.0.0.0/16",
23
+ * region: "us-west-2", // Override region
24
+ * profile: "production", // Override profile
25
+ * tags: { Name: "main-vpc" }
26
+ * });
27
+ * ```
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * // Scope-level credential configuration
32
+ * await alchemy.run("production", {
33
+ * aws: {
34
+ * region: "us-west-2",
35
+ * profile: "production",
36
+ * }
37
+ * }, async () => {
38
+ * // Resources created here will use the scope credentials by default
39
+ * const vpc = await Vpc("main-vpc", {
40
+ * cidrBlock: "10.0.0.0/16"
41
+ * });
42
+ * });
43
+ * ```
44
+ */
45
+ export interface AwsClientProps {
46
+ /**
47
+ * AWS access key ID for authentication.
48
+ *
49
+ * This corresponds to the AWS_ACCESS_KEY_ID environment variable.
50
+ * When provided at the resource or scope level, it overrides the environment variable.
51
+ */
52
+ accessKeyId?: Secret<string>;
53
+
54
+ /**
55
+ * AWS secret access key for authentication.
56
+ *
57
+ * This corresponds to the AWS_SECRET_ACCESS_KEY environment variable.
58
+ * When provided at the resource or scope level, it overrides the environment variable.
59
+ */
60
+ secretAccessKey?: Secret<string>;
61
+
62
+ /**
63
+ * AWS session token for temporary credentials.
64
+ *
65
+ * This corresponds to the AWS_SESSION_TOKEN environment variable.
66
+ * Used with temporary credentials from STS or when assuming a role.
67
+ */
68
+ sessionToken?: Secret<string>;
69
+
70
+ /**
71
+ * AWS region to use for API calls.
72
+ *
73
+ * This corresponds to the AWS_REGION or AWS_DEFAULT_REGION environment variables.
74
+ * When provided at the resource or scope level, it overrides the environment variables.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Resource in a specific region
79
+ * const euVpc = await Vpc("eu-vpc", {
80
+ * cidrBlock: "10.0.0.0/16",
81
+ * region: "eu-west-1",
82
+ * tags: { Name: "eu-vpc" }
83
+ * });
84
+ * ```
85
+ */
86
+ region?: string;
87
+
88
+ /**
89
+ * AWS profile name to use from credentials file.
90
+ *
91
+ * This corresponds to the AWS_PROFILE environment variable.
92
+ * Profiles are defined in ~/.aws/credentials or ~/.aws/config files.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // Resource using a specific profile
97
+ * const prodVpc = await Vpc("prod-vpc", {
98
+ * cidrBlock: "10.0.0.0/16",
99
+ * profile: "production-account",
100
+ * tags: { Name: "prod-vpc" }
101
+ * });
102
+ * ```
103
+ */
104
+ profile?: string;
105
+
106
+ /**
107
+ * AWS role ARN to assume for authentication.
108
+ *
109
+ * This corresponds to the AWS_ROLE_ARN environment variable.
110
+ * Used for cross-account access or for assuming roles with specific permissions.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * // Resource using role assumption
115
+ * const crossAccountVpc = await Vpc("cross-account-vpc", {
116
+ * cidrBlock: "10.0.0.0/16",
117
+ * roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
118
+ * roleSessionName: "alchemy-deployment",
119
+ * tags: { Name: "cross-account-vpc" }
120
+ * });
121
+ * ```
122
+ */
123
+ roleArn?: string;
124
+
125
+ /**
126
+ * External ID to use when assuming a role.
127
+ *
128
+ * This corresponds to the AWS_EXTERNAL_ID environment variable.
129
+ * Used as an additional security measure when assuming roles across accounts.
130
+ */
131
+ externalId?: string;
132
+
133
+ /**
134
+ * Session name to use when assuming a role.
135
+ *
136
+ * This corresponds to the AWS_ROLE_SESSION_NAME environment variable.
137
+ * Helps identify who/what is using the assumed role in AWS CloudTrail logs.
138
+ */
139
+ roleSessionName?: string;
140
+ }
141
+ /**
142
+ * AWS scope extensions - adds AWS credential support to scope options.
143
+ * This uses TypeScript module augmentation to extend the ProviderCredentials interface.
144
+ * Since ScopeOptions and RunOptions both extend ProviderCredentials,
145
+ * they automatically inherit these properties.
146
+ */
147
+ declare module "../scope.ts" {
148
+ interface ProviderCredentials {
149
+ /**
150
+ * AWS credentials configuration for this scope.
151
+ * All AWS resources created within this scope will inherit these credentials
152
+ * unless overridden at the resource level.
153
+ */
154
+ aws?: AwsClientProps;
155
+ }
156
+ }
@@ -1,6 +1,167 @@
1
- import type { Secret } from "../secret.ts";
1
+ import { alchemy } from "../alchemy.ts";
2
+ import { isSecret } from "../secret.ts";
3
+ import type { AwsClientProps } from "./client-props.ts";
2
4
 
3
- export interface AwsCredentials {
4
- accessKeyId: Secret;
5
- secretAccessKey: Secret;
5
+ /**
6
+ * Validate AWS client properties to ensure they are strings.
7
+ * This follows the same pattern as Cloudflare credential validation.
8
+ */
9
+ function validateAwsClientProps(props: AwsClientProps, context: string): void {
10
+ const validKeys = [
11
+ "accessKeyId",
12
+ "secretAccessKey",
13
+ "sessionToken",
14
+ "region",
15
+ "profile",
16
+ "roleArn",
17
+ "externalId",
18
+ "roleSessionName",
19
+ ];
20
+
21
+ for (const [key, value] of Object.entries(props)) {
22
+ if (!validKeys.includes(key)) {
23
+ continue; // Ignore unknown properties
24
+ }
25
+
26
+ if (value !== undefined && typeof value !== "string" && !isSecret(value)) {
27
+ throw new Error(
28
+ `Invalid AWS configuration in ${context}: Property '${key}' must be a string or Secret, got ${typeof value}. ` +
29
+ "Please ensure all AWS credential properties are strings or Secret objects.",
30
+ );
31
+ }
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Get global AWS configuration from environment variables.
37
+ * This provides the base layer of AWS credential configuration.
38
+ */
39
+ export function getGlobalAwsConfig(): AwsClientProps {
40
+ return {
41
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID
42
+ ? alchemy.secret(process.env.AWS_ACCESS_KEY_ID)
43
+ : undefined,
44
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
45
+ ? alchemy.secret(process.env.AWS_SECRET_ACCESS_KEY)
46
+ : undefined,
47
+ sessionToken: process.env.AWS_SESSION_TOKEN
48
+ ? alchemy.secret(process.env.AWS_SESSION_TOKEN)
49
+ : undefined,
50
+ region: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION,
51
+ profile: process.env.AWS_PROFILE,
52
+ roleArn: process.env.AWS_ROLE_ARN,
53
+ externalId: process.env.AWS_EXTERNAL_ID,
54
+ roleSessionName: process.env.AWS_ROLE_SESSION_NAME,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Resolve AWS credentials using three-tier resolution: global → scope → resource.
60
+ *
61
+ * This function implements a comprehensive credential resolution system that allows
62
+ * for flexible AWS credential management across different levels of your application.
63
+ * It enables multi-account and multi-region deployments by providing a consistent
64
+ * way to override credentials at different scopes.
65
+ *
66
+ * The resolution follows this precedence order:
67
+ * 1. Resource-level credentials (highest priority)
68
+ * 2. Scope-level credentials (medium priority)
69
+ * 3. Global environment variables (lowest priority)
70
+ *
71
+ * Supported credential properties include:
72
+ * - `region`: AWS region (e.g., 'us-west-2', 'eu-central-1')
73
+ * - `profile`: AWS profile name from credentials file
74
+ * - `accessKeyId`: AWS access key ID
75
+ * - `secretAccessKey`: AWS secret access key
76
+ * - `sessionToken`: AWS session token for temporary credentials
77
+ * - `roleArn`: AWS role ARN to assume
78
+ * - `roleSessionName`: Session name when assuming a role
79
+ * - `externalId`: External ID when assuming a role
80
+ *
81
+ * @param resourceProps - Resource-level AWS credential properties (optional)
82
+ * @returns Resolved AWS client properties with validation
83
+ *
84
+ * @throws {Error} When scope contains invalid AWS configuration
85
+ * @throws {Error} When resource properties contain invalid AWS configuration
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // Basic usage with resource-level credentials
90
+ * const credentials = resolveAwsCredentials({
91
+ * region: "us-west-2",
92
+ * profile: "production"
93
+ * });
94
+ * ```
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * // Usage with scope-level credentials
99
+ * await alchemy.run("my-app", {
100
+ * aws: {
101
+ * region: "eu-west-1",
102
+ * profile: "staging"
103
+ * }
104
+ * }, async () => {
105
+ * // Resources created here will use the scope credentials by default
106
+ * const vpc = await Vpc("main-vpc", {
107
+ * cidrBlock: "10.0.0.0/16"
108
+ * });
109
+ *
110
+ * // Resources can override scope credentials
111
+ * const crossRegionSubnet = await Subnet("cross-region-subnet", {
112
+ * vpc,
113
+ * cidrBlock: "10.0.1.0/24",
114
+ * region: "us-east-1" // Override scope region
115
+ * });
116
+ * });
117
+ * ```
118
+ */
119
+ export async function resolveAwsCredentials(
120
+ resourceProps?: AwsClientProps,
121
+ ): Promise<AwsClientProps> {
122
+ // 1. Start with global environment variables (lowest priority)
123
+ const globalConfig = getGlobalAwsConfig();
124
+
125
+ // 2. Layer in scope-level credentials (medium priority)
126
+ let scopeConfig: AwsClientProps = {};
127
+ try {
128
+ // Import Scope dynamically to avoid circular dependency
129
+ const { Scope } = await import("../scope.ts");
130
+ const currentScope = Scope.getScope();
131
+ if (currentScope?.providerCredentials?.aws) {
132
+ scopeConfig = currentScope.providerCredentials.aws;
133
+
134
+ // Validate scope-level credentials if provided
135
+ validateAwsClientProps(scopeConfig, "scope");
136
+ }
137
+ } catch (error) {
138
+ // If we can't access scope (e.g., not running in scope context), just continue
139
+ // with empty scope config unless it's a validation error
140
+ if (
141
+ error instanceof Error &&
142
+ error.message.includes("Invalid AWS configuration")
143
+ ) {
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ // 3. Layer in resource-level credentials (highest priority)
149
+ const resourceConfig = resourceProps || {};
150
+
151
+ // Validate resource-level credentials if provided
152
+ if (resourceProps && Object.keys(resourceProps).length > 0) {
153
+ validateAwsClientProps(resourceProps, "resource properties");
154
+ }
155
+
156
+ // Merge configurations with proper precedence (later properties override earlier ones)
157
+ const resolvedConfig = {
158
+ ...globalConfig,
159
+ ...scopeConfig,
160
+ ...resourceConfig,
161
+ };
162
+
163
+ // Filter out undefined values from the final result
164
+ return Object.fromEntries(
165
+ Object.entries(resolvedConfig).filter(([_, value]) => value !== undefined),
166
+ ) as AwsClientProps;
6
167
  }
@@ -8,6 +8,7 @@ import {
8
8
  type TimeoutConfig,
9
9
  waitForResourceState,
10
10
  } from "../../util/timeout.ts";
11
+ import type { AwsClientProps } from "../client-props.ts";
11
12
  import type { InternetGateway } from "./internet-gateway.ts";
12
13
  import { callEC2Api, createEC2Client } from "./utils.ts";
13
14
  import type { Vpc } from "./vpc.ts";
@@ -15,7 +16,7 @@ import type { Vpc } from "./vpc.ts";
15
16
  /**
16
17
  * Properties for creating or updating an Internet Gateway Attachment
17
18
  */
18
- export interface InternetGatewayAttachmentProps {
19
+ export interface InternetGatewayAttachmentProps extends AwsClientProps {
19
20
  /**
20
21
  * The Internet Gateway to attach
21
22
  */
@@ -67,6 +68,9 @@ export interface InternetGatewayAttachment
67
68
  * allowing traffic to flow between the VPC and the internet when combined with proper
68
69
  * routing configuration.
69
70
  *
71
+ * Supports AWS credential overrides at the resource level, allowing you to deploy Internet Gateway Attachments
72
+ * to different AWS accounts or regions than the default scope configuration.
73
+ *
70
74
  * @example
71
75
  * // Basic Internet Gateway attachment for internet connectivity
72
76
  * const vpc = await Vpc("main-vpc", {
@@ -81,6 +85,36 @@ export interface InternetGatewayAttachment
81
85
  * });
82
86
  *
83
87
  * @example
88
+ * // Internet Gateway Attachment with AWS credential overrides
89
+ * const crossAccountAttachment = await InternetGatewayAttachment("cross-account-attachment", {
90
+ * internetGateway: igw,
91
+ * vpc: vpc,
92
+ * // Override AWS credentials for this specific resource
93
+ * region: "us-east-1",
94
+ * profile: "production-account",
95
+ * });
96
+ *
97
+ * @example
98
+ * // Internet Gateway Attachment in different region with role assumption
99
+ * const multiRegionAttachment = await InternetGatewayAttachment("multi-region-attachment", {
100
+ * internetGateway: euIgw,
101
+ * vpc: euVpc,
102
+ * region: "eu-west-1",
103
+ * roleArn: "arn:aws:iam::123456789012:role/CrossRegionRole",
104
+ * roleSessionName: "igw-attachment-deployment",
105
+ * });
106
+ *
107
+ * @example
108
+ * // Internet Gateway Attachment with explicit credentials
109
+ * const explicitCredsAttachment = await InternetGatewayAttachment("explicit-creds-attachment", {
110
+ * internetGateway: testIgw,
111
+ * vpc: testVpc,
112
+ * accessKeyId: alchemy.secret("AKIAIOSFODNN7EXAMPLE"),
113
+ * secretAccessKey: alchemy.secret("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
114
+ * region: "us-west-2",
115
+ * });
116
+ *
117
+ * @example
84
118
  * // Internet Gateway attachment with custom timeout configuration
85
119
  * // for environments with slower AWS API responses
86
120
  * const attachment = await InternetGatewayAttachment("slow-env-attachment", {
@@ -109,7 +143,7 @@ export interface InternetGatewayAttachment
109
143
  *
110
144
  * // Create public subnet and route table for internet access
111
145
  * const publicSubnet = await Subnet("public-subnet", {
112
- * vpc,
146
+ * vpc: vpc,
113
147
  * cidrBlock: "10.0.1.0/24",
114
148
  * availabilityZone: "us-east-1a"
115
149
  * });
@@ -121,7 +155,27 @@ export interface InternetGatewayAttachment
121
155
  * const internetRoute = await Route("internet-route", {
122
156
  * routeTable: publicRouteTable,
123
157
  * destinationCidrBlock: "0.0.0.0/0",
124
- * gateway: igw
158
+ * target: { internetGateway: igw }
159
+ * });
160
+ *
161
+ * @example
162
+ * // Multi-account deployment with scope-level and resource-level overrides
163
+ * await alchemy.run("production", {
164
+ * aws: { region: "us-west-2", profile: "main-account" }
165
+ * }, async () => {
166
+ * // This Internet Gateway Attachment uses scope credentials (main-account, us-west-2)
167
+ * const mainAttachment = await InternetGatewayAttachment("main-attachment", {
168
+ * internetGateway: mainIgw,
169
+ * vpc: mainVpc
170
+ * });
171
+ *
172
+ * // This Internet Gateway Attachment overrides to use different account
173
+ * const crossAccountAttachment = await InternetGatewayAttachment("cross-account-attachment", {
174
+ * internetGateway: crossIgw,
175
+ * vpc: crossVpc,
176
+ * profile: "secondary-account",
177
+ * region: "us-east-1", // Also override region
178
+ * });
125
179
  * });
126
180
  */
127
181
  export const InternetGatewayAttachment = Resource(
@@ -131,7 +185,8 @@ export const InternetGatewayAttachment = Resource(
131
185
  _id: string,
132
186
  props: InternetGatewayAttachmentProps,
133
187
  ): Promise<InternetGatewayAttachment> {
134
- const client = await createEC2Client();
188
+ // Create EC2 client with credential resolution handled internally
189
+ const client = await createEC2Client(props);
135
190
  const timeoutConfig = mergeTimeoutConfig(
136
191
  INTERNET_GATEWAY_ATTACHMENT_TIMEOUT,
137
192
  props.timeout,