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.
Files changed (143) hide show
  1. package/README.md +1 -2
  2. package/bin/alchemy.js +1406 -1464
  3. package/bin/commands/init.ts +29 -2
  4. package/bin/services/execute-alchemy.ts +1 -2
  5. package/lib/alchemy.d.ts +2 -2
  6. package/lib/alchemy.d.ts.map +1 -1
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/aws/client-props.d.ts +149 -0
  9. package/lib/aws/client-props.d.ts.map +1 -0
  10. package/lib/aws/client-props.js +1 -0
  11. package/lib/aws/client-props.js.map +1 -0
  12. package/lib/aws/credentials.d.ts +68 -5
  13. package/lib/aws/credentials.d.ts.map +1 -1
  14. package/lib/aws/credentials.js +148 -0
  15. package/lib/aws/credentials.js.map +1 -1
  16. package/lib/aws/ec2/internet-gateway-attachment.d.ts +57 -3
  17. package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -1
  18. package/lib/aws/ec2/internet-gateway-attachment.js +57 -3
  19. package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -1
  20. package/lib/aws/ec2/internet-gateway.d.ts +68 -40
  21. package/lib/aws/ec2/internet-gateway.d.ts.map +1 -1
  22. package/lib/aws/ec2/internet-gateway.js +68 -40
  23. package/lib/aws/ec2/internet-gateway.js.map +1 -1
  24. package/lib/aws/ec2/nat-gateway.d.ts +99 -18
  25. package/lib/aws/ec2/nat-gateway.d.ts.map +1 -1
  26. package/lib/aws/ec2/nat-gateway.js +113 -37
  27. package/lib/aws/ec2/nat-gateway.js.map +1 -1
  28. package/lib/aws/ec2/route-table-association.d.ts +1 -1
  29. package/lib/aws/ec2/route-table-association.js +1 -1
  30. package/lib/aws/ec2/route-table.d.ts +64 -1
  31. package/lib/aws/ec2/route-table.d.ts.map +1 -1
  32. package/lib/aws/ec2/route-table.js +64 -1
  33. package/lib/aws/ec2/route-table.js.map +1 -1
  34. package/lib/aws/ec2/route.d.ts +60 -1
  35. package/lib/aws/ec2/route.d.ts.map +1 -1
  36. package/lib/aws/ec2/route.js +60 -1
  37. package/lib/aws/ec2/route.js.map +1 -1
  38. package/lib/aws/ec2/security-group.d.ts +96 -3
  39. package/lib/aws/ec2/security-group.d.ts.map +1 -1
  40. package/lib/aws/ec2/security-group.js +96 -3
  41. package/lib/aws/ec2/security-group.js.map +1 -1
  42. package/lib/aws/ec2/subnet.d.ts +61 -3
  43. package/lib/aws/ec2/subnet.d.ts.map +1 -1
  44. package/lib/aws/ec2/subnet.js +61 -3
  45. package/lib/aws/ec2/subnet.js.map +1 -1
  46. package/lib/aws/ec2/utils.d.ts +10 -2
  47. package/lib/aws/ec2/utils.d.ts.map +1 -1
  48. package/lib/aws/ec2/utils.js +68 -7
  49. package/lib/aws/ec2/utils.js.map +1 -1
  50. package/lib/aws/ec2/vpc.d.ts +50 -2
  51. package/lib/aws/ec2/vpc.d.ts.map +1 -1
  52. package/lib/aws/ec2/vpc.js +50 -2
  53. package/lib/aws/ec2/vpc.js.map +1 -1
  54. package/lib/aws/index.d.ts +2 -0
  55. package/lib/aws/index.d.ts.map +1 -1
  56. package/lib/aws/index.js +2 -0
  57. package/lib/aws/index.js.map +1 -1
  58. package/lib/cloudflare/api-response.js +2 -2
  59. package/lib/cloudflare/api-response.js.map +1 -1
  60. package/lib/cloudflare/api.d.ts +19 -0
  61. package/lib/cloudflare/api.d.ts.map +1 -1
  62. package/lib/cloudflare/api.js +4 -0
  63. package/lib/cloudflare/api.js.map +1 -1
  64. package/lib/cloudflare/assets.d.ts.map +1 -1
  65. package/lib/cloudflare/assets.js +3 -4
  66. package/lib/cloudflare/assets.js.map +1 -1
  67. package/lib/cloudflare/bucket.d.ts +5 -3
  68. package/lib/cloudflare/bucket.d.ts.map +1 -1
  69. package/lib/cloudflare/bucket.js +22 -18
  70. package/lib/cloudflare/bucket.js.map +1 -1
  71. package/lib/cloudflare/bundle/plugin-compat-warning.js +7 -7
  72. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -1
  73. package/lib/cloudflare/cloudflare-env-proxy.js.map +1 -1
  74. package/lib/cloudflare/kv-namespace.d.ts +2 -2
  75. package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
  76. package/lib/cloudflare/kv-namespace.js.map +1 -1
  77. package/lib/cloudflare/oauth.js +3 -3
  78. package/lib/cloudflare/oauth.js.map +1 -1
  79. package/lib/cloudflare/quick-tunnel.d.ts +1 -1
  80. package/lib/cloudflare/website.d.ts.map +1 -1
  81. package/lib/cloudflare/website.js +26 -97
  82. package/lib/cloudflare/website.js.map +1 -1
  83. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  84. package/lib/cloudflare/worker-bundle.js +9 -8
  85. package/lib/cloudflare/worker-bundle.js.map +1 -1
  86. package/lib/cloudflare/worker-tail.d.ts.map +1 -1
  87. package/lib/cloudflare/worker-tail.js +6 -6
  88. package/lib/cloudflare/worker-tail.js.map +1 -1
  89. package/lib/resource.d.ts +1 -0
  90. package/lib/resource.d.ts.map +1 -1
  91. package/lib/resource.js +13 -2
  92. package/lib/resource.js.map +1 -1
  93. package/lib/scope.d.ts +24 -23
  94. package/lib/scope.d.ts.map +1 -1
  95. package/lib/scope.js +25 -20
  96. package/lib/scope.js.map +1 -1
  97. package/lib/state/cloudflare-state-store.d.ts.map +1 -1
  98. package/lib/state/cloudflare-state-store.js +2 -1
  99. package/lib/state/cloudflare-state-store.js.map +1 -1
  100. package/lib/util/idempotent-spawn.d.ts +10 -8
  101. package/lib/util/idempotent-spawn.d.ts.map +1 -1
  102. package/lib/util/idempotent-spawn.js +97 -78
  103. package/lib/util/idempotent-spawn.js.map +1 -1
  104. package/lib/util/index.d.ts +2 -0
  105. package/lib/util/index.d.ts.map +1 -0
  106. package/lib/util/index.js +2 -0
  107. package/lib/util/index.js.map +1 -0
  108. package/lib/util/safe-fetch.d.ts +24 -0
  109. package/lib/util/safe-fetch.d.ts.map +1 -1
  110. package/lib/util/safe-fetch.js +72 -0
  111. package/lib/util/safe-fetch.js.map +1 -1
  112. package/package.json +8 -5
  113. package/src/alchemy.ts +2 -2
  114. package/src/aws/client-props.ts +156 -0
  115. package/src/aws/credentials.ts +165 -4
  116. package/src/aws/ec2/internet-gateway-attachment.ts +59 -4
  117. package/src/aws/ec2/internet-gateway.ts +70 -41
  118. package/src/aws/ec2/nat-gateway.ts +123 -33
  119. package/src/aws/ec2/route-table-association.ts +1 -1
  120. package/src/aws/ec2/route-table.ts +66 -2
  121. package/src/aws/ec2/route.ts +62 -2
  122. package/src/aws/ec2/security-group.ts +98 -4
  123. package/src/aws/ec2/subnet.ts +63 -4
  124. package/src/aws/ec2/utils.ts +73 -7
  125. package/src/aws/ec2/vpc.ts +52 -3
  126. package/src/aws/index.ts +2 -0
  127. package/src/cloudflare/api-response.ts +2 -2
  128. package/src/cloudflare/api.ts +23 -0
  129. package/src/cloudflare/assets.ts +3 -4
  130. package/src/cloudflare/bucket.ts +27 -25
  131. package/src/cloudflare/bundle/plugin-compat-warning.ts +7 -7
  132. package/src/cloudflare/cloudflare-env-proxy.ts +2 -2
  133. package/src/cloudflare/kv-namespace.ts +26 -27
  134. package/src/cloudflare/oauth.ts +3 -3
  135. package/src/cloudflare/website.ts +30 -111
  136. package/src/cloudflare/worker-bundle.ts +9 -8
  137. package/src/cloudflare/worker-tail.ts +6 -8
  138. package/src/resource.ts +19 -2
  139. package/src/scope.ts +73 -43
  140. package/src/state/cloudflare-state-store.ts +2 -1
  141. package/src/util/idempotent-spawn.ts +117 -100
  142. package/src/util/index.ts +1 -0
  143. package/src/util/safe-fetch.ts +114 -0
@@ -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
  });
@@ -478,36 +478,40 @@ async function emptyBucket(
478
478
  bucketName: string,
479
479
  props: BucketProps,
480
480
  ) {
481
- let batch: Promise<unknown>[] = [];
482
- for await (const key of listObjects(api, bucketName, props)) {
483
- batch.push(
484
- extractCloudflareResult(
485
- `delete R2 object "${key}"`,
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/${key}`,
488
- { headers: withJurisdiction(props) },
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
- if (batch.length >= 10) {
493
- // this is an arbitary batch size, open to feedback
494
- await Promise.all(batch);
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
- * Returns an async iterable of all object keys in a bucket,
503
- * handling pagination automatically.
507
+ * Lists objects in a bucket.
504
508
  */
505
- export async function* listObjects(
509
+ export async function listObjects(
506
510
  api: CloudflareApi,
507
511
  bucketName: string,
508
512
  props: { jurisdiction?: string },
509
513
  cursor?: string,
510
- ): AsyncGenerator<string> {
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
- for (const object of json.result) {
543
- yield object.key;
544
- }
545
- if (json.result_info?.is_truncated && json.result_info.cursor) {
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) => kleur.yellow(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 ${kleur.red("nodejs_compat")} compatibility flag is not set. `,
63
- `Add ${kleur.blue("nodejs_compat")} to your compatibility flags and ensure compatibilityDate >= 2024-09-23. Imported from:`,
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 ${kleur.yellow("node:async_hooks")} but ${kleur.red("nodejs_als")} compatibility flag is not set. `,
76
- `Add ${kleur.blue("nodejs_als")} or ${kleur.blue("nodejs_compat")} to your compatibility flags. Imported from:`,
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) => `- ${kleur.yellow(path.relative(cwd, 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 interface KVNamespace
104
- extends Resource<"cloudflare::KVNamespace">,
105
- Omit<KVNamespaceProps, "delete" | "dev"> {
106
- type: "kv_namespace";
107
- /**
108
- * The ID of the namespace
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
- * Time at which the namespace was last modified
119
- */
120
- modifiedAt: number;
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
- * The ID of the KV namespace in development mode
117
+ * Time at which the namespace was last modified
129
118
  */
130
- id: string;
119
+ modifiedAt: number;
131
120
 
132
121
  /**
133
- * Whether the KV namespace is running remotely
122
+ * Development mode properties
123
+ * @internal
134
124
  */
135
- remote: boolean;
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.
@@ -1,4 +1,4 @@
1
- import kleur from "kleur";
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 envPaths from "env-paths";
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
- kleur.gray(
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,