alchemy 0.5.2 → 0.7.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/lib/cloudflare/account-api-token.d.ts +6 -1
- package/lib/cloudflare/account-api-token.js +5 -1
- package/lib/cloudflare/account-id.d.ts +5 -1
- package/lib/cloudflare/account-id.js +5 -0
- package/lib/cloudflare/api-error.d.ts +32 -0
- package/lib/cloudflare/api-error.js +47 -0
- package/lib/cloudflare/api.d.ts +16 -49
- package/lib/cloudflare/api.js +37 -149
- package/lib/cloudflare/auth.d.ts +55 -38
- package/lib/cloudflare/auth.js +148 -77
- package/lib/cloudflare/bindings.d.ts +19 -7
- package/lib/cloudflare/bindings.js +0 -9
- package/lib/cloudflare/bound.d.ts +2 -1
- package/lib/cloudflare/bucket.d.ts +30 -5
- package/lib/cloudflare/bucket.js +12 -11
- package/lib/cloudflare/custom-domain.d.ts +2 -1
- package/lib/cloudflare/custom-domain.js +3 -2
- package/lib/cloudflare/dns-records.d.ts +2 -1
- package/lib/cloudflare/dns-records.js +2 -2
- package/lib/cloudflare/index.d.ts +1 -0
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/kv-namespace.d.ts +7 -1
- package/lib/cloudflare/kv-namespace.js +72 -71
- package/lib/cloudflare/permission-groups.d.ts +4 -0
- package/lib/cloudflare/permission-groups.js +5 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +3 -17
- package/lib/cloudflare/r2-rest-state-store.js +4 -12
- package/lib/cloudflare/worker.d.ts +2 -6
- package/lib/cloudflare/worker.js +58 -90
- package/lib/cloudflare/workflow.d.ts +46 -0
- package/lib/cloudflare/workflow.js +29 -0
- package/lib/cloudflare/zone.d.ts +2 -6
- package/lib/cloudflare/zone.js +1 -3
- package/lib/internal/docs/providers.js +1 -8
- package/package.json +5 -1
- package/src/cloudflare/account-api-token.ts +7 -3
- package/src/cloudflare/account-id.ts +12 -0
- package/src/cloudflare/api-error.ts +58 -0
- package/src/cloudflare/api.ts +50 -216
- package/src/cloudflare/auth.ts +262 -119
- package/src/cloudflare/bindings.ts +21 -16
- package/src/cloudflare/bound.ts +4 -1
- package/src/cloudflare/bucket.ts +52 -29
- package/src/cloudflare/custom-domain.ts +8 -3
- package/src/cloudflare/dns-records.ts +7 -3
- package/src/cloudflare/durable-object-namespace.ts +1 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/kv-namespace.ts +117 -105
- package/src/cloudflare/permission-groups.ts +5 -1
- package/src/cloudflare/r2-rest-state-store.ts +8 -31
- package/src/cloudflare/worker.ts +71 -141
- package/src/cloudflare/workflow.ts +79 -0
- package/src/cloudflare/zone.ts +17 -25
- package/src/internal/docs/providers.ts +1 -8
package/lib/cloudflare/zone.js
CHANGED
|
@@ -57,9 +57,7 @@ import { createCloudflareApi } from "./api";
|
|
|
57
57
|
*/
|
|
58
58
|
export const Zone = Resource("cloudflare::Zone", async function (id, props) {
|
|
59
59
|
// Create Cloudflare API client with automatic account discovery
|
|
60
|
-
const api = await createCloudflareApi(
|
|
61
|
-
accountId: props.accountId,
|
|
62
|
-
});
|
|
60
|
+
const api = await createCloudflareApi(props);
|
|
63
61
|
if (this.phase === "delete") {
|
|
64
62
|
if (this.output?.id) {
|
|
65
63
|
// Delete zone
|
|
@@ -21,13 +21,6 @@ export async function Providers({ srcDir, outDir, filter, parallel = true, }) {
|
|
|
21
21
|
}))
|
|
22
22
|
.filter((dirent) => dirent.isDirectory() && !exclude.includes(dirent.name))
|
|
23
23
|
.map((dirent) => path.join(dirent.parentPath, dirent.name));
|
|
24
|
-
// For each provider, list all files
|
|
25
|
-
if (filter === false) {
|
|
26
|
-
return [];
|
|
27
|
-
}
|
|
28
|
-
else if (typeof filter === "number") {
|
|
29
|
-
providers = providers.slice(0, filter);
|
|
30
|
-
}
|
|
31
24
|
if (parallel) {
|
|
32
25
|
return await Promise.all(providers.map((provider) => generateProviderDocs({ provider, outDir, parallel })));
|
|
33
26
|
}
|
|
@@ -77,7 +70,7 @@ async function generateProviderDocs({ provider, outDir, parallel, }) {
|
|
|
77
70
|
The ${provider} Service has the following resources:
|
|
78
71
|
${alchemy.files(files)}
|
|
79
72
|
|
|
80
|
-
A file is considered a "Resource" if it contains a const <ResourceName> = Resource(...) call or if it is a function that calls a Resource function, e.g. const TypeScriptFile = () => File(...).
|
|
73
|
+
A file is considered a "Resource" if it contains a const <ResourceName> = Resource(...) call or if it is a function that calls a Resource function, e.g. const TypeScriptFile = () => File(...), or it is the Workflow or DurableObject classes.
|
|
81
74
|
A file is considered a "Client" if it exposes a wrapper around creating a SDK client or fetch.
|
|
82
75
|
A file is considered a "Utility" if it contains utility functions that are not resources or clients.
|
|
83
76
|
A file is considered a "Types" if it contains just type definitions and maybe helpers around working with those types.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "./lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@aws-sdk/client-sesv2": "^3.0.0",
|
|
40
40
|
"@aws-sdk/client-sqs": "^3.0.0",
|
|
41
41
|
"@aws-sdk/client-sts": "^3.0.0",
|
|
42
|
+
"@iarna/toml": "^2.2.5",
|
|
42
43
|
"@octokit/rest": "^21.1.1",
|
|
43
44
|
"ai": "^4.0.0",
|
|
44
45
|
"arktype": "^2.0.0",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"prettier": "^3.0.0",
|
|
54
55
|
"stripe": "^17.0.0",
|
|
55
56
|
"turndown": "^7.0.0",
|
|
57
|
+
"xdg-app-paths": "^8.0.0",
|
|
56
58
|
"yaml": "^2.0.0",
|
|
57
59
|
"zod": "^3.0.0"
|
|
58
60
|
},
|
|
@@ -62,6 +64,7 @@
|
|
|
62
64
|
"@aws-sdk/client-s3": "3.726.1",
|
|
63
65
|
"@biomejs/biome": "^1.9.4",
|
|
64
66
|
"@cloudflare/workers-types": "^4.20250303.0",
|
|
67
|
+
"@iarna/toml": "^2.2.5",
|
|
65
68
|
"@octokit/rest": "^21.1.1",
|
|
66
69
|
"@types/bun": "latest",
|
|
67
70
|
"@types/diff": "^5.0.0",
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
"vite": "^6.0.7",
|
|
83
86
|
"vitepress": "^1.6.3",
|
|
84
87
|
"wrangler": "^3.114.0",
|
|
88
|
+
"xdg-app-paths": "^8.3.0",
|
|
85
89
|
"yaml": "^2.7.1"
|
|
86
90
|
}
|
|
87
91
|
}
|
|
@@ -3,7 +3,7 @@ import type { Context } from "../context";
|
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
4
|
import { Secret } from "../secret";
|
|
5
5
|
import { sha256 } from "../util/sha256";
|
|
6
|
-
import { createCloudflareApi } from "./api";
|
|
6
|
+
import { createCloudflareApi, type CloudflareApiOptions } from "./api";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Permission group for a token policy
|
|
@@ -63,7 +63,7 @@ export interface TokenCondition {
|
|
|
63
63
|
/**
|
|
64
64
|
* Properties for creating or updating an Account API Token
|
|
65
65
|
*/
|
|
66
|
-
export interface AccountApiTokenProps {
|
|
66
|
+
export interface AccountApiTokenProps extends CloudflareApiOptions {
|
|
67
67
|
/**
|
|
68
68
|
* Name of the token
|
|
69
69
|
*/
|
|
@@ -162,6 +162,10 @@ export interface AccountApiToken
|
|
|
162
162
|
/**
|
|
163
163
|
* Creates a Cloudflare Account API Token with specified permissions.
|
|
164
164
|
*
|
|
165
|
+
* Note: Requires a Cloudflare API Key or Token with admin-level account access.
|
|
166
|
+
* The OAuth token from `wrangler login` is NOT sufficient for this operation.
|
|
167
|
+
* You must use an API token with permission to manage account API tokens.
|
|
168
|
+
*
|
|
165
169
|
* @see https://developers.cloudflare.com/api/resources/accounts/subresources/tokens/methods/create/
|
|
166
170
|
*
|
|
167
171
|
* @example
|
|
@@ -222,7 +226,7 @@ export const AccountApiToken = Resource(
|
|
|
222
226
|
props: AccountApiTokenProps
|
|
223
227
|
): Promise<AccountApiToken> {
|
|
224
228
|
// Create Cloudflare API client with automatic account discovery
|
|
225
|
-
const api = await createCloudflareApi();
|
|
229
|
+
const api = await createCloudflareApi(props);
|
|
226
230
|
|
|
227
231
|
if (this.phase === "delete") {
|
|
228
232
|
// Delete token if we have an ID
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getCloudflareUserInfo, type CloudflareAuthOptions } from "./auth";
|
|
2
|
+
|
|
3
|
+
export type CloudflareAccountId = string & {
|
|
4
|
+
readonly __brand: "CloudflareAccountId";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export async function CloudflareAccountId(
|
|
8
|
+
options: CloudflareAuthOptions
|
|
9
|
+
): Promise<CloudflareAccountId> {
|
|
10
|
+
const userInfo = await getCloudflareUserInfo(options);
|
|
11
|
+
return userInfo.accounts[0].id as CloudflareAccountId;
|
|
12
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for Cloudflare API errors
|
|
3
|
+
* Includes HTTP status information from the Response
|
|
4
|
+
*/
|
|
5
|
+
export class CloudflareApiError extends Error {
|
|
6
|
+
/**
|
|
7
|
+
* HTTP status code
|
|
8
|
+
*/
|
|
9
|
+
status: number;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* HTTP status text
|
|
13
|
+
*/
|
|
14
|
+
statusText: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Raw error data from the API
|
|
18
|
+
*/
|
|
19
|
+
errorData?: any;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create a new CloudflareApiError
|
|
23
|
+
*/
|
|
24
|
+
constructor(message: string, response: Response, errorData?: any) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "CloudflareApiError";
|
|
27
|
+
this.status = response.status;
|
|
28
|
+
this.statusText = response.statusText;
|
|
29
|
+
this.errorData = errorData;
|
|
30
|
+
|
|
31
|
+
// Ensure instanceof works correctly
|
|
32
|
+
Object.setPrototypeOf(this, CloudflareApiError.prototype);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Helper function to handle API errors
|
|
38
|
+
*
|
|
39
|
+
* @param response The fetch Response object
|
|
40
|
+
* @param action The action being performed (e.g., "creating", "deleting")
|
|
41
|
+
* @param resourceType The type of resource being acted upon (e.g., "R2 bucket", "Worker")
|
|
42
|
+
* @param resourceName The name/identifier of the specific resource
|
|
43
|
+
* @returns Never returns - always throws an error
|
|
44
|
+
*/
|
|
45
|
+
export async function handleApiError(
|
|
46
|
+
response: Response,
|
|
47
|
+
action: string,
|
|
48
|
+
resourceType: string,
|
|
49
|
+
resourceName: string
|
|
50
|
+
): Promise<never> {
|
|
51
|
+
const json: any = await response.json();
|
|
52
|
+
const errors: { message: string }[] = json.errors || [
|
|
53
|
+
{ message: response.statusText },
|
|
54
|
+
];
|
|
55
|
+
const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errors[0]?.message || response.statusText}`;
|
|
56
|
+
|
|
57
|
+
throw new CloudflareApiError(errorMessage, response, errors);
|
|
58
|
+
}
|
package/src/cloudflare/api.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import type { Secret } from "../secret";
|
|
2
2
|
import { withExponentialBackoff } from "../util/retry";
|
|
3
|
-
import {
|
|
3
|
+
import { getCloudflareAuthHeaders, getCloudflareUserInfo } from "./auth";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Options for Cloudflare API requests
|
|
7
7
|
*/
|
|
8
8
|
export interface CloudflareApiOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Base URL for Cloudflare API
|
|
11
|
+
*
|
|
12
|
+
* @default https://api.cloudflare.com/client/v4
|
|
13
|
+
*/
|
|
14
|
+
baseUrl?: string;
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* API Key to use (overrides CLOUDFLARE_API_KEY env var)
|
|
11
18
|
*/
|
|
12
19
|
apiKey?: Secret;
|
|
13
20
|
|
|
21
|
+
/**
|
|
22
|
+
* API Token to use (overrides CLOUDFLARE_API_TOKEN env var)
|
|
23
|
+
*/
|
|
24
|
+
apiToken?: Secret;
|
|
25
|
+
|
|
14
26
|
/**
|
|
15
27
|
* Account ID to use (overrides CLOUDFLARE_ACCOUNT_ID env var)
|
|
16
28
|
* If not provided, will be automatically retrieved from the Cloudflare API
|
|
@@ -29,73 +41,6 @@ export interface CloudflareApiOptions {
|
|
|
29
41
|
email?: string;
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
/**
|
|
33
|
-
* Account information returned from Cloudflare API
|
|
34
|
-
*/
|
|
35
|
-
interface CloudflareAccount {
|
|
36
|
-
id: string;
|
|
37
|
-
name: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Custom error class for Cloudflare API errors
|
|
42
|
-
* Includes HTTP status information from the Response
|
|
43
|
-
*/
|
|
44
|
-
export class CloudflareApiError extends Error {
|
|
45
|
-
/**
|
|
46
|
-
* HTTP status code
|
|
47
|
-
*/
|
|
48
|
-
status: number;
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* HTTP status text
|
|
52
|
-
*/
|
|
53
|
-
statusText: string;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Raw error data from the API
|
|
57
|
-
*/
|
|
58
|
-
errorData?: any;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Create a new CloudflareApiError
|
|
62
|
-
*/
|
|
63
|
-
constructor(message: string, response: Response, errorData?: any) {
|
|
64
|
-
super(message);
|
|
65
|
-
this.name = "CloudflareApiError";
|
|
66
|
-
this.status = response.status;
|
|
67
|
-
this.statusText = response.statusText;
|
|
68
|
-
this.errorData = errorData;
|
|
69
|
-
|
|
70
|
-
// Ensure instanceof works correctly
|
|
71
|
-
Object.setPrototypeOf(this, CloudflareApiError.prototype);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Helper function to handle API errors
|
|
77
|
-
*
|
|
78
|
-
* @param response The fetch Response object
|
|
79
|
-
* @param action The action being performed (e.g., "creating", "deleting")
|
|
80
|
-
* @param resourceType The type of resource being acted upon (e.g., "R2 bucket", "Worker")
|
|
81
|
-
* @param resourceName The name/identifier of the specific resource
|
|
82
|
-
* @returns Never returns - always throws an error
|
|
83
|
-
*/
|
|
84
|
-
export async function handleApiError(
|
|
85
|
-
response: Response,
|
|
86
|
-
action: string,
|
|
87
|
-
resourceType: string,
|
|
88
|
-
resourceName: string
|
|
89
|
-
): Promise<never> {
|
|
90
|
-
const json: any = await response.json();
|
|
91
|
-
console.log(json);
|
|
92
|
-
const errorData: any = json.errors || [{ message: response.statusText }];
|
|
93
|
-
|
|
94
|
-
const errorMessage = `Error ${action} ${resourceType} '${resourceName}': ${errorData.errors?.[0]?.message || response.statusText}`;
|
|
95
|
-
|
|
96
|
-
throw new CloudflareApiError(errorMessage, response, errorData);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
44
|
/**
|
|
100
45
|
* Creates a CloudflareApi instance with automatic account ID discovery if not provided
|
|
101
46
|
*
|
|
@@ -103,55 +48,25 @@ export async function handleApiError(
|
|
|
103
48
|
* @returns Promise resolving to a CloudflareApi instance
|
|
104
49
|
*/
|
|
105
50
|
export async function createCloudflareApi(
|
|
106
|
-
options: CloudflareApiOptions = {}
|
|
51
|
+
options: Partial<CloudflareApiOptions> = {}
|
|
107
52
|
): Promise<CloudflareApi> {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export type CloudflareAccountId = string & {
|
|
122
|
-
readonly __brand: "CloudflareAccountId";
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
export async function CloudflareAccountId(
|
|
126
|
-
options: CloudflareApiOptions = {}
|
|
127
|
-
): Promise<CloudflareAccountId> {
|
|
128
|
-
return (options.accountId ||
|
|
129
|
-
process.env.CLOUDFLARE_ACCOUNT_ID ||
|
|
130
|
-
(await fetchAccountId())) as CloudflareAccountId;
|
|
53
|
+
const userInfo = await getCloudflareUserInfo(options);
|
|
54
|
+
return new CloudflareApi({
|
|
55
|
+
baseUrl: options.baseUrl,
|
|
56
|
+
accountId: options.accountId ?? userInfo.accounts[0].id!,
|
|
57
|
+
email: userInfo.email!,
|
|
58
|
+
apiKey: userInfo.apiKey,
|
|
59
|
+
apiToken: userInfo.apiToken,
|
|
60
|
+
zoneId: options.zoneId,
|
|
61
|
+
});
|
|
131
62
|
}
|
|
132
63
|
|
|
133
64
|
/**
|
|
134
65
|
* Cloudflare API client using raw fetch
|
|
135
66
|
*/
|
|
136
67
|
export class CloudflareApi {
|
|
137
|
-
|
|
138
|
-
readonly baseUrl: string;
|
|
139
|
-
|
|
140
|
-
/** Cloudflare API Key */
|
|
141
|
-
readonly apiKey: string;
|
|
142
|
-
|
|
143
|
-
/** Cloudflare account ID */
|
|
144
|
-
readonly accountId: string;
|
|
145
|
-
|
|
146
|
-
/** Cloudflare zone ID (if provided) */
|
|
147
|
-
readonly zoneId: string | null;
|
|
148
|
-
|
|
149
|
-
/** User email (when using API Key auth) */
|
|
150
|
-
readonly email?: string;
|
|
151
|
-
|
|
152
|
-
/** Auth options for making requests */
|
|
153
|
-
private readonly authOptions: CloudflareAuthOptions;
|
|
154
|
-
|
|
68
|
+
public readonly accountId: string;
|
|
69
|
+
public readonly baseUrl: string;
|
|
155
70
|
/**
|
|
156
71
|
* Create a new Cloudflare API client
|
|
157
72
|
* Use createCloudflareApi factory function instead of direct constructor
|
|
@@ -159,43 +74,13 @@ export class CloudflareApi {
|
|
|
159
74
|
*
|
|
160
75
|
* @param options API options
|
|
161
76
|
*/
|
|
162
|
-
constructor(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const apiKey =
|
|
166
|
-
options.apiKey?.unencrypted ||
|
|
167
|
-
process.env.CLOUDFLARE_API_KEY ||
|
|
168
|
-
undefined;
|
|
169
|
-
if (!apiKey) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
"No API key provided. Use createCloudflareApi() instead for automatic account discovery."
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
this.apiKey = apiKey;
|
|
175
|
-
|
|
176
|
-
this.email = options.email || process.env.CLOUDFLARE_EMAIL;
|
|
177
|
-
if (!this.email) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
"No email provided. Use createCloudflareApi() instead for automatic account discovery."
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Get account ID from options or environment
|
|
184
|
-
this.accountId =
|
|
185
|
-
options.accountId || process.env.CLOUDFLARE_ACCOUNT_ID || "";
|
|
186
|
-
if (!this.accountId) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
"No account ID provided. Use createCloudflareApi() instead for automatic account discovery."
|
|
189
|
-
);
|
|
77
|
+
constructor(
|
|
78
|
+
private readonly options: CloudflareApiOptions & {
|
|
79
|
+
accountId: string;
|
|
190
80
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
|
|
195
|
-
// Store auth options for later use
|
|
196
|
-
this.authOptions = {
|
|
197
|
-
email: this.email,
|
|
198
|
-
};
|
|
81
|
+
) {
|
|
82
|
+
this.accountId = options.accountId;
|
|
83
|
+
this.baseUrl = options.baseUrl ?? "https://api.cloudflare.com/client/v4";
|
|
199
84
|
}
|
|
200
85
|
|
|
201
86
|
/**
|
|
@@ -206,47 +91,36 @@ export class CloudflareApi {
|
|
|
206
91
|
* @returns Raw Response object from fetch
|
|
207
92
|
*/
|
|
208
93
|
async fetch(path: string, init: RequestInit = {}): Promise<Response> {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (init.headers) {
|
|
212
|
-
const initHeaders = init.headers as Record<string, string>;
|
|
213
|
-
if (initHeaders["Content-Type"]) {
|
|
214
|
-
contentType = initHeaders["Content-Type"];
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Get auth headers (async now)
|
|
219
|
-
const authHeaders = await getCloudflareHeaders(
|
|
220
|
-
contentType,
|
|
221
|
-
this.authOptions
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Combine all headers
|
|
225
|
-
const combinedHeaders: Record<string, string> = {
|
|
226
|
-
...authHeaders,
|
|
94
|
+
let headers: Record<string, string> = {
|
|
95
|
+
"Content-Type": "application/json",
|
|
227
96
|
};
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
97
|
+
if (Array.isArray(init.headers)) {
|
|
98
|
+
init.headers.forEach(([key, value]) => {
|
|
99
|
+
headers[key] = value;
|
|
100
|
+
});
|
|
101
|
+
} else if (init.headers instanceof Headers) {
|
|
102
|
+
init.headers.forEach((value, key) => {
|
|
103
|
+
headers[key] = value;
|
|
234
104
|
});
|
|
105
|
+
} else if (init.headers) {
|
|
106
|
+
headers = init.headers;
|
|
235
107
|
}
|
|
108
|
+
headers = {
|
|
109
|
+
...(await getCloudflareAuthHeaders(this.options)),
|
|
110
|
+
...headers,
|
|
111
|
+
};
|
|
236
112
|
|
|
237
|
-
//
|
|
113
|
+
// TODO(sam): is this necessary?
|
|
238
114
|
if (init.body instanceof FormData) {
|
|
239
|
-
delete
|
|
115
|
+
delete headers["Content-Type"];
|
|
240
116
|
}
|
|
241
117
|
|
|
242
|
-
const url = `${this.baseUrl}${path}`;
|
|
243
|
-
|
|
244
118
|
// Use withExponentialBackoff for automatic retry on network errors
|
|
245
119
|
return withExponentialBackoff(
|
|
246
120
|
() =>
|
|
247
|
-
fetch(
|
|
121
|
+
fetch(`${this.baseUrl}${path}`, {
|
|
248
122
|
...init,
|
|
249
|
-
headers
|
|
123
|
+
headers,
|
|
250
124
|
}),
|
|
251
125
|
(error) => {
|
|
252
126
|
// Only retry on network-related errors
|
|
@@ -328,43 +202,3 @@ export class CloudflareApi {
|
|
|
328
202
|
return this.fetch(path, { ...init, method: "DELETE" });
|
|
329
203
|
}
|
|
330
204
|
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Create a temporary API client for bootstrapping
|
|
334
|
-
* This client can only be used to fetch the account ID
|
|
335
|
-
*/
|
|
336
|
-
async function fetchAccountId(): Promise<string> {
|
|
337
|
-
// Create a minimal API client for bootstrapping
|
|
338
|
-
const baseUrl = "https://api.cloudflare.com/client/v4";
|
|
339
|
-
|
|
340
|
-
// Get content type and auth headers
|
|
341
|
-
const authHeaders = await getCloudflareHeaders("application/json");
|
|
342
|
-
|
|
343
|
-
// Make the request to get accounts
|
|
344
|
-
const response = await fetch(`${baseUrl}/accounts`, {
|
|
345
|
-
method: "GET",
|
|
346
|
-
headers: authHeaders,
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
if (!response.ok) {
|
|
350
|
-
const errorData: any = await response.json().catch(() => ({
|
|
351
|
-
errors: [{ message: response.statusText }],
|
|
352
|
-
}));
|
|
353
|
-
|
|
354
|
-
throw new Error(
|
|
355
|
-
`Error fetching Cloudflare accounts: ${errorData.errors?.[0]?.message || response.statusText}`
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const data: any = await response.json();
|
|
360
|
-
const accounts = data.result as CloudflareAccount[];
|
|
361
|
-
|
|
362
|
-
if (!accounts || accounts.length === 0) {
|
|
363
|
-
throw new Error(
|
|
364
|
-
"No Cloudflare accounts found. Check your API token/key permissions."
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Return the first account ID
|
|
369
|
-
return accounts[0].id;
|
|
370
|
-
}
|