alchemy 0.53.0 → 0.55.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/bin/alchemy.js +196353 -0
- package/bin/alchemy.ts +2 -2
- package/bin/commands/init.ts +1190 -0
- package/bin/constants.ts +18 -0
- package/bin/services/execute-alchemy.ts +7 -2
- package/bin/services/package-manager.ts +1 -1
- package/bin/types.ts +14 -5
- package/lib/aws/ec2/index.d.ts +10 -0
- package/lib/aws/ec2/index.d.ts.map +1 -0
- package/lib/aws/ec2/index.js +10 -0
- package/lib/aws/ec2/index.js.map +1 -0
- package/lib/aws/ec2/internet-gateway-attachment.d.ts +150 -0
- package/lib/aws/ec2/internet-gateway-attachment.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway-attachment.js +248 -0
- package/lib/aws/ec2/internet-gateway-attachment.js.map +1 -0
- package/lib/aws/ec2/internet-gateway.d.ts +132 -0
- package/lib/aws/ec2/internet-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/internet-gateway.js +311 -0
- package/lib/aws/ec2/internet-gateway.js.map +1 -0
- package/lib/aws/ec2/nat-gateway.d.ts +145 -0
- package/lib/aws/ec2/nat-gateway.d.ts.map +1 -0
- package/lib/aws/ec2/nat-gateway.js +618 -0
- package/lib/aws/ec2/nat-gateway.js.map +1 -0
- package/lib/aws/ec2/route-table-association.d.ts +134 -0
- package/lib/aws/ec2/route-table-association.d.ts.map +1 -0
- package/lib/aws/ec2/route-table-association.js +364 -0
- package/lib/aws/ec2/route-table-association.js.map +1 -0
- package/lib/aws/ec2/route-table.d.ts +94 -0
- package/lib/aws/ec2/route-table.d.ts.map +1 -0
- package/lib/aws/ec2/route-table.js +253 -0
- package/lib/aws/ec2/route-table.js.map +1 -0
- package/lib/aws/ec2/route.d.ts +130 -0
- package/lib/aws/ec2/route.d.ts.map +1 -0
- package/lib/aws/ec2/route.js +253 -0
- package/lib/aws/ec2/route.js.map +1 -0
- package/lib/aws/ec2/security-group-rule.d.ts +84 -0
- package/lib/aws/ec2/security-group-rule.d.ts.map +1 -0
- package/lib/aws/ec2/security-group-rule.js +123 -0
- package/lib/aws/ec2/security-group-rule.js.map +1 -0
- package/lib/aws/ec2/security-group.d.ts +84 -0
- package/lib/aws/ec2/security-group.d.ts.map +1 -0
- package/lib/aws/ec2/security-group.js +207 -0
- package/lib/aws/ec2/security-group.js.map +1 -0
- package/lib/aws/ec2/subnet.d.ts +110 -0
- package/lib/aws/ec2/subnet.d.ts.map +1 -0
- package/lib/aws/ec2/subnet.js +336 -0
- package/lib/aws/ec2/subnet.js.map +1 -0
- package/lib/aws/ec2/utils.d.ts +15 -0
- package/lib/aws/ec2/utils.d.ts.map +1 -0
- package/lib/aws/ec2/utils.js +79 -0
- package/lib/aws/ec2/utils.js.map +1 -0
- package/lib/aws/ec2/vpc.d.ts +160 -0
- package/lib/aws/ec2/vpc.d.ts.map +1 -0
- package/lib/aws/ec2/vpc.js +453 -0
- package/lib/aws/ec2/vpc.js.map +1 -0
- package/lib/aws/index.d.ts +7 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +8 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/utils.d.ts +25 -0
- package/lib/aws/utils.d.ts.map +1 -0
- package/lib/aws/utils.js +89 -0
- package/lib/aws/utils.js.map +1 -0
- package/lib/build-date.d.ts +6 -0
- package/lib/build-date.d.ts.map +1 -0
- package/lib/build-date.js +8 -0
- package/lib/build-date.js.map +1 -0
- package/lib/cloudflare/astro.d.ts.map +1 -1
- package/lib/cloudflare/astro.js +4 -3
- package/lib/cloudflare/astro.js.map +1 -1
- package/lib/cloudflare/bundle/externals.d.ts +3 -0
- package/lib/cloudflare/bundle/externals.d.ts.map +1 -0
- package/lib/cloudflare/bundle/externals.js +35 -0
- package/lib/cloudflare/bundle/externals.js.map +1 -0
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +8 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/bundle/internal-worker-bundle.js +11 -4
- package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +4 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-hot-reload.js +27 -11
- package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +5 -1
- package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
- package/lib/cloudflare/bundle/plugin-wasm.d.ts +6 -2
- package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
- package/lib/cloudflare/bundle/plugin-wasm.js +21 -47
- package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
- package/lib/cloudflare/bundle/validate-node-compat.d.ts +2 -1
- package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -1
- package/lib/cloudflare/bundle/validate-node-compat.js +5 -4
- package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -1
- package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
- package/lib/cloudflare/compatibility-date.gen.js +1 -1
- package/lib/cloudflare/d1-database.js +1 -1
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
- package/lib/cloudflare/d1-migrations.js +8 -10
- package/lib/cloudflare/d1-migrations.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts +3 -3
- package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare-worker-options.js +23 -6
- package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/miniflare.js +2 -4
- package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +4 -6
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/react-router.d.ts +4 -0
- package/lib/cloudflare/react-router.d.ts.map +1 -1
- package/lib/cloudflare/react-router.js +8 -5
- package/lib/cloudflare/react-router.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts +80 -0
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -0
- package/lib/cloudflare/worker-bundle.js +266 -0
- package/lib/cloudflare/worker-bundle.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +81 -195
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +1 -0
- package/lib/esbuild/bundle.d.ts.map +1 -1
- package/lib/state/cloudflare-state-store.js +1 -5
- package/lib/state/cloudflare-state-store.js.map +1 -1
- package/lib/state/dofs-state-store/store.d.ts.map +1 -1
- package/lib/state/dofs-state-store/store.js +4 -4
- package/lib/state/dofs-state-store/store.js.map +1 -1
- package/lib/util/params.d.ts +5 -0
- package/lib/util/params.d.ts.map +1 -0
- package/lib/util/params.js +30 -0
- package/lib/util/params.js.map +1 -0
- package/lib/util/timeout.d.ts +31 -0
- package/lib/util/timeout.d.ts.map +1 -0
- package/lib/util/timeout.js +109 -0
- package/lib/util/timeout.js.map +1 -0
- package/lib/util/xml.d.ts +5 -0
- package/lib/util/xml.d.ts.map +1 -0
- package/lib/util/xml.js +8 -0
- package/lib/util/xml.js.map +1 -0
- package/package.json +12 -5
- package/src/aws/ec2/index.ts +9 -0
- package/src/aws/ec2/internet-gateway-attachment.ts +468 -0
- package/src/aws/ec2/internet-gateway.ts +557 -0
- package/src/aws/ec2/nat-gateway.ts +1123 -0
- package/src/aws/ec2/route-table-association.ts +747 -0
- package/src/aws/ec2/route-table.ts +452 -0
- package/src/aws/ec2/route.ts +447 -0
- package/src/aws/ec2/security-group-rule.ts +229 -0
- package/src/aws/ec2/security-group.ts +418 -0
- package/src/aws/ec2/subnet.ts +625 -0
- package/src/aws/ec2/utils.ts +102 -0
- package/src/aws/ec2/vpc.ts +969 -0
- package/src/aws/index.ts +9 -0
- package/src/aws/utils.ts +137 -0
- package/src/build-date.ts +8 -0
- package/src/cloudflare/astro.ts +4 -3
- package/src/cloudflare/bundle/externals.ts +36 -0
- package/src/cloudflare/bundle/internal-worker-bundle.ts +12 -3
- package/src/cloudflare/bundle/plugin-hot-reload.ts +32 -12
- package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +6 -1
- package/src/cloudflare/bundle/plugin-wasm.ts +26 -54
- package/src/cloudflare/bundle/validate-node-compat.ts +6 -5
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/d1-database.ts +1 -1
- package/src/cloudflare/d1-migrations.ts +19 -18
- package/src/cloudflare/miniflare/miniflare-worker-options.ts +27 -13
- package/src/cloudflare/miniflare/miniflare.ts +2 -4
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +5 -7
- package/src/cloudflare/react-router.ts +14 -6
- package/src/cloudflare/worker-bundle.ts +364 -0
- package/src/cloudflare/worker.ts +104 -241
- package/src/state/cloudflare-state-store.ts +1 -5
- package/src/state/dofs-state-store/store.ts +4 -4
- package/src/util/params.ts +33 -0
- package/src/util/timeout.ts +178 -0
- package/src/util/xml.ts +7 -0
- package/templates/nuxt/package.json +1 -1
- package/templates/nuxt/types/env.d.ts +11 -0
- package/templates/react-router/workers/app.ts +2 -2
- package/templates/rwsdk/.cursor/rules/alchemy_cloudflare.mdc +385 -0
- package/templates/rwsdk/wrangler.jsonc +10 -7
- package/templates/typescript/tsconfig.json +1 -2
- package/bin/alchemy.mjs +0 -51766
- package/lib/cloudflare/bundle/esbuild.d.ts +0 -16
- package/lib/cloudflare/bundle/esbuild.d.ts.map +0 -1
- package/lib/cloudflare/bundle/esbuild.js +0 -159
- package/lib/cloudflare/bundle/esbuild.js.map +0 -1
- package/lib/cloudflare/bundle/fs.d.ts +0 -18
- package/lib/cloudflare/bundle/fs.d.ts.map +0 -1
- package/lib/cloudflare/bundle/fs.js +0 -78
- package/lib/cloudflare/bundle/fs.js.map +0 -1
- package/lib/cloudflare/bundle/index.d.ts +0 -3
- package/lib/cloudflare/bundle/index.d.ts.map +0 -1
- package/lib/cloudflare/bundle/index.js +0 -2
- package/lib/cloudflare/bundle/index.js.map +0 -1
- package/lib/cloudflare/bundle/inline.d.ts +0 -12
- package/lib/cloudflare/bundle/inline.d.ts.map +0 -1
- package/lib/cloudflare/bundle/inline.js +0 -32
- package/lib/cloudflare/bundle/inline.js.map +0 -1
- package/lib/cloudflare/bundle/normalize.d.ts +0 -21
- package/lib/cloudflare/bundle/normalize.d.ts.map +0 -1
- package/lib/cloudflare/bundle/normalize.js +0 -49
- package/lib/cloudflare/bundle/normalize.js.map +0 -1
- package/lib/cloudflare/bundle/shared.d.ts +0 -30
- package/lib/cloudflare/bundle/shared.d.ts.map +0 -1
- package/lib/cloudflare/bundle/shared.js +0 -34
- package/lib/cloudflare/bundle/shared.js.map +0 -1
- package/src/cloudflare/bundle/esbuild.ts +0 -194
- package/src/cloudflare/bundle/fs.ts +0 -103
- package/src/cloudflare/bundle/index.ts +0 -6
- package/src/cloudflare/bundle/inline.ts +0 -42
- package/src/cloudflare/bundle/normalize.ts +0 -76
- package/src/cloudflare/bundle/shared.ts +0 -73
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import type { AwsClient } from "aws4fetch";
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
import type { Context } from "../../context.ts";
|
|
4
|
+
import { Resource } from "../../resource.ts";
|
|
5
|
+
import { ignore } from "../../util/ignore.ts";
|
|
6
|
+
import { logger } from "../../util/logger.ts";
|
|
7
|
+
import {
|
|
8
|
+
mergeTimeoutConfig,
|
|
9
|
+
type TimeoutConfig,
|
|
10
|
+
waitForResourceState,
|
|
11
|
+
} from "../../util/timeout.ts";
|
|
12
|
+
import { retry } from "../retry.ts";
|
|
13
|
+
import { callEC2Api, createEC2Client } from "./utils.ts";
|
|
14
|
+
import type { Vpc } from "./vpc.ts";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Properties for creating or updating a Subnet
|
|
18
|
+
*/
|
|
19
|
+
export interface SubnetProps {
|
|
20
|
+
/**
|
|
21
|
+
* The VPC to create the subnet in
|
|
22
|
+
*/
|
|
23
|
+
vpc: Vpc | string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The CIDR block for the subnet
|
|
27
|
+
*/
|
|
28
|
+
cidrBlock: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The availability zone for the subnet
|
|
32
|
+
*/
|
|
33
|
+
availabilityZone: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether instances launched in this subnet should be assigned a public IP address
|
|
37
|
+
* @default false
|
|
38
|
+
*/
|
|
39
|
+
mapPublicIpOnLaunch?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Tags to apply to the subnet
|
|
43
|
+
*/
|
|
44
|
+
tags?: Record<string, string>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Timeout configuration for subnet operations
|
|
48
|
+
* @default Subnet-specific sensible defaults (30 attempts, 1000ms delay)
|
|
49
|
+
*/
|
|
50
|
+
timeout?: Partial<TimeoutConfig>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Output returned after Subnet creation/update
|
|
55
|
+
*/
|
|
56
|
+
export interface Subnet extends Resource<"aws::Subnet">, SubnetProps {
|
|
57
|
+
/**
|
|
58
|
+
* The ID of the subnet
|
|
59
|
+
*/
|
|
60
|
+
subnetId: string;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The ID of the VPC the subnet belongs to
|
|
64
|
+
*/
|
|
65
|
+
vpcId: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The current state of the subnet
|
|
69
|
+
*/
|
|
70
|
+
state: "pending" | "available";
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The number of available IP addresses in the subnet
|
|
74
|
+
*/
|
|
75
|
+
availableIpAddressCount: number;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether this is the default subnet for the availability zone
|
|
79
|
+
*/
|
|
80
|
+
defaultForAz: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Wait for subnet to be in available state
|
|
85
|
+
*/
|
|
86
|
+
async function waitForSubnetAvailable(
|
|
87
|
+
client: AwsClient,
|
|
88
|
+
subnetId: string,
|
|
89
|
+
timeoutConfig: TimeoutConfig,
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
const checkFunction = async () => {
|
|
92
|
+
const response = await callEC2Api<DescribeSubnetsResponse>(
|
|
93
|
+
client,
|
|
94
|
+
"DescribeSubnets",
|
|
95
|
+
parseSubnetXmlResponse,
|
|
96
|
+
{
|
|
97
|
+
"SubnetId.1": subnetId,
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
return response.Subnets?.[0];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const isReady = (subnet: AwsSubnet | undefined) => {
|
|
104
|
+
return subnet?.State === "available";
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
await waitForResourceState(
|
|
108
|
+
checkFunction,
|
|
109
|
+
isReady,
|
|
110
|
+
timeoutConfig,
|
|
111
|
+
subnetId,
|
|
112
|
+
"Subnet",
|
|
113
|
+
"is now available",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Wait for Subnet to be deleted
|
|
119
|
+
*/
|
|
120
|
+
async function waitForSubnetDeleted(
|
|
121
|
+
client: AwsClient,
|
|
122
|
+
subnetId: string,
|
|
123
|
+
timeoutConfig: TimeoutConfig,
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const checkFunction = async () => {
|
|
126
|
+
try {
|
|
127
|
+
const response = await callEC2Api<DescribeSubnetsResponse>(
|
|
128
|
+
client,
|
|
129
|
+
"DescribeSubnets",
|
|
130
|
+
parseSubnetXmlResponse,
|
|
131
|
+
{
|
|
132
|
+
"SubnetId.1": subnetId,
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
return response.Subnets?.[0];
|
|
136
|
+
} catch (error: any) {
|
|
137
|
+
// If Subnet is not found, it's been deleted - return undefined
|
|
138
|
+
if (
|
|
139
|
+
error.code === "InvalidSubnetID.NotFound" ||
|
|
140
|
+
error.code === "InvalidSubnet.NotFound"
|
|
141
|
+
) {
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const isReady = (subnet: AwsSubnet | undefined) => {
|
|
149
|
+
// Subnet is deleted if it doesn't exist
|
|
150
|
+
return !subnet;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
await waitForResourceState(
|
|
154
|
+
checkFunction,
|
|
155
|
+
isReady,
|
|
156
|
+
timeoutConfig,
|
|
157
|
+
subnetId,
|
|
158
|
+
"Subnet",
|
|
159
|
+
"be deleted",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* AWS Subnet Resource
|
|
165
|
+
*
|
|
166
|
+
* Creates and manages subnets within a VPC. Subnets are used to segment
|
|
167
|
+
* your VPC into smaller networks for organizing resources.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Create a public subnet for web servers
|
|
171
|
+
* const publicSubnet = await Subnet("public-subnet", {
|
|
172
|
+
* vpc: myVpc,
|
|
173
|
+
* cidrBlock: "10.0.1.0/24",
|
|
174
|
+
* availabilityZone: process.env.AWS_REGION ? `${process.env.AWS_REGION}a` : "us-east-1a",
|
|
175
|
+
* mapPublicIpOnLaunch: true,
|
|
176
|
+
* tags: {
|
|
177
|
+
* Name: "public-subnet",
|
|
178
|
+
* Type: "public"
|
|
179
|
+
* }
|
|
180
|
+
* });
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // Create a private subnet for databases
|
|
184
|
+
* const privateSubnet = await Subnet("private-subnet", {
|
|
185
|
+
* vpc: myVpc,
|
|
186
|
+
* cidrBlock: "10.0.2.0/24",
|
|
187
|
+
* availabilityZone: "us-west-2b",
|
|
188
|
+
* mapPublicIpOnLaunch: false,
|
|
189
|
+
* tags: {
|
|
190
|
+
* Name: "private-subnet",
|
|
191
|
+
* Type: "private"
|
|
192
|
+
* }
|
|
193
|
+
* });
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* // Create a subnet with custom timeout configuration
|
|
197
|
+
* const customSubnet = await Subnet("custom-subnet", {
|
|
198
|
+
* vpc: myVpc,
|
|
199
|
+
* cidrBlock: "10.0.3.0/24",
|
|
200
|
+
* availabilityZone: "us-west-2c",
|
|
201
|
+
* timeout: {
|
|
202
|
+
* maxAttempts: 50,
|
|
203
|
+
* delayMs: 500
|
|
204
|
+
* }
|
|
205
|
+
* });
|
|
206
|
+
*/
|
|
207
|
+
export const Subnet = Resource(
|
|
208
|
+
"aws::Subnet",
|
|
209
|
+
async function (
|
|
210
|
+
this: Context<Subnet>,
|
|
211
|
+
_id: string,
|
|
212
|
+
props: SubnetProps,
|
|
213
|
+
): Promise<Subnet> {
|
|
214
|
+
const client = await createEC2Client();
|
|
215
|
+
const vpcId = typeof props.vpc === "string" ? props.vpc : props.vpc.vpcId;
|
|
216
|
+
|
|
217
|
+
// Validate timeout config early, but use default for deletion if invalid
|
|
218
|
+
let timeoutConfig: TimeoutConfig;
|
|
219
|
+
try {
|
|
220
|
+
timeoutConfig = mergeTimeoutConfig(SUBNET_TIMEOUT, props.timeout);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
if (this.phase === "delete") {
|
|
223
|
+
// Use default timeout for deletion to avoid blocking cleanup
|
|
224
|
+
timeoutConfig = SUBNET_TIMEOUT;
|
|
225
|
+
} else {
|
|
226
|
+
// Re-throw validation error during creation/update
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.phase === "delete") {
|
|
232
|
+
if (this.output?.subnetId) {
|
|
233
|
+
logger.log(`🗑️ Deleting Subnet: ${this.output.subnetId}`);
|
|
234
|
+
await retry(async () => {
|
|
235
|
+
await ignore("InvalidSubnetID.NotFound", async () => {
|
|
236
|
+
await callEC2Api(client, "DeleteSubnet", parseSubnetXmlResponse, {
|
|
237
|
+
SubnetId: this.output!.subnetId,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Wait for Subnet to be fully deleted to avoid dependency violations
|
|
243
|
+
logger.log(
|
|
244
|
+
` Waiting for Subnet ${this.output.subnetId} to be fully deleted...`,
|
|
245
|
+
);
|
|
246
|
+
await waitForSubnetDeleted(client, this.output.subnetId, timeoutConfig);
|
|
247
|
+
logger.log(` ✅ Subnet ${this.output.subnetId} deletion completed`);
|
|
248
|
+
}
|
|
249
|
+
return this.destroy();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let subnet: AwsSubnet;
|
|
253
|
+
|
|
254
|
+
if (this.phase === "update" && this.output?.subnetId) {
|
|
255
|
+
// Get existing subnet
|
|
256
|
+
const response = await callEC2Api<DescribeSubnetsResponse>(
|
|
257
|
+
client,
|
|
258
|
+
"DescribeSubnets",
|
|
259
|
+
parseSubnetXmlResponse,
|
|
260
|
+
{
|
|
261
|
+
"SubnetId.1": this.output.subnetId,
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
if (!response.Subnets?.[0]) {
|
|
266
|
+
throw new Error(`Subnet ${this.output.subnetId} not found`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
subnet = response.Subnets[0];
|
|
270
|
+
|
|
271
|
+
// Update public IP assignment if it has changed
|
|
272
|
+
if (props.mapPublicIpOnLaunch !== undefined) {
|
|
273
|
+
await callEC2Api(
|
|
274
|
+
client,
|
|
275
|
+
"ModifySubnetAttribute",
|
|
276
|
+
parseSubnetXmlResponse,
|
|
277
|
+
{
|
|
278
|
+
SubnetId: subnet.SubnetId,
|
|
279
|
+
"MapPublicIpOnLaunch.Value": props.mapPublicIpOnLaunch.toString(),
|
|
280
|
+
},
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update tags if provided
|
|
285
|
+
if (props.tags) {
|
|
286
|
+
const tagParams: Record<string, string> = {};
|
|
287
|
+
Object.entries(props.tags).forEach(([key, value], index) => {
|
|
288
|
+
tagParams[`Tag.${index + 1}.Key`] = key;
|
|
289
|
+
tagParams[`Tag.${index + 1}.Value`] = value;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await callEC2Api(client, "CreateTags", parseSubnetXmlResponse, {
|
|
293
|
+
"ResourceId.1": subnet.SubnetId,
|
|
294
|
+
...tagParams,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
// Create new subnet
|
|
299
|
+
const createSubnetParams: CreateSubnetParams = {
|
|
300
|
+
VpcId: vpcId,
|
|
301
|
+
CidrBlock: props.cidrBlock,
|
|
302
|
+
AvailabilityZone: props.availabilityZone,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Add tags if specified
|
|
306
|
+
if (props.tags) {
|
|
307
|
+
createSubnetParams.TagSpecifications = [
|
|
308
|
+
{
|
|
309
|
+
ResourceType: "subnet",
|
|
310
|
+
Tags: Object.entries(props.tags).map(([key, value]) => ({
|
|
311
|
+
Key: key,
|
|
312
|
+
Value: value,
|
|
313
|
+
})),
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const createParams =
|
|
319
|
+
convertCreateSubnetParamsToAwsFormat(createSubnetParams);
|
|
320
|
+
|
|
321
|
+
const response = await callEC2Api<CreateSubnetResponse>(
|
|
322
|
+
client,
|
|
323
|
+
"CreateSubnet",
|
|
324
|
+
parseSubnetXmlResponse,
|
|
325
|
+
createParams,
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
if (!response.Subnet) {
|
|
329
|
+
throw new Error("Failed to create subnet");
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
subnet = response.Subnet;
|
|
333
|
+
|
|
334
|
+
// Set public IP assignment if specified
|
|
335
|
+
if (props.mapPublicIpOnLaunch !== undefined) {
|
|
336
|
+
await callEC2Api(
|
|
337
|
+
client,
|
|
338
|
+
"ModifySubnetAttribute",
|
|
339
|
+
parseSubnetXmlResponse,
|
|
340
|
+
{
|
|
341
|
+
SubnetId: subnet.SubnetId,
|
|
342
|
+
"MapPublicIpOnLaunch.Value": props.mapPublicIpOnLaunch.toString(),
|
|
343
|
+
},
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Wait for subnet to be available
|
|
348
|
+
await waitForSubnetAvailable(client, subnet.SubnetId, timeoutConfig);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return this({
|
|
352
|
+
subnetId: subnet.SubnetId,
|
|
353
|
+
vpcId: subnet.VpcId,
|
|
354
|
+
state: subnet.State as "pending" | "available",
|
|
355
|
+
availableIpAddressCount: subnet.AvailableIpAddressCount,
|
|
356
|
+
defaultForAz: subnet.DefaultForAz,
|
|
357
|
+
...props,
|
|
358
|
+
vpc: vpcId,
|
|
359
|
+
});
|
|
360
|
+
},
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Parse XML responses specifically for Subnet operations
|
|
365
|
+
*/
|
|
366
|
+
function parseSubnetXmlResponse<
|
|
367
|
+
T extends
|
|
368
|
+
| CreateSubnetResponse
|
|
369
|
+
| DescribeSubnetsResponse
|
|
370
|
+
| { success: boolean },
|
|
371
|
+
>(xmlText: string): T {
|
|
372
|
+
const parser = new XMLParser({
|
|
373
|
+
ignoreAttributes: true,
|
|
374
|
+
parseAttributeValue: true,
|
|
375
|
+
parseTagValue: true,
|
|
376
|
+
trimValues: true,
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const parsed = parser.parse(xmlText);
|
|
380
|
+
const result: Record<string, unknown> = {};
|
|
381
|
+
|
|
382
|
+
// Parse CreateSubnetResponse
|
|
383
|
+
if (parsed.CreateSubnetResponse) {
|
|
384
|
+
const subnet = parsed.CreateSubnetResponse.subnet as SubnetXmlResult;
|
|
385
|
+
if (subnet) {
|
|
386
|
+
result.Subnet = {
|
|
387
|
+
SubnetId: subnet.subnetId,
|
|
388
|
+
VpcId: subnet.vpcId,
|
|
389
|
+
State: subnet.state,
|
|
390
|
+
CidrBlock: subnet.cidrBlock,
|
|
391
|
+
AvailabilityZone: subnet.availabilityZone,
|
|
392
|
+
AvailableIpAddressCount: subnet.availableIpAddressCount,
|
|
393
|
+
DefaultForAz: subnet.defaultForAz,
|
|
394
|
+
MapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Parse DescribeSubnetsResponse
|
|
400
|
+
if (parsed.DescribeSubnetsResponse) {
|
|
401
|
+
const subnetSet = parsed.DescribeSubnetsResponse.subnetSet;
|
|
402
|
+
if (subnetSet?.item) {
|
|
403
|
+
const subnets = Array.isArray(subnetSet.item)
|
|
404
|
+
? subnetSet.item
|
|
405
|
+
: [subnetSet.item];
|
|
406
|
+
result.Subnets = subnets.map((subnet: SubnetXmlResult) => ({
|
|
407
|
+
SubnetId: subnet.subnetId,
|
|
408
|
+
VpcId: subnet.vpcId,
|
|
409
|
+
State: subnet.state,
|
|
410
|
+
CidrBlock: subnet.cidrBlock,
|
|
411
|
+
AvailabilityZone: subnet.availabilityZone,
|
|
412
|
+
AvailableIpAddressCount: subnet.availableIpAddressCount,
|
|
413
|
+
DefaultForAz: subnet.defaultForAz,
|
|
414
|
+
MapPublicIpOnLaunch: subnet.mapPublicIpOnLaunch,
|
|
415
|
+
}));
|
|
416
|
+
} else {
|
|
417
|
+
result.Subnets = [];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Generic success responses
|
|
422
|
+
const successResponses = [
|
|
423
|
+
"ModifySubnetAttributeResponse",
|
|
424
|
+
"DeleteSubnetResponse",
|
|
425
|
+
];
|
|
426
|
+
|
|
427
|
+
for (const responseType of successResponses) {
|
|
428
|
+
if (parsed[responseType]) {
|
|
429
|
+
result.success = true;
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return result as T;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Subnet timeout constants
|
|
439
|
+
*/
|
|
440
|
+
export const SUBNET_TIMEOUT: TimeoutConfig = {
|
|
441
|
+
maxAttempts: 30,
|
|
442
|
+
delayMs: 1000, // 1 second - Subnets are quick
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Comprehensive AWS Subnet API Parameter Types
|
|
447
|
+
*/
|
|
448
|
+
|
|
449
|
+
// Common structures
|
|
450
|
+
interface TagSpecification {
|
|
451
|
+
ResourceType: string;
|
|
452
|
+
Tags: Array<{
|
|
453
|
+
Key: string;
|
|
454
|
+
Value: string;
|
|
455
|
+
}>;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Subnet API Request Parameters
|
|
459
|
+
interface CreateSubnetParams {
|
|
460
|
+
VpcId: string;
|
|
461
|
+
CidrBlock?: string;
|
|
462
|
+
Ipv6CidrBlock?: string;
|
|
463
|
+
AvailabilityZone?: string;
|
|
464
|
+
AvailabilityZoneId?: string;
|
|
465
|
+
TagSpecifications?: TagSpecification[];
|
|
466
|
+
OutpostArn?: string;
|
|
467
|
+
CustomerOwnedIpv4Pool?: string;
|
|
468
|
+
MapPublicIpOnLaunch?: boolean;
|
|
469
|
+
MapCustomerOwnedIpOnLaunch?: boolean;
|
|
470
|
+
Ipv4IpamPoolId?: string;
|
|
471
|
+
Ipv4NetmaskLength?: number;
|
|
472
|
+
Ipv6IpamPoolId?: string;
|
|
473
|
+
Ipv6NetmaskLength?: number;
|
|
474
|
+
DryRun?: boolean;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Subnet XML Response Types
|
|
478
|
+
interface SubnetXmlTag {
|
|
479
|
+
key: string;
|
|
480
|
+
value: string;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
interface SubnetXmlResult {
|
|
484
|
+
subnetId: string;
|
|
485
|
+
state: "pending" | "available" | "unavailable";
|
|
486
|
+
vpcId: string;
|
|
487
|
+
cidrBlock: string;
|
|
488
|
+
ipv6CidrBlockAssociationSet?: Array<{
|
|
489
|
+
associationId: string;
|
|
490
|
+
ipv6CidrBlock: string;
|
|
491
|
+
ipv6CidrBlockState: {
|
|
492
|
+
state:
|
|
493
|
+
| "associating"
|
|
494
|
+
| "associated"
|
|
495
|
+
| "disassociating"
|
|
496
|
+
| "disassociated"
|
|
497
|
+
| "failing"
|
|
498
|
+
| "failed";
|
|
499
|
+
statusMessage?: string;
|
|
500
|
+
};
|
|
501
|
+
}>;
|
|
502
|
+
availableIpAddressCount: number;
|
|
503
|
+
availabilityZone: string;
|
|
504
|
+
availabilityZoneId?: string;
|
|
505
|
+
defaultForAz: boolean;
|
|
506
|
+
mapPublicIpOnLaunch: boolean;
|
|
507
|
+
mapCustomerOwnedIpOnLaunch?: boolean;
|
|
508
|
+
customerOwnedIpv4Pool?: string;
|
|
509
|
+
tags?: SubnetXmlTag[];
|
|
510
|
+
assignIpv6AddressOnCreation: boolean;
|
|
511
|
+
privateDnsNameOptionsOnLaunch?: {
|
|
512
|
+
hostnameType: "ip-name" | "resource-name";
|
|
513
|
+
enableResourceNameDnsARecord: boolean;
|
|
514
|
+
enableResourceNameDnsAAAARecord: boolean;
|
|
515
|
+
};
|
|
516
|
+
enableDns64?: boolean;
|
|
517
|
+
ipv6Native?: boolean;
|
|
518
|
+
outpostArn?: string;
|
|
519
|
+
ownerId?: string;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Helper functions to convert typed parameters to AWS API format
|
|
524
|
+
*/
|
|
525
|
+
function convertCreateSubnetParamsToAwsFormat(
|
|
526
|
+
params: CreateSubnetParams,
|
|
527
|
+
): Record<string, string> {
|
|
528
|
+
const awsParams: Record<string, string> = {
|
|
529
|
+
VpcId: params.VpcId,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
if (params.CidrBlock) {
|
|
533
|
+
awsParams.CidrBlock = params.CidrBlock;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (params.Ipv6CidrBlock) {
|
|
537
|
+
awsParams.Ipv6CidrBlock = params.Ipv6CidrBlock;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (params.AvailabilityZone) {
|
|
541
|
+
awsParams.AvailabilityZone = params.AvailabilityZone;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (params.AvailabilityZoneId) {
|
|
545
|
+
awsParams.AvailabilityZoneId = params.AvailabilityZoneId;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (params.OutpostArn) {
|
|
549
|
+
awsParams.OutpostArn = params.OutpostArn;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (params.CustomerOwnedIpv4Pool) {
|
|
553
|
+
awsParams.CustomerOwnedIpv4Pool = params.CustomerOwnedIpv4Pool;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (params.MapPublicIpOnLaunch !== undefined) {
|
|
557
|
+
awsParams.MapPublicIpOnLaunch = params.MapPublicIpOnLaunch.toString();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (params.MapCustomerOwnedIpOnLaunch !== undefined) {
|
|
561
|
+
awsParams.MapCustomerOwnedIpOnLaunch =
|
|
562
|
+
params.MapCustomerOwnedIpOnLaunch.toString();
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (params.Ipv4IpamPoolId) {
|
|
566
|
+
awsParams.Ipv4IpamPoolId = params.Ipv4IpamPoolId;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (params.Ipv4NetmaskLength !== undefined) {
|
|
570
|
+
awsParams.Ipv4NetmaskLength = params.Ipv4NetmaskLength.toString();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (params.Ipv6IpamPoolId) {
|
|
574
|
+
awsParams.Ipv6IpamPoolId = params.Ipv6IpamPoolId;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (params.Ipv6NetmaskLength !== undefined) {
|
|
578
|
+
awsParams.Ipv6NetmaskLength = params.Ipv6NetmaskLength.toString();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (params.DryRun !== undefined) {
|
|
582
|
+
awsParams.DryRun = params.DryRun.toString();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Handle TagSpecifications
|
|
586
|
+
if (params.TagSpecifications) {
|
|
587
|
+
params.TagSpecifications.forEach((tagSpec, specIndex) => {
|
|
588
|
+
const specNum = specIndex + 1;
|
|
589
|
+
awsParams[`TagSpecification.${specNum}.ResourceType`] =
|
|
590
|
+
tagSpec.ResourceType;
|
|
591
|
+
|
|
592
|
+
tagSpec.Tags.forEach((tag, tagIndex) => {
|
|
593
|
+
const tagNum = tagIndex + 1;
|
|
594
|
+
awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Key`] = tag.Key;
|
|
595
|
+
awsParams[`TagSpecification.${specNum}.Tag.${tagNum}.Value`] =
|
|
596
|
+
tag.Value;
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
return awsParams;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* AWS Subnet API response types
|
|
606
|
+
*/
|
|
607
|
+
interface AwsSubnet {
|
|
608
|
+
SubnetId: string;
|
|
609
|
+
VpcId: string;
|
|
610
|
+
State: string;
|
|
611
|
+
CidrBlock: string;
|
|
612
|
+
AvailabilityZone: string;
|
|
613
|
+
AvailableIpAddressCount: number;
|
|
614
|
+
DefaultForAz: boolean;
|
|
615
|
+
MapPublicIpOnLaunch: boolean;
|
|
616
|
+
Tags?: Array<{ Key: string; Value: string }>;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
interface CreateSubnetResponse {
|
|
620
|
+
Subnet: AwsSubnet;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
interface DescribeSubnetsResponse {
|
|
624
|
+
Subnets: AwsSubnet[];
|
|
625
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";
|
|
2
|
+
import { AwsClient } from "aws4fetch";
|
|
3
|
+
import { logger } from "../../util/logger.ts";
|
|
4
|
+
import { flattenParams } from "../../util/params.ts";
|
|
5
|
+
import { getRegion } from "../utils.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared EC2 utilities for all EC2 resources
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create an AWS EC2 client
|
|
13
|
+
*/
|
|
14
|
+
export async function createEC2Client(): Promise<AwsClient> {
|
|
15
|
+
const credentials = await fromNodeProviderChain()();
|
|
16
|
+
const region = await getRegion();
|
|
17
|
+
|
|
18
|
+
return new AwsClient({
|
|
19
|
+
...credentials,
|
|
20
|
+
service: "ec2",
|
|
21
|
+
region,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Make an EC2 API call with custom response parser
|
|
27
|
+
*/
|
|
28
|
+
export async function callEC2Api<T>(
|
|
29
|
+
client: AwsClient,
|
|
30
|
+
action: string,
|
|
31
|
+
responseParser: (xmlText: string) => T,
|
|
32
|
+
params: Record<string, any> = {},
|
|
33
|
+
): Promise<T> {
|
|
34
|
+
// Try the API call, and retry once with fresh credentials on auth failure
|
|
35
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
36
|
+
try {
|
|
37
|
+
const region = await getRegion();
|
|
38
|
+
const url = `https://ec2.${region}.amazonaws.com/`;
|
|
39
|
+
|
|
40
|
+
const body = new URLSearchParams({
|
|
41
|
+
Action: action,
|
|
42
|
+
Version: "2016-11-15",
|
|
43
|
+
...flattenParams(params),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const signedRequest = await client.sign(url, {
|
|
47
|
+
method: "POST",
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
50
|
+
},
|
|
51
|
+
body: body.toString(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const response = await fetch(signedRequest);
|
|
55
|
+
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorText = await response.text();
|
|
58
|
+
|
|
59
|
+
// Parse AWS error code from XML response
|
|
60
|
+
const errorCodeMatch = errorText.match(/<Code>([^<]+)<\/Code>/);
|
|
61
|
+
const errorCode = errorCodeMatch ? errorCodeMatch[1] : null;
|
|
62
|
+
|
|
63
|
+
// If this is an auth failure and we haven't retried yet, create a fresh client and retry
|
|
64
|
+
if (errorCode === "AuthFailure" && attempt === 1) {
|
|
65
|
+
logger.log(
|
|
66
|
+
`🔄 Auth failure detected, refreshing credentials and retrying ${action}...`,
|
|
67
|
+
);
|
|
68
|
+
// Create a fresh client with new credentials
|
|
69
|
+
client = await createEC2Client();
|
|
70
|
+
continue; // Retry with fresh client
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const error = new Error(
|
|
74
|
+
`EC2 API error: ${response.status} ${response.statusText}\n${errorText}`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Set the error code for the ignore function
|
|
78
|
+
if (errorCode) {
|
|
79
|
+
(error as any).code = errorCode;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const xmlText = await response.text();
|
|
86
|
+
const parsed = responseParser(xmlText);
|
|
87
|
+
return parsed;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// If it's not an auth failure or we've already retried, throw the error
|
|
90
|
+
if (attempt === 2 || !(error as any)?.message?.includes("AuthFailure")) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
// Otherwise, continue to retry
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// This should never be reached, but TypeScript needs it
|
|
98
|
+
throw new Error("Unexpected error in API call retry logic");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Re-export getRegion for convenience
|
|
102
|
+
export { getRegion };
|