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
@@ -9,6 +9,7 @@ import {
9
9
  type TimeoutConfig,
10
10
  waitForResourceState,
11
11
  } from "../../util/timeout.ts";
12
+ import type { AwsClientProps } from "../client-props.ts";
12
13
  import { retry } from "../retry.ts";
13
14
  import type { InternetGateway } from "./internet-gateway.ts";
14
15
  import type { NatGateway } from "./nat-gateway.ts";
@@ -18,7 +19,7 @@ import { callEC2Api, createEC2Client } from "./utils.ts";
18
19
  /**
19
20
  * Properties for creating or updating a Route
20
21
  */
21
- export interface RouteProps {
22
+ export interface RouteProps extends AwsClientProps {
22
23
  /**
23
24
  * The route table to add the route to
24
25
  */
@@ -74,6 +75,9 @@ export interface Route extends Resource<"aws::Route">, RouteProps {
74
75
  * Creates and manages individual routes within route tables, directing traffic
75
76
  * to various targets like Internet Gateways, NAT Gateways, or instances.
76
77
  *
78
+ * Supports AWS credential overrides at the resource level, allowing you to deploy Routes
79
+ * to different AWS accounts or regions than the default scope configuration.
80
+ *
77
81
  * @example
78
82
  * // Create a default route to Internet Gateway
79
83
  * const publicRoute = await Route("public-default-route", {
@@ -91,6 +95,39 @@ export interface Route extends Resource<"aws::Route">, RouteProps {
91
95
  * });
92
96
  *
93
97
  * @example
98
+ * // Create Route with AWS credential overrides
99
+ * const crossAccountRoute = await Route("cross-account-route", {
100
+ * routeTable: mainRouteTable,
101
+ * destinationCidrBlock: "0.0.0.0/0",
102
+ * target: { internetGateway: mainIgw },
103
+ * // Override AWS credentials for this specific resource
104
+ * region: "us-east-1",
105
+ * profile: "production-account",
106
+ * });
107
+ *
108
+ * @example
109
+ * // Create Route in different region with role assumption
110
+ * const multiRegionRoute = await Route("multi-region-route", {
111
+ * routeTable: euRouteTable,
112
+ * destinationCidrBlock: "0.0.0.0/0",
113
+ * target: { internetGateway: euIgw },
114
+ * region: "eu-west-1",
115
+ * roleArn: "arn:aws:iam::123456789012:role/CrossRegionRole",
116
+ * roleSessionName: "route-deployment",
117
+ * });
118
+ *
119
+ * @example
120
+ * // Create Route with explicit credentials
121
+ * const explicitCredsRoute = await Route("explicit-creds-route", {
122
+ * routeTable: testRouteTable,
123
+ * destinationCidrBlock: "0.0.0.0/0",
124
+ * target: { internetGateway: testIgw },
125
+ * accessKeyId: alchemy.secret("AKIAIOSFODNN7EXAMPLE"),
126
+ * secretAccessKey: alchemy.secret("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
127
+ * region: "us-west-2",
128
+ * });
129
+ *
130
+ * @example
94
131
  * // Create a route to a specific instance
95
132
  * const instanceRoute = await Route("instance-route", {
96
133
  * routeTable: "rtb-12345678",
@@ -133,6 +170,28 @@ export interface Route extends Resource<"aws::Route">, RouteProps {
133
170
  * delayMs: 2000
134
171
  * }
135
172
  * });
173
+ *
174
+ * @example
175
+ * // Multi-account deployment with scope-level and resource-level overrides
176
+ * await alchemy.run("production", {
177
+ * aws: { region: "us-west-2", profile: "main-account" }
178
+ * }, async () => {
179
+ * // This Route uses scope credentials (main-account, us-west-2)
180
+ * const mainRoute = await Route("main-route", {
181
+ * routeTable: mainRouteTable,
182
+ * destinationCidrBlock: "0.0.0.0/0",
183
+ * target: { internetGateway: mainIgw }
184
+ * });
185
+ *
186
+ * // This Route overrides to use different account
187
+ * const crossAccountRoute = await Route("cross-account-route", {
188
+ * routeTable: crossRouteTable,
189
+ * destinationCidrBlock: "0.0.0.0/0",
190
+ * target: { internetGateway: crossIgw },
191
+ * profile: "secondary-account",
192
+ * region: "us-east-1", // Also override region
193
+ * });
194
+ * });
136
195
  */
137
196
  export const Route = Resource(
138
197
  "aws::Route",
@@ -141,7 +200,8 @@ export const Route = Resource(
141
200
  _id: string,
142
201
  props: RouteProps,
143
202
  ): Promise<Route> {
144
- const client = await createEC2Client();
203
+ // Create EC2 client with credential resolution handled internally
204
+ const client = await createEC2Client(props);
145
205
  const _timeoutConfig = mergeTimeoutConfig(ROUTE_TIMEOUT, props.timeout);
146
206
  const routeTableId =
147
207
  typeof props.routeTable === "string"
@@ -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 { callEC2Api, createEC2Client } from "./utils.ts";
12
13
  import type { Vpc } from "./vpc.ts";
13
14
 
@@ -22,7 +23,7 @@ export const SECURITY_GROUP_TIMEOUT: TimeoutConfig = {
22
23
  /**
23
24
  * Properties for creating or updating a Security Group
24
25
  */
25
- export interface SecurityGroupProps {
26
+ export interface SecurityGroupProps extends AwsClientProps {
26
27
  /**
27
28
  * The VPC to create the security group in.
28
29
  */
@@ -79,8 +80,12 @@ export interface SecurityGroup
79
80
  * instances and other AWS resources. Rules are managed separately using the
80
81
  * `SecurityGroupRule` resource.
81
82
  *
83
+ * Supports AWS credential overrides at the resource level, allowing you to deploy Security Groups
84
+ * to different AWS accounts or regions than the default scope configuration.
85
+ *
82
86
  * @example
83
- * // Create a web server security group
87
+ * ```typescript
88
+ * // Create a basic web server security group
84
89
  * const webSecurityGroup = await SecurityGroup("web-sg", {
85
90
  * vpc: mainVpc,
86
91
  * groupName: "web-server-sg",
@@ -90,11 +95,72 @@ export interface SecurityGroup
90
95
  * Environment: "production"
91
96
  * }
92
97
  * });
98
+ * ```
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // Create Security Group with AWS credential overrides
103
+ * const crossAccountSg = await SecurityGroup("cross-account-sg", {
104
+ * vpc: mainVpc,
105
+ * groupName: "cross-account-security-group",
106
+ * description: "Security group in different account",
107
+ * // Override AWS credentials for this specific resource
108
+ * region: "us-east-1",
109
+ * profile: "production-account",
110
+ * tags: {
111
+ * Name: "cross-account-sg",
112
+ * Environment: "production"
113
+ * }
114
+ * });
115
+ * ```
93
116
  *
94
117
  * @example
118
+ * ```typescript
119
+ * // Create Security Group in different region with role assumption
120
+ * const multiRegionSg = await SecurityGroup("multi-region-sg", {
121
+ * vpc: euVpc,
122
+ * groupName: "eu-security-group",
123
+ * description: "Security group in EU region",
124
+ * region: "eu-west-1",
125
+ * roleArn: "arn:aws:iam::123456789012:role/CrossRegionRole",
126
+ * roleSessionName: "security-group-deployment",
127
+ * tags: {
128
+ * Name: "eu-security-group",
129
+ * Region: "europe"
130
+ * }
131
+ * });
132
+ * ```
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * // Create Security Group with explicit credentials
137
+ * const explicitCredsSg = await SecurityGroup("explicit-creds-sg", {
138
+ * vpc: testVpc,
139
+ * groupName: "test-security-group",
140
+ * description: "Security group with explicit credentials",
141
+ * accessKeyId: alchemy.secret("AKIAIOSFODNN7EXAMPLE"),
142
+ * secretAccessKey: alchemy.secret("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"),
143
+ * region: "us-west-2",
144
+ * tags: {
145
+ * Name: "explicit-credentials-sg",
146
+ * Purpose: "testing"
147
+ * }
148
+ * });
149
+ * ```
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * // Create Security Group with rules (rules managed separately)
154
+ * const webSg = await SecurityGroup("web-sg", {
155
+ * vpc: mainVpc,
156
+ * groupName: "web-server-sg",
157
+ * description: "Security group for web servers",
158
+ * tags: { Name: "web-server-security-group" }
159
+ * });
160
+ *
95
161
  * // Separately, define a rule to allow HTTP traffic
96
162
  * const httpRule = await SecurityGroupRule("web-sg-http-rule", {
97
- * securityGroup: webSecurityGroup,
163
+ * securityGroup: webSg,
98
164
  * type: "ingress",
99
165
  * protocol: "tcp",
100
166
  * fromPort: 80,
@@ -102,6 +168,33 @@ export interface SecurityGroup
102
168
  * cidrBlocks: ["0.0.0.0/0"],
103
169
  * description: "Allow HTTP access from anywhere"
104
170
  * });
171
+ * ```
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // Multi-account deployment with scope-level and resource-level overrides
176
+ * await alchemy.run("production", {
177
+ * aws: { region: "us-west-2", profile: "main-account" }
178
+ * }, async () => {
179
+ * // This SG uses scope credentials (main-account, us-west-2)
180
+ * const mainSg = await SecurityGroup("main-sg", {
181
+ * vpc: mainVpc,
182
+ * groupName: "main-account-sg",
183
+ * description: "Security group in main account",
184
+ * tags: { Name: "main-account-sg" }
185
+ * });
186
+ *
187
+ * // This SG overrides to use different account
188
+ * const crossAccountSg = await SecurityGroup("cross-account-sg", {
189
+ * vpc: crossVpc,
190
+ * groupName: "secondary-account-sg",
191
+ * description: "Security group in secondary account",
192
+ * profile: "secondary-account",
193
+ * region: "us-east-1", // Also override region
194
+ * tags: { Name: "secondary-account-sg" }
195
+ * });
196
+ * });
197
+ * ```
105
198
  */
106
199
  export const SecurityGroup = Resource(
107
200
  "aws::SecurityGroup",
@@ -110,7 +203,8 @@ export const SecurityGroup = Resource(
110
203
  _id: string,
111
204
  props: SecurityGroupProps,
112
205
  ): Promise<SecurityGroup> {
113
- const client = await createEC2Client();
206
+ // Create EC2 client with credential resolution handled internally
207
+ const client = await createEC2Client(props);
114
208
  const vpcId = typeof props.vpc === "string" ? props.vpc : props.vpc.vpcId;
115
209
  const timeoutConfig = mergeTimeoutConfig(
116
210
  SECURITY_GROUP_TIMEOUT,
@@ -9,6 +9,7 @@ import {
9
9
  type TimeoutConfig,
10
10
  waitForResourceState,
11
11
  } from "../../util/timeout.ts";
12
+ import type { AwsClientProps } from "../client-props.ts";
12
13
  import { retry } from "../retry.ts";
13
14
  import { callEC2Api, createEC2Client } from "./utils.ts";
14
15
  import type { Vpc } from "./vpc.ts";
@@ -16,7 +17,7 @@ import type { Vpc } from "./vpc.ts";
16
17
  /**
17
18
  * Properties for creating or updating a Subnet
18
19
  */
19
- export interface SubnetProps {
20
+ export interface SubnetProps extends AwsClientProps {
20
21
  /**
21
22
  * The VPC to create the subnet in
22
23
  */
@@ -164,14 +165,15 @@ async function waitForSubnetDeleted(
164
165
  * AWS Subnet Resource
165
166
  *
166
167
  * Creates and manages subnets within a VPC. Subnets are used to segment
167
- * your VPC into smaller networks for organizing resources.
168
+ * your VPC into smaller networks for organizing resources. Supports AWS
169
+ * credential overrides for multi-account and cross-region deployments.
168
170
  *
169
171
  * @example
170
172
  * // Create a public subnet for web servers
171
173
  * const publicSubnet = await Subnet("public-subnet", {
172
174
  * vpc: myVpc,
173
175
  * cidrBlock: "10.0.1.0/24",
174
- * availabilityZone: process.env.AWS_REGION ? `${process.env.AWS_REGION}a` : "us-east-1a",
176
+ * availabilityZone: `${process.env.AWS_REGION}a` || "us-east-1a",
175
177
  * mapPublicIpOnLaunch: true,
176
178
  * tags: {
177
179
  * Name: "public-subnet",
@@ -203,6 +205,62 @@ async function waitForSubnetDeleted(
203
205
  * delayMs: 500
204
206
  * }
205
207
  * });
208
+ *
209
+ * @example
210
+ * // Create a subnet with AWS credential overrides for cross-account deployment
211
+ * const crossAccountSubnet = await Subnet("cross-account-subnet", {
212
+ * vpc: myVpc,
213
+ * cidrBlock: "10.1.1.0/24",
214
+ * availabilityZone: "us-east-1a",
215
+ * region: "us-east-1",
216
+ * profile: "production-account",
217
+ * mapPublicIpOnLaunch: true,
218
+ * tags: {
219
+ * Name: "cross-account-subnet",
220
+ * Account: "production"
221
+ * }
222
+ * });
223
+ *
224
+ * @example
225
+ * // Create a subnet in a different region with explicit credentials
226
+ * const multiRegionSubnet = await Subnet("multi-region-subnet", {
227
+ * vpc: "vpc-12345678", // VPC ID from different region
228
+ * cidrBlock: "10.2.1.0/24",
229
+ * availabilityZone: "eu-west-1a",
230
+ * region: "eu-west-1",
231
+ * accessKeyId: alchemy.secret("AKIA..."),
232
+ * secretAccessKey: alchemy.secret("..."),
233
+ * mapPublicIpOnLaunch: false,
234
+ * tags: {
235
+ * Name: "eu-subnet",
236
+ * Region: "europe"
237
+ * }
238
+ * });
239
+ *
240
+ * @example
241
+ * // Using scope-level credentials with resource-level overrides
242
+ * await alchemy.run("multi-account", {
243
+ * aws: {
244
+ * region: "us-west-2",
245
+ * profile: "staging"
246
+ * }
247
+ * }, async () => {
248
+ * // This subnet uses staging credentials from scope
249
+ * const stagingSubnet = await Subnet("staging-subnet", {
250
+ * vpc: stagingVpc,
251
+ * cidrBlock: "10.0.1.0/24",
252
+ * availabilityZone: "us-west-2a"
253
+ * });
254
+ *
255
+ * // This subnet overrides to use production credentials
256
+ * const prodSubnet = await Subnet("prod-subnet", {
257
+ * vpc: prodVpc,
258
+ * cidrBlock: "10.1.1.0/24",
259
+ * availabilityZone: "us-east-1a",
260
+ * profile: "production",
261
+ * region: "us-east-1"
262
+ * });
263
+ * });
206
264
  */
207
265
  export const Subnet = Resource(
208
266
  "aws::Subnet",
@@ -211,7 +269,8 @@ export const Subnet = Resource(
211
269
  _id: string,
212
270
  props: SubnetProps,
213
271
  ): Promise<Subnet> {
214
- const client = await createEC2Client();
272
+ // Create EC2 client with credential resolution handled internally
273
+ const client = await createEC2Client(props);
215
274
  const vpcId = typeof props.vpc === "string" ? props.vpc : props.vpc.vpcId;
216
275
 
217
276
  // Validate timeout config early, but use default for deletion if invalid
@@ -1,7 +1,11 @@
1
1
  import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
2
2
  import { AwsClient } from "aws4fetch";
3
+ import { alchemy } from "../../alchemy.ts";
4
+ import { Secret } from "../../secret.ts";
3
5
  import { logger } from "../../util/logger.ts";
4
6
  import { flattenParams } from "../../util/params.ts";
7
+ import type { AwsClientProps } from "../client-props.ts";
8
+ import { resolveAwsCredentials } from "../credentials.ts";
5
9
  import { getRegion } from "../utils.ts";
6
10
 
7
11
  /**
@@ -9,19 +13,81 @@ import { getRegion } from "../utils.ts";
9
13
  */
10
14
 
11
15
  /**
12
- * Create an AWS EC2 client
16
+ * Create an AWS EC2 client with credential resolution from props
17
+ *
18
+ * This function handles the complete credential resolution process internally,
19
+ * merging global, scope, and resource-level credentials according to the
20
+ * established precedence hierarchy.
21
+ *
22
+ * @param props - AWS client properties that may include credential overrides
23
+ * @returns Promise<AwsClient> - Configured AWS client for EC2 operations
13
24
  */
14
- export async function createEC2Client(): Promise<AwsClient> {
15
- const credentials = await fromNodeProviderChain()();
16
- const region = await getRegion();
25
+ export async function createEC2Client(
26
+ props?: AwsClientProps,
27
+ ): Promise<AwsClient> {
28
+ // Resolve credentials from all sources (global, scope, resource)
29
+ const credentials = await resolveAwsCredentials(props);
30
+ let awsCredentials: AwsClientProps;
31
+ let region: string;
32
+
33
+ if (credentials && (credentials.accessKeyId || credentials.secretAccessKey)) {
34
+ // Use provided credentials directly (unwrap secrets for AWS SDK)
35
+ awsCredentials = {
36
+ accessKeyId: credentials.accessKeyId,
37
+ secretAccessKey: credentials.secretAccessKey,
38
+ sessionToken: credentials.sessionToken,
39
+ };
40
+ } else if (credentials?.profile) {
41
+ // Use profile-based credentials if profile is specified
42
+ try {
43
+ const profileCredentials = await fromNodeProviderChain({
44
+ profile: credentials.profile,
45
+ })();
46
+ awsCredentials = {
47
+ accessKeyId: alchemy.secret(profileCredentials.accessKeyId),
48
+ secretAccessKey: alchemy.secret(profileCredentials.secretAccessKey),
49
+ sessionToken: alchemy.secret(profileCredentials.sessionToken),
50
+ };
51
+ } catch (error) {
52
+ logger.log(
53
+ `Error loading credentials from profile ${credentials.profile}:`,
54
+ error,
55
+ );
56
+ // Fall back to default credentials
57
+ awsCredentials = await loadAwsPropsFromCredentialChain();
58
+ }
59
+ } else {
60
+ // Fall back to AWS SDK credential chain
61
+ awsCredentials = await loadAwsPropsFromCredentialChain();
62
+ }
63
+
64
+ // Use region from resolved credentials or fall back to getRegion()
65
+ if (credentials?.region) {
66
+ region = credentials.region;
67
+ } else {
68
+ region = await getRegion();
69
+ }
17
70
 
18
71
  return new AwsClient({
19
- ...credentials,
72
+ ...awsCredentials,
73
+ accessKeyId: Secret.unwrap(awsCredentials.accessKeyId)!,
74
+ secretAccessKey: Secret.unwrap(awsCredentials.secretAccessKey)!,
75
+ sessionToken: Secret.unwrap(awsCredentials.sessionToken) || undefined,
20
76
  service: "ec2",
21
77
  region,
22
78
  });
23
79
  }
24
80
 
81
+ async function loadAwsPropsFromCredentialChain(): Promise<AwsClientProps> {
82
+ const defaultCredentialsRaw = await fromNodeProviderChain()();
83
+ return {
84
+ ...defaultCredentialsRaw,
85
+ accessKeyId: alchemy.secret(defaultCredentialsRaw.accessKeyId),
86
+ secretAccessKey: alchemy.secret(defaultCredentialsRaw.secretAccessKey),
87
+ sessionToken: alchemy.secret(defaultCredentialsRaw.sessionToken),
88
+ };
89
+ }
90
+
25
91
  /**
26
92
  * Make an EC2 API call with custom response parser
27
93
  */
@@ -34,8 +100,8 @@ export async function callEC2Api<T>(
34
100
  // Try the API call, and retry once with fresh credentials on auth failure
35
101
  for (let attempt = 1; attempt <= 2; attempt++) {
36
102
  try {
37
- const region = await getRegion();
38
- const url = `https://ec2.${region}.amazonaws.com/`;
103
+ // Use the client's region instead of the global region
104
+ const url = `https://ec2.${client.region}.amazonaws.com/`;
39
105
 
40
106
  const body = new URLSearchParams({
41
107
  Action: action,
@@ -9,12 +9,13 @@ import {
9
9
  type TimeoutConfig,
10
10
  waitForResourceState,
11
11
  } from "../../util/timeout.ts";
12
+ import type { AwsClientProps } from "../client-props.ts";
12
13
  import { callEC2Api, createEC2Client } from "./utils.ts";
13
14
 
14
15
  /**
15
16
  * Properties for creating or updating a VPC
16
17
  */
17
- export interface VpcProps {
18
+ export interface VpcProps extends AwsClientProps {
18
19
  /**
19
20
  * The IPv4 network range for the VPC, in CIDR notation
20
21
  * @example "10.0.0.0/16"
@@ -148,7 +149,8 @@ export interface Vpc extends Resource<"aws::Vpc">, VpcProps {
148
149
  * AWS VPC (Virtual Private Cloud) Resource
149
150
  *
150
151
  * Creates and manages Amazon VPC instances with configurable CIDR blocks,
151
- * DNS settings, and instance tenancy options.
152
+ * DNS settings, and instance tenancy options. Supports AWS credential overrides
153
+ * for multi-account and cross-region deployments.
152
154
  *
153
155
  * @example
154
156
  * // Create a basic VPC with default settings
@@ -188,6 +190,52 @@ export interface Vpc extends Resource<"aws::Vpc">, VpcProps {
188
190
  * Type: "isolated"
189
191
  * }
190
192
  * });
193
+ *
194
+ * @example
195
+ * // Create a VPC with AWS credential overrides for cross-account deployment
196
+ * const crossAccountVpc = await Vpc("cross-account-vpc", {
197
+ * cidrBlock: "10.1.0.0/16",
198
+ * region: "us-east-1",
199
+ * profile: "production-account",
200
+ * tags: {
201
+ * Name: "cross-account-vpc",
202
+ * Account: "production"
203
+ * }
204
+ * });
205
+ *
206
+ * @example
207
+ * // Create a VPC in a different region with explicit credentials
208
+ * const multiRegionVpc = await Vpc("multi-region-vpc", {
209
+ * cidrBlock: "10.2.0.0/16",
210
+ * region: "eu-west-1",
211
+ * accessKeyId: alchemy.secret("AKIA..."),
212
+ * secretAccessKey: alchemy.secret("..."),
213
+ * tags: {
214
+ * Name: "eu-vpc",
215
+ * Region: "europe"
216
+ * }
217
+ * });
218
+ *
219
+ * @example
220
+ * // Using scope-level credentials with resource-level overrides
221
+ * await alchemy.run("multi-account", {
222
+ * aws: {
223
+ * region: "us-west-2",
224
+ * profile: "staging"
225
+ * }
226
+ * }, async () => {
227
+ * // This VPC uses staging credentials from scope
228
+ * const stagingVpc = await Vpc("staging-vpc", {
229
+ * cidrBlock: "10.0.0.0/16"
230
+ * });
231
+ *
232
+ * // This VPC overrides to use production credentials
233
+ * const prodVpc = await Vpc("prod-vpc", {
234
+ * cidrBlock: "10.1.0.0/16",
235
+ * profile: "production",
236
+ * region: "us-east-1"
237
+ * });
238
+ * });
191
239
  */
192
240
  export const Vpc = Resource(
193
241
  "aws::Vpc",
@@ -196,7 +244,8 @@ export const Vpc = Resource(
196
244
  _id: string,
197
245
  props: VpcProps,
198
246
  ): Promise<Vpc> {
199
- const client = await createEC2Client();
247
+ // Create EC2 client with credential resolution handled internally
248
+ const client = await createEC2Client(props);
200
249
  const timeoutConfig = mergeTimeoutConfig(VPC_TIMEOUT, props.timeout);
201
250
 
202
251
  if (this.phase === "delete") {
package/src/aws/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from "./account-id.ts";
2
2
  export * from "./bucket.ts";
3
+ export * from "./client-props.ts";
4
+ export * from "./credentials.ts";
3
5
  export * from "./function.ts";
4
6
  export * from "./policy-attachment.ts";
5
7
  export * from "./policy.ts";
@@ -102,8 +102,8 @@ export async function extractCloudflareResult<T>(
102
102
  label: string,
103
103
  promise: Promise<Response>,
104
104
  ): Promise<T> {
105
- const response = await promise.catch(() => {
106
- throw new Error(`Failed to ${label}: Failed to fetch`);
105
+ const response = await promise.catch((cause) => {
106
+ throw new Error(`Failed to ${label}: Failed to fetch`, { cause });
107
107
  });
108
108
  const json = (await response.json().catch(() => {
109
109
  throw new Error(
@@ -63,6 +63,10 @@ const cloudflareApiCache: Record<string, CloudflareApi> = {};
63
63
  export async function createCloudflareApi(
64
64
  options: Partial<CloudflareApiOptions> | InternalCloudflareApiOptions = {},
65
65
  ): Promise<CloudflareApi> {
66
+ // TODO: Implement scope-level credential resolution similar to AWS
67
+ // This function should check for scope.providerCredentials.cloudflare
68
+ // and merge those credentials with the provided options, following
69
+ // the same three-tier resolution pattern: global → scope → resource
66
70
  const cacheKey = computeCacheKey(options);
67
71
  if (cloudflareApiCache[cacheKey]) {
68
72
  return cloudflareApiCache[cacheKey];
@@ -251,3 +255,22 @@ class TooManyRequestsError extends Error {
251
255
  }
252
256
 
253
257
  class ForbiddenError extends Error {}
258
+ /**
259
+ * Cloudflare scope extensions - adds Cloudflare credential support to scope options.
260
+ * This uses TypeScript module augmentation to extend the ProviderCredentials interface.
261
+ * Since ScopeOptions and RunOptions both extend ProviderCredentials,
262
+ * they automatically inherit these properties.
263
+ *
264
+ * NOTE: These scope credentials are not currently being used by createCloudflareApi.
265
+ * See TODO above in createCloudflareApi function for implementation needed.
266
+ */
267
+ declare module "../scope.ts" {
268
+ interface ProviderCredentials {
269
+ /**
270
+ * Cloudflare credentials configuration for this scope.
271
+ * All Cloudflare resources created within this scope will inherit these credentials
272
+ * unless overridden at the resource level.
273
+ */
274
+ cloudflare?: CloudflareApiOptions;
275
+ }
276
+ }
@@ -1,5 +1,5 @@
1
1
  import * as fs from "node:fs/promises";
2
- import * as path from "node:path";
2
+ import * as path from "pathe";
3
3
  import type { Context } from "../context.ts";
4
4
  import { Resource } from "../resource.ts";
5
5
  import { getContentType } from "../util/content-type.ts";
@@ -118,11 +118,10 @@ export const Assets = Resource(
118
118
  // Create asset file objects
119
119
  const files: AssetFile[] = filesList.map((filePath) => {
120
120
  const relativePath = path.relative(props.path, filePath);
121
- const normalizedPath = relativePath.split(path.sep).join("/"); // Ensure forward slashes for URLs
122
121
 
123
122
  return {
124
- path: normalizedPath,
125
- filePath,
123
+ path: path.normalize(relativePath),
124
+ filePath: path.normalize(filePath),
126
125
  contentType: getContentType(filePath) ?? "application/null",
127
126
  };
128
127
  });