alchemy 0.61.0 → 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.
- package/README.md +1 -2
- package/bin/alchemy.js +1406 -1464
- package/bin/commands/init.ts +29 -2
- package/bin/services/execute-alchemy.ts +1 -2
- package/lib/alchemy.d.ts +2 -2
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js.map +1 -1
- package/lib/aws/client-props.d.ts +149 -0
- package/lib/aws/client-props.d.ts.map +1 -0
- package/lib/aws/client-props.js +1 -0
- package/lib/aws/client-props.js.map +1 -0
- package/lib/aws/credentials.d.ts +68 -5
- package/lib/aws/credentials.d.ts.map +1 -1
- package/lib/aws/credentials.js +148 -0
- package/lib/aws/credentials.js.map +1 -1
- package/lib/aws/ec2/internet-gateway-attachment.d.ts +57 -3
- package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -1
- package/lib/aws/ec2/internet-gateway-attachment.js +57 -3
- package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -1
- package/lib/aws/ec2/internet-gateway.d.ts +68 -40
- package/lib/aws/ec2/internet-gateway.d.ts.map +1 -1
- package/lib/aws/ec2/internet-gateway.js +68 -40
- package/lib/aws/ec2/internet-gateway.js.map +1 -1
- package/lib/aws/ec2/nat-gateway.d.ts +99 -18
- package/lib/aws/ec2/nat-gateway.d.ts.map +1 -1
- package/lib/aws/ec2/nat-gateway.js +113 -37
- package/lib/aws/ec2/nat-gateway.js.map +1 -1
- package/lib/aws/ec2/route-table-association.d.ts +1 -1
- package/lib/aws/ec2/route-table-association.js +1 -1
- package/lib/aws/ec2/route-table.d.ts +64 -1
- package/lib/aws/ec2/route-table.d.ts.map +1 -1
- package/lib/aws/ec2/route-table.js +64 -1
- package/lib/aws/ec2/route-table.js.map +1 -1
- package/lib/aws/ec2/route.d.ts +60 -1
- package/lib/aws/ec2/route.d.ts.map +1 -1
- package/lib/aws/ec2/route.js +60 -1
- package/lib/aws/ec2/route.js.map +1 -1
- package/lib/aws/ec2/security-group.d.ts +96 -3
- package/lib/aws/ec2/security-group.d.ts.map +1 -1
- package/lib/aws/ec2/security-group.js +96 -3
- package/lib/aws/ec2/security-group.js.map +1 -1
- package/lib/aws/ec2/subnet.d.ts +61 -3
- package/lib/aws/ec2/subnet.d.ts.map +1 -1
- package/lib/aws/ec2/subnet.js +61 -3
- package/lib/aws/ec2/subnet.js.map +1 -1
- package/lib/aws/ec2/utils.d.ts +10 -2
- package/lib/aws/ec2/utils.d.ts.map +1 -1
- package/lib/aws/ec2/utils.js +68 -7
- package/lib/aws/ec2/utils.js.map +1 -1
- package/lib/aws/ec2/vpc.d.ts +50 -2
- package/lib/aws/ec2/vpc.d.ts.map +1 -1
- package/lib/aws/ec2/vpc.js +50 -2
- package/lib/aws/ec2/vpc.js.map +1 -1
- package/lib/aws/index.d.ts +2 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +2 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/cloudflare/api-response.js +2 -2
- package/lib/cloudflare/api-response.js.map +1 -1
- package/lib/cloudflare/api.d.ts +19 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +4 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/assets.d.ts.map +1 -1
- package/lib/cloudflare/assets.js +3 -4
- package/lib/cloudflare/assets.js.map +1 -1
- package/lib/cloudflare/bucket.d.ts +5 -3
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +22 -18
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-compat-warning.js +7 -7
- package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
- package/lib/cloudflare/kv-namespace.d.ts +2 -2
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/oauth.js +3 -3
- package/lib/cloudflare/oauth.js.map +1 -1
- package/lib/cloudflare/quick-tunnel.d.ts +1 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +26 -97
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/worker-bundle.js +9 -8
- package/lib/cloudflare/worker-bundle.js.map +1 -1
- package/lib/cloudflare/worker-tail.d.ts.map +1 -1
- package/lib/cloudflare/worker-tail.js +6 -6
- package/lib/cloudflare/worker-tail.js.map +1 -1
- package/lib/resource.d.ts +1 -0
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +13 -2
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +24 -23
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +25 -20
- package/lib/scope.js.map +1 -1
- package/lib/state/cloudflare-state-store.d.ts.map +1 -1
- package/lib/state/cloudflare-state-store.js +2 -1
- package/lib/state/cloudflare-state-store.js.map +1 -1
- package/lib/util/idempotent-spawn.d.ts +10 -8
- package/lib/util/idempotent-spawn.d.ts.map +1 -1
- package/lib/util/idempotent-spawn.js +97 -78
- package/lib/util/idempotent-spawn.js.map +1 -1
- package/lib/util/index.d.ts +2 -0
- package/lib/util/index.d.ts.map +1 -0
- package/lib/util/index.js +2 -0
- package/lib/util/index.js.map +1 -0
- package/lib/util/safe-fetch.d.ts +24 -0
- package/lib/util/safe-fetch.d.ts.map +1 -1
- package/lib/util/safe-fetch.js +72 -0
- package/lib/util/safe-fetch.js.map +1 -1
- package/package.json +8 -5
- package/src/alchemy.ts +2 -2
- package/src/aws/client-props.ts +156 -0
- package/src/aws/credentials.ts +165 -4
- package/src/aws/ec2/internet-gateway-attachment.ts +59 -4
- package/src/aws/ec2/internet-gateway.ts +70 -41
- package/src/aws/ec2/nat-gateway.ts +123 -33
- package/src/aws/ec2/route-table-association.ts +1 -1
- package/src/aws/ec2/route-table.ts +66 -2
- package/src/aws/ec2/route.ts +62 -2
- package/src/aws/ec2/security-group.ts +98 -4
- package/src/aws/ec2/subnet.ts +63 -4
- package/src/aws/ec2/utils.ts +73 -7
- package/src/aws/ec2/vpc.ts +52 -3
- package/src/aws/index.ts +2 -0
- package/src/cloudflare/api-response.ts +2 -2
- package/src/cloudflare/api.ts +23 -0
- package/src/cloudflare/assets.ts +3 -4
- package/src/cloudflare/bucket.ts +27 -25
- package/src/cloudflare/bundle/plugin-compat-warning.ts +7 -7
- package/src/cloudflare/cloudflare-env-proxy.ts +2 -2
- package/src/cloudflare/kv-namespace.ts +26 -27
- package/src/cloudflare/oauth.ts +3 -3
- package/src/cloudflare/website.ts +30 -111
- package/src/cloudflare/worker-bundle.ts +9 -8
- package/src/cloudflare/worker-tail.ts +6 -8
- package/src/resource.ts +19 -2
- package/src/scope.ts +73 -43
- package/src/state/cloudflare-state-store.ts +2 -1
- package/src/util/idempotent-spawn.ts +117 -100
- package/src/util/index.ts +1 -0
- package/src/util/safe-fetch.ts +114 -0
package/src/aws/ec2/subnet.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
package/src/aws/ec2/utils.ts
CHANGED
|
@@ -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(
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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,
|
package/src/aws/ec2/vpc.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
@@ -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(
|
package/src/cloudflare/api.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/cloudflare/assets.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as path from "
|
|
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:
|
|
125
|
-
filePath,
|
|
123
|
+
path: path.normalize(relativePath),
|
|
124
|
+
filePath: path.normalize(filePath),
|
|
126
125
|
contentType: getContentType(filePath) ?? "application/null",
|
|
127
126
|
};
|
|
128
127
|
});
|
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -478,36 +478,40 @@ async function emptyBucket(
|
|
|
478
478
|
bucketName: string,
|
|
479
479
|
props: BucketProps,
|
|
480
480
|
) {
|
|
481
|
-
let
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
481
|
+
let cursor: string | undefined;
|
|
482
|
+
while (true) {
|
|
483
|
+
const result = await listObjects(api, bucketName, props, cursor);
|
|
484
|
+
if (result.keys.length) {
|
|
485
|
+
// Another undocumented API! But it lets us delete multiple objects at once instead of one by one.
|
|
486
|
+
await extractCloudflareResult(
|
|
487
|
+
`delete ${result.keys.length} objects from bucket "${bucketName}"`,
|
|
486
488
|
api.delete(
|
|
487
|
-
`/accounts/${api.accountId}/r2/buckets/${bucketName}/objects
|
|
488
|
-
{
|
|
489
|
+
`/accounts/${api.accountId}/r2/buckets/${bucketName}/objects`,
|
|
490
|
+
{
|
|
491
|
+
headers: withJurisdiction(props),
|
|
492
|
+
method: "DELETE",
|
|
493
|
+
body: JSON.stringify(result.keys),
|
|
494
|
+
},
|
|
489
495
|
),
|
|
490
|
-
)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
batch = [];
|
|
496
|
+
);
|
|
497
|
+
if (result.cursor) {
|
|
498
|
+
cursor = result.cursor;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
496
501
|
}
|
|
502
|
+
break;
|
|
497
503
|
}
|
|
498
|
-
await Promise.all(batch);
|
|
499
504
|
}
|
|
500
505
|
|
|
501
506
|
/**
|
|
502
|
-
*
|
|
503
|
-
* handling pagination automatically.
|
|
507
|
+
* Lists objects in a bucket.
|
|
504
508
|
*/
|
|
505
|
-
export async function
|
|
509
|
+
export async function listObjects(
|
|
506
510
|
api: CloudflareApi,
|
|
507
511
|
bucketName: string,
|
|
508
512
|
props: { jurisdiction?: string },
|
|
509
513
|
cursor?: string,
|
|
510
|
-
)
|
|
514
|
+
) {
|
|
511
515
|
const params = new URLSearchParams({
|
|
512
516
|
per_page: "1000",
|
|
513
517
|
});
|
|
@@ -531,7 +535,7 @@ export async function* listObjects(
|
|
|
531
535
|
if (!json.success) {
|
|
532
536
|
// 10006 indicates that the bucket does not exist, so there are no objects to list
|
|
533
537
|
if (json.errors.some((e) => e.code === 10006)) {
|
|
534
|
-
return;
|
|
538
|
+
return { keys: [], cursor: undefined };
|
|
535
539
|
}
|
|
536
540
|
throw new CloudflareApiError(
|
|
537
541
|
`Failed to list objects in bucket "${bucketName}": ${json.errors.map((e) => `- [${e.code}] ${e.message}${e.documentation_url ? ` (${e.documentation_url})` : ""}`).join("\n")}`,
|
|
@@ -539,12 +543,10 @@ export async function* listObjects(
|
|
|
539
543
|
json.errors,
|
|
540
544
|
);
|
|
541
545
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
yield* listObjects(api, bucketName, props, json.result_info.cursor);
|
|
547
|
-
}
|
|
546
|
+
return {
|
|
547
|
+
keys: json.result.map((object) => object.key),
|
|
548
|
+
cursor: json.result_info?.cursor,
|
|
549
|
+
};
|
|
548
550
|
}
|
|
549
551
|
|
|
550
552
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Plugin, PluginBuild } from "esbuild";
|
|
2
|
-
import kleur from "kleur";
|
|
3
2
|
import module from "node:module";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import pc from "picocolors";
|
|
5
5
|
import { logger } from "../../util/logger.ts";
|
|
6
6
|
|
|
7
7
|
const NODE_REGEX = new RegExp(
|
|
@@ -54,13 +54,13 @@ export function esbuildPluginCompatWarning(mode: "als" | null): Plugin {
|
|
|
54
54
|
if (packages.size > 0) {
|
|
55
55
|
const formattedImports = Array.from(packages)
|
|
56
56
|
.sort()
|
|
57
|
-
.map((imp) =>
|
|
57
|
+
.map((imp) => pc.yellow(imp))
|
|
58
58
|
.join(", ");
|
|
59
59
|
|
|
60
60
|
logger.warn(
|
|
61
61
|
[
|
|
62
|
-
`Detected Node.js imports (${formattedImports}) but ${
|
|
63
|
-
`Add ${
|
|
62
|
+
`Detected Node.js imports (${formattedImports}) but ${pc.red("nodejs_compat")} compatibility flag is not set. `,
|
|
63
|
+
`Add ${pc.blue("nodejs_compat")} to your compatibility flags and ensure compatibilityDate >= 2024-09-23. Imported from:`,
|
|
64
64
|
formatImporters(
|
|
65
65
|
nodeImporters,
|
|
66
66
|
build.initialOptions.absWorkingDir ?? process.cwd(),
|
|
@@ -72,8 +72,8 @@ export function esbuildPluginCompatWarning(mode: "als" | null): Plugin {
|
|
|
72
72
|
if (alsImporters.size > 0 && !mode) {
|
|
73
73
|
logger.warn(
|
|
74
74
|
[
|
|
75
|
-
`Detected import of ${
|
|
76
|
-
`Add ${
|
|
75
|
+
`Detected import of ${pc.yellow("node:async_hooks")} but ${pc.red("nodejs_als")} compatibility flag is not set. `,
|
|
76
|
+
`Add ${pc.blue("nodejs_als")} or ${pc.blue("nodejs_compat")} to your compatibility flags. Imported from:`,
|
|
77
77
|
formatImporters(
|
|
78
78
|
alsImporters,
|
|
79
79
|
build.initialOptions.absWorkingDir ?? process.cwd(),
|
|
@@ -89,6 +89,6 @@ export function esbuildPluginCompatWarning(mode: "als" | null): Plugin {
|
|
|
89
89
|
function formatImporters(importers: Set<string>, cwd: string) {
|
|
90
90
|
return Array.from(importers)
|
|
91
91
|
.sort()
|
|
92
|
-
.map((imp) => `- ${
|
|
92
|
+
.map((imp) => `- ${pc.yellow(path.relative(cwd, imp))}`)
|
|
93
93
|
.join("\n");
|
|
94
94
|
}
|
|
@@ -41,10 +41,10 @@ export const getPlatformProxyOptions = (
|
|
|
41
41
|
...input,
|
|
42
42
|
configPath: validateConfigPath(input.configPath ?? DEFAULT_CONFIG_PATH),
|
|
43
43
|
persist,
|
|
44
|
-
experimental: input.experimental ?? {
|
|
44
|
+
experimental: (input as any).experimental ?? {
|
|
45
45
|
remoteBindings: true,
|
|
46
46
|
},
|
|
47
|
-
};
|
|
47
|
+
} as any;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
const warned = new Set<string>();
|
|
@@ -100,41 +100,40 @@ export function isKVNamespace(resource: Resource): resource is KVNamespace {
|
|
|
100
100
|
/**
|
|
101
101
|
* Output returned after KV Namespace creation/update
|
|
102
102
|
*/
|
|
103
|
-
export
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
namespaceId: string;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Time at which the namespace was created
|
|
114
|
-
*/
|
|
115
|
-
createdAt: number;
|
|
103
|
+
export type KVNamespace = Resource<"cloudflare::KVNamespace"> &
|
|
104
|
+
Omit<KVNamespaceProps, "delete" | "dev"> & {
|
|
105
|
+
type: "kv_namespace";
|
|
106
|
+
/**
|
|
107
|
+
* The ID of the namespace
|
|
108
|
+
*/
|
|
109
|
+
namespaceId: string;
|
|
116
110
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Time at which the namespace was created
|
|
113
|
+
*/
|
|
114
|
+
createdAt: number;
|
|
121
115
|
|
|
122
|
-
/**
|
|
123
|
-
* Development mode properties
|
|
124
|
-
* @internal
|
|
125
|
-
*/
|
|
126
|
-
dev: {
|
|
127
116
|
/**
|
|
128
|
-
*
|
|
117
|
+
* Time at which the namespace was last modified
|
|
129
118
|
*/
|
|
130
|
-
|
|
119
|
+
modifiedAt: number;
|
|
131
120
|
|
|
132
121
|
/**
|
|
133
|
-
*
|
|
122
|
+
* Development mode properties
|
|
123
|
+
* @internal
|
|
134
124
|
*/
|
|
135
|
-
|
|
125
|
+
dev: {
|
|
126
|
+
/**
|
|
127
|
+
* The ID of the KV namespace in development mode
|
|
128
|
+
*/
|
|
129
|
+
id: string;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether the KV namespace is running remotely
|
|
133
|
+
*/
|
|
134
|
+
remote: boolean;
|
|
135
|
+
};
|
|
136
136
|
};
|
|
137
|
-
}
|
|
138
137
|
|
|
139
138
|
/**
|
|
140
139
|
* A Cloudflare KV Namespace is a key-value store that can be used to store data for your application.
|
package/src/cloudflare/oauth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import envPaths from "env-paths";
|
|
2
2
|
import { err, ok, okAsync, ResultAsync } from "neverthrow";
|
|
3
3
|
import assert from "node:assert";
|
|
4
4
|
import crypto from "node:crypto";
|
|
@@ -6,7 +6,7 @@ import fs from "node:fs/promises";
|
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import open from "open";
|
|
9
|
-
import
|
|
9
|
+
import pc from "picocolors";
|
|
10
10
|
import { HTTPServer } from "../util/http.ts";
|
|
11
11
|
import { memoize } from "../util/memoize.ts";
|
|
12
12
|
import {
|
|
@@ -133,7 +133,7 @@ export const wranglerLogin = (
|
|
|
133
133
|
[
|
|
134
134
|
"Opening browser to authorize with Cloudflare...",
|
|
135
135
|
"",
|
|
136
|
-
|
|
136
|
+
pc.gray(
|
|
137
137
|
"If you are not automatically redirected, please open the following URL in your browser:",
|
|
138
138
|
),
|
|
139
139
|
challenge.url,
|