alchemy 0.5.1 → 0.6.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 +50 -151
- package/lib/cloudflare/auth.d.ts +55 -38
- package/lib/cloudflare/auth.js +148 -77
- 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/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 +4 -62
- package/lib/cloudflare/zone.d.ts +2 -6
- package/lib/cloudflare/zone.js +1 -3
- package/lib/index.d.ts +1 -0
- package/lib/internal/docs/providers.js +0 -7
- 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 +70 -219
- package/src/cloudflare/auth.ts +262 -119
- 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/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 +11 -103
- package/src/cloudflare/zone.ts +17 -25
- package/src/index.ts +1 -0
- package/src/internal/docs/providers.ts +0 -7
package/src/cloudflare/auth.ts
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { Secret } from "../secret";
|
|
5
|
+
import type { CloudflareAccountId } from "./account-id";
|
|
6
|
+
import { handleApiError } from "./api-error";
|
|
1
7
|
/**
|
|
2
8
|
* Authentication options for Cloudflare API
|
|
3
9
|
*/
|
|
4
10
|
export interface CloudflareAuthOptions {
|
|
5
11
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @default true
|
|
12
|
+
* API Key to use with API Key
|
|
8
13
|
*/
|
|
9
|
-
|
|
14
|
+
apiKey?: Secret;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* API Token to use with API Key
|
|
18
|
+
*/
|
|
19
|
+
apiToken?: Secret;
|
|
10
20
|
|
|
11
21
|
/**
|
|
12
22
|
* Email to use with API Key
|
|
@@ -15,126 +25,137 @@ export interface CloudflareAuthOptions {
|
|
|
15
25
|
email?: string;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
export interface CloudflareAccount {
|
|
29
|
+
name: string;
|
|
30
|
+
id: CloudflareAccountId;
|
|
31
|
+
type: "standard" | "zero_rating" | "full_control";
|
|
32
|
+
settings: {
|
|
33
|
+
enforce_twofactor: boolean;
|
|
34
|
+
api_access_enabled: null;
|
|
35
|
+
access_approval_expiry: null;
|
|
36
|
+
abuse_contact_email: null;
|
|
37
|
+
};
|
|
38
|
+
legacy_flags: {
|
|
39
|
+
enterprise_zone_quota: {
|
|
40
|
+
maximum: number;
|
|
41
|
+
current: number;
|
|
42
|
+
available: number;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
}
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
47
|
+
export interface CloudflareUserInfo {
|
|
48
|
+
apiToken?: Secret;
|
|
49
|
+
apiKey?: Secret;
|
|
50
|
+
email: string;
|
|
51
|
+
username: string;
|
|
52
|
+
accounts: CloudflareAccount[];
|
|
53
|
+
organizations: CloudflareOrganization[];
|
|
54
|
+
tokenPermissions: string[] | undefined;
|
|
55
|
+
first_name: string | null;
|
|
56
|
+
last_name: string | null;
|
|
57
|
+
telephone: string | null;
|
|
58
|
+
country: string | null;
|
|
59
|
+
zipcode: string | null;
|
|
60
|
+
two_factor_authentication_enabled: boolean;
|
|
61
|
+
two_factor_authentication_locked: boolean;
|
|
62
|
+
has_pro_zones: boolean;
|
|
63
|
+
has_business_zones: boolean;
|
|
64
|
+
has_enterprise_zones: boolean;
|
|
65
|
+
suspended: boolean;
|
|
66
|
+
betas: string[];
|
|
67
|
+
}
|
|
31
68
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
export interface CloudflareOrganization {
|
|
70
|
+
id: string;
|
|
71
|
+
name: string;
|
|
72
|
+
status: string;
|
|
73
|
+
permissions: string[];
|
|
74
|
+
roles: string[];
|
|
36
75
|
}
|
|
37
76
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
77
|
+
const userInfoCache: Record<string, CloudflareUserInfo> = {};
|
|
78
|
+
|
|
79
|
+
export async function getCloudflareUserInfo(
|
|
80
|
+
options: CloudflareAuthOptions
|
|
81
|
+
): Promise<CloudflareUserInfo> {
|
|
82
|
+
const cacheKey = JSON.stringify({
|
|
83
|
+
apiKey: options.apiKey?.unencrypted,
|
|
84
|
+
apiToken: options.apiToken?.unencrypted,
|
|
85
|
+
email: options.email,
|
|
86
|
+
});
|
|
87
|
+
if (userInfoCache[cacheKey]) {
|
|
88
|
+
return userInfoCache[cacheKey];
|
|
89
|
+
}
|
|
90
|
+
const headers = await getCloudflareAuthHeaders(options);
|
|
91
|
+
const user = await fetch("https://api.cloudflare.com/client/v4/user", {
|
|
92
|
+
headers,
|
|
93
|
+
});
|
|
94
|
+
if (!user.ok) {
|
|
95
|
+
if (user.status === 403) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"Cloudflare authentication required. Did you forget to login with `wrangler login` or set CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_KEY, or CLOUDFLARE_EMAIL, or CLOUDFLARE_API_KEY?"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
await handleApiError(user, "getting", "user", "user");
|
|
101
|
+
}
|
|
102
|
+
const accounts = await fetch(
|
|
103
|
+
"https://api.cloudflare.com/client/v4/accounts",
|
|
104
|
+
{
|
|
105
|
+
headers,
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
const userInfo: CloudflareUserInfo = {
|
|
109
|
+
...((await user.json()) as any).result,
|
|
110
|
+
accounts: ((await accounts.json()) as any).result,
|
|
48
111
|
};
|
|
112
|
+
userInfoCache[cacheKey] = userInfo;
|
|
113
|
+
return userInfo;
|
|
49
114
|
}
|
|
50
115
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* Priority:
|
|
55
|
-
* 1. API Token (CLOUDFLARE_API_TOKEN) - Recommended by Cloudflare
|
|
56
|
-
* 2. API Key (CLOUDFLARE_API_KEY) - Will attempt to discover email automatically
|
|
57
|
-
*
|
|
58
|
-
* @param options Authentication options
|
|
59
|
-
* @returns Authentication headers
|
|
60
|
-
*/
|
|
61
|
-
export async function getCloudflareAuth(
|
|
62
|
-
options: CloudflareAuthOptions = {},
|
|
63
|
-
): Promise<CloudflareAuthHeaders> {
|
|
64
|
-
const { required = true } = options;
|
|
65
|
-
|
|
116
|
+
export async function getCloudflareAuthHeaders(
|
|
117
|
+
options: CloudflareAuthOptions = {}
|
|
118
|
+
): Promise<Record<string, string>> {
|
|
66
119
|
// Check for API Token (preferred method)
|
|
67
|
-
const apiToken =
|
|
120
|
+
const apiToken =
|
|
121
|
+
options.apiToken?.unencrypted ?? process.env.CLOUDFLARE_API_TOKEN;
|
|
68
122
|
if (apiToken) {
|
|
69
123
|
return {
|
|
70
|
-
|
|
71
|
-
Authorization: `Bearer ${apiToken}`,
|
|
72
|
-
},
|
|
73
|
-
type: "token",
|
|
124
|
+
Authorization: `Bearer ${apiToken}`,
|
|
74
125
|
};
|
|
75
126
|
}
|
|
76
127
|
|
|
77
128
|
// Check for API Key
|
|
78
|
-
const apiKey = process.env.CLOUDFLARE_API_KEY;
|
|
129
|
+
const apiKey = options.apiKey?.unencrypted ?? process.env.CLOUDFLARE_API_KEY;
|
|
79
130
|
if (apiKey) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
},
|
|
88
|
-
type: "key",
|
|
89
|
-
email: providedEmail,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Otherwise, try to discover email from Cloudflare API
|
|
94
|
-
try {
|
|
95
|
-
const email = await getUserEmailFromApiKey(apiKey);
|
|
96
|
-
return {
|
|
97
|
-
headers: {
|
|
98
|
-
"X-Auth-Key": apiKey,
|
|
99
|
-
"X-Auth-Email": email,
|
|
100
|
-
},
|
|
101
|
-
type: "key",
|
|
102
|
-
email,
|
|
103
|
-
};
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error("Failed to discover email from API key:", error);
|
|
106
|
-
throw new Error(
|
|
107
|
-
"Failed to automatically discover email for API Key authentication.\n" +
|
|
108
|
-
"Options:\n" +
|
|
109
|
-
"1. Set CLOUDFLARE_EMAIL environment variable\n" +
|
|
110
|
-
"2. Use CLOUDFLARE_API_TOKEN instead (recommended by Cloudflare)",
|
|
111
|
-
);
|
|
112
|
-
}
|
|
131
|
+
return {
|
|
132
|
+
"X-Auth-Key": apiKey,
|
|
133
|
+
"X-Auth-Email":
|
|
134
|
+
options.email ??
|
|
135
|
+
process.env.CLOUDFLARE_EMAIL ??
|
|
136
|
+
(await getUserEmailFromApiKey(apiKey)),
|
|
137
|
+
};
|
|
113
138
|
}
|
|
114
139
|
|
|
115
|
-
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
" Generate a token at: https://dash.cloudflare.com/profile/api-tokens\n" +
|
|
121
|
-
"2. CLOUDFLARE_API_KEY environment variable",
|
|
122
|
-
);
|
|
140
|
+
const authConfig = await getRefreshedAuthConfig();
|
|
141
|
+
if (authConfig.oauth_token) {
|
|
142
|
+
return {
|
|
143
|
+
Authorization: `Bearer ${authConfig.oauth_token}`,
|
|
144
|
+
};
|
|
123
145
|
}
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Cloudflare authentication required. Did you forget to login with `wrangler login` or set CLOUDFLARE_API_TOKEN, CLOUDFLARE_API_KEY, or CLOUDFLARE_EMAIL, or CLOUDFLARE_API_KEY?"
|
|
149
|
+
);
|
|
129
150
|
}
|
|
130
151
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
*
|
|
134
|
-
* @param apiKey The Cloudflare API key
|
|
135
|
-
* @returns The user's email
|
|
136
|
-
*/
|
|
152
|
+
const emailCache: Record<string, string> = {};
|
|
153
|
+
|
|
137
154
|
async function getUserEmailFromApiKey(apiKey: string): Promise<string> {
|
|
155
|
+
if (emailCache[apiKey]) {
|
|
156
|
+
return emailCache[apiKey];
|
|
157
|
+
}
|
|
158
|
+
|
|
138
159
|
try {
|
|
139
160
|
const baseUrl = "https://api.cloudflare.com/client/v4";
|
|
140
161
|
|
|
@@ -148,40 +169,162 @@ async function getUserEmailFromApiKey(apiKey: string): Promise<string> {
|
|
|
148
169
|
|
|
149
170
|
if (!response.ok) {
|
|
150
171
|
throw new Error(
|
|
151
|
-
`Failed to get user information: ${response.status} ${response.statusText}
|
|
172
|
+
`Failed to get user information: ${response.status} ${response.statusText}`
|
|
152
173
|
);
|
|
153
174
|
}
|
|
154
175
|
|
|
155
|
-
const data = (await response.json()) as
|
|
176
|
+
const data = (await response.json()) as {
|
|
177
|
+
success: boolean;
|
|
178
|
+
result: {
|
|
179
|
+
id: string;
|
|
180
|
+
email: string;
|
|
181
|
+
name: string;
|
|
182
|
+
[key: string]: any;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
156
185
|
|
|
157
186
|
if (!data.success || !data.result || !data.result.email) {
|
|
158
187
|
throw new Error("Cloudflare API did not return valid user information");
|
|
159
188
|
}
|
|
160
189
|
|
|
190
|
+
emailCache[apiKey] = data.result.email;
|
|
161
191
|
return data.result.email;
|
|
162
192
|
} catch (error) {
|
|
163
193
|
console.error("Error retrieving email from Cloudflare API:", error);
|
|
164
194
|
throw new Error(
|
|
165
|
-
"Failed to automatically discover email for API Key authentication"
|
|
195
|
+
"Failed to automatically discover email for API Key authentication"
|
|
166
196
|
);
|
|
167
197
|
}
|
|
168
198
|
}
|
|
169
199
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
200
|
+
async function refreshAuthToken(
|
|
201
|
+
options: WranglerConfig
|
|
202
|
+
): Promise<WranglerConfig> {
|
|
203
|
+
const response = await fetch("https://dash.cloudflare.com/oauth2/token", {
|
|
204
|
+
method: "POST",
|
|
205
|
+
headers: {
|
|
206
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
207
|
+
},
|
|
208
|
+
body: new URLSearchParams({
|
|
209
|
+
grant_type: "refresh_token",
|
|
210
|
+
refresh_token: options.refresh_token!,
|
|
211
|
+
client_id: "54d11594-84e4-41aa-b438-e81b8fa78ee7",
|
|
212
|
+
}).toString(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`Failed to refresh auth token: ${response.status} ${response.statusText}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const data: any = await response.json();
|
|
222
|
+
if (!data.access_token) {
|
|
223
|
+
throw new Error("Failed to refresh auth token - no access token returned");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
options.oauth_token = data.access_token;
|
|
227
|
+
options.refresh_token = data.refresh_token;
|
|
228
|
+
options.expiration_time = new Date(
|
|
229
|
+
Date.now() + data.expires_in * 1000
|
|
230
|
+
).toISOString();
|
|
231
|
+
options.scopes = data.scope?.split(" ") || [];
|
|
232
|
+
|
|
233
|
+
return options;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface WranglerConfig {
|
|
237
|
+
path: string;
|
|
238
|
+
oauth_token?: string;
|
|
239
|
+
refresh_token?: string;
|
|
240
|
+
expiration_time?: string;
|
|
241
|
+
scopes?: string[];
|
|
242
|
+
/** @deprecated - this field was only provided by the deprecated v1 `wrangler config` command. */
|
|
243
|
+
api_token?: string;
|
|
244
|
+
}
|
|
182
245
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
246
|
+
async function getRefreshedAuthConfig(): Promise<WranglerConfig> {
|
|
247
|
+
let authConfig = await readWranglerConfig();
|
|
248
|
+
if (authConfig.expiration_time) {
|
|
249
|
+
const expiry = new Date(authConfig.expiration_time);
|
|
250
|
+
// if expiring in 10s
|
|
251
|
+
if (expiry.getTime() < Date.now() + 10 * 1000) {
|
|
252
|
+
authConfig = await refreshAuthToken(authConfig);
|
|
253
|
+
authConfigCache[authConfig.path] = authConfig;
|
|
254
|
+
await writeWranglerConfig(authConfig);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return authConfig;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function writeWranglerConfig(config: WranglerConfig) {
|
|
261
|
+
const TOML = await import("@iarna/toml");
|
|
262
|
+
const configPath = await findWranglerConfig();
|
|
263
|
+
config = {
|
|
264
|
+
...config,
|
|
186
265
|
};
|
|
266
|
+
// @ts-ignore - i put this here
|
|
267
|
+
delete config.path;
|
|
268
|
+
const toml = TOML.stringify(config as any);
|
|
269
|
+
await fs.writeFile(configPath, toml);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// cache the file once per process
|
|
273
|
+
const authConfigCache: Record<string, WranglerConfig> = {};
|
|
274
|
+
|
|
275
|
+
async function readWranglerConfig(): Promise<WranglerConfig> {
|
|
276
|
+
const configPath = await findWranglerConfig();
|
|
277
|
+
const config = (authConfigCache[configPath] ??= await parseTOML(
|
|
278
|
+
await fs.readFile(configPath, "utf-8")
|
|
279
|
+
));
|
|
280
|
+
config.path = configPath;
|
|
281
|
+
return config;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let wranglerConfigPath: string | undefined;
|
|
285
|
+
|
|
286
|
+
async function findWranglerConfig(): Promise<string> {
|
|
287
|
+
if (wranglerConfigPath) {
|
|
288
|
+
return wranglerConfigPath;
|
|
289
|
+
}
|
|
290
|
+
const environment = process.env.WRANGLER_API_ENVIRONMENT ?? "production";
|
|
291
|
+
const filePath = path.join(
|
|
292
|
+
"config",
|
|
293
|
+
`${environment === "production" ? "default.toml" : `${environment}.toml`}`
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const xdgAppPaths = (await import("xdg-app-paths")).default;
|
|
297
|
+
//TODO: We should implement a custom path --global-config and/or the WRANGLER_HOME type environment variable
|
|
298
|
+
const configDir = xdgAppPaths(".wrangler").config(); // New XDG compliant config path
|
|
299
|
+
const legacyConfigDir = path.join(os.homedir(), ".wrangler"); // Legacy config in user's home directory
|
|
300
|
+
|
|
301
|
+
// Check for the .wrangler directory in root if it is not there then use the XDG compliant path.
|
|
302
|
+
wranglerConfigPath = path.join(
|
|
303
|
+
(await isDirectory(legacyConfigDir)) ? legacyConfigDir : configDir,
|
|
304
|
+
filePath
|
|
305
|
+
);
|
|
306
|
+
return wranglerConfigPath;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function parseTOML(input: string): Promise<any> {
|
|
310
|
+
const TOML = await import("@iarna/toml");
|
|
311
|
+
try {
|
|
312
|
+
// Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
|
|
313
|
+
const normalizedInput = input.replace(/\r\n/g, "\n");
|
|
314
|
+
return TOML.parse(normalizedInput);
|
|
315
|
+
} catch (err: any) {
|
|
316
|
+
const { name } = err;
|
|
317
|
+
if (name !== "TomlError") {
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
throw new Error("TOML parse error");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function isDirectory(dir: string) {
|
|
325
|
+
try {
|
|
326
|
+
return (await fs.stat(dir)).isDirectory();
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
187
330
|
}
|
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { AwsClient } from "aws4fetch";
|
|
2
2
|
import type { Context } from "../context";
|
|
3
3
|
import { Resource } from "../resource";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
CloudflareApiError,
|
|
8
|
-
createCloudflareApi,
|
|
9
|
-
handleApiError,
|
|
10
|
-
type CloudflareApiOptions,
|
|
11
|
-
} from "./api";
|
|
4
|
+
import type { Secret } from "../secret";
|
|
5
|
+
import { CloudflareApi, createCloudflareApi } from "./api";
|
|
6
|
+
import { CloudflareApiError, handleApiError } from "./api-error";
|
|
12
7
|
|
|
13
8
|
/**
|
|
14
9
|
* Properties for creating or updating an R2 Bucket
|
|
15
10
|
*/
|
|
16
|
-
export interface BucketProps
|
|
11
|
+
export interface BucketProps {
|
|
17
12
|
/**
|
|
18
13
|
* Name of the bucket
|
|
19
14
|
* Names can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)
|
|
@@ -52,6 +47,36 @@ export interface BucketProps extends CloudflareApiOptions {
|
|
|
52
47
|
* @default false
|
|
53
48
|
*/
|
|
54
49
|
empty?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* API Token to use for the bucket
|
|
53
|
+
*/
|
|
54
|
+
apiToken?: Secret;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* API Key to use for the bucket
|
|
58
|
+
*/
|
|
59
|
+
apiKey?: Secret;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Email to use for the bucket
|
|
63
|
+
*/
|
|
64
|
+
email?: string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Account ID to use for the bucket
|
|
68
|
+
*/
|
|
69
|
+
accountId?: string;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Access Key to use for the bucket
|
|
73
|
+
*/
|
|
74
|
+
accessKey?: Secret;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Secret Access Key to use for the bucket
|
|
78
|
+
*/
|
|
79
|
+
secretAccessKey?: Secret;
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
/**
|
|
@@ -135,7 +160,10 @@ export const R2Bucket = Resource(
|
|
|
135
160
|
if (props.delete !== false) {
|
|
136
161
|
if (props.empty) {
|
|
137
162
|
console.log("Emptying R2 bucket:", bucketName);
|
|
138
|
-
const r2Client = await createR2Client(
|
|
163
|
+
const r2Client = await createR2Client({
|
|
164
|
+
...props,
|
|
165
|
+
accountId: api.accountId,
|
|
166
|
+
});
|
|
139
167
|
// Empty the bucket first by deleting all objects
|
|
140
168
|
await emptyBucket(r2Client, bucketName, props.jurisdiction);
|
|
141
169
|
}
|
|
@@ -166,6 +194,7 @@ export const R2Bucket = Resource(
|
|
|
166
194
|
creationDate: new Date(),
|
|
167
195
|
jurisdiction: props.jurisdiction || "default",
|
|
168
196
|
type: "r2_bucket",
|
|
197
|
+
accountId: api.accountId,
|
|
169
198
|
});
|
|
170
199
|
}
|
|
171
200
|
}
|
|
@@ -175,9 +204,9 @@ export const R2Bucket = Resource(
|
|
|
175
204
|
* Configuration for R2 client to connect to Cloudflare R2
|
|
176
205
|
*/
|
|
177
206
|
export interface R2ClientConfig {
|
|
178
|
-
accountId
|
|
179
|
-
accessKeyId?:
|
|
180
|
-
secretAccessKey?:
|
|
207
|
+
accountId: string;
|
|
208
|
+
accessKeyId?: Secret;
|
|
209
|
+
secretAccessKey?: Secret;
|
|
181
210
|
jurisdiction?: string;
|
|
182
211
|
}
|
|
183
212
|
|
|
@@ -188,13 +217,12 @@ type R2Client = AwsClient & { accountId: string };
|
|
|
188
217
|
*
|
|
189
218
|
* @see https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/
|
|
190
219
|
*/
|
|
191
|
-
export
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const accessKeyId = config.accessKeyId || process.env.R2_ACCESS_KEY_ID;
|
|
220
|
+
export function createR2Client(config?: R2ClientConfig): Promise<R2Client> {
|
|
221
|
+
const accountId = config?.accountId ?? process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
222
|
+
const accessKeyId =
|
|
223
|
+
config?.accessKeyId?.unencrypted || process.env.R2_ACCESS_KEY_ID;
|
|
196
224
|
const secretAccessKey =
|
|
197
|
-
config
|
|
225
|
+
config?.secretAccessKey?.unencrypted || process.env.R2_SECRET_ACCESS_KEY;
|
|
198
226
|
|
|
199
227
|
if (!accountId) {
|
|
200
228
|
throw new Error("CLOUDFLARE_ACCOUNT_ID environment variable is required");
|
|
@@ -282,12 +310,11 @@ export async function createBucket(
|
|
|
282
310
|
);
|
|
283
311
|
|
|
284
312
|
if (!createResponse.ok) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
createResponse
|
|
313
|
+
return await handleApiError(
|
|
314
|
+
createResponse,
|
|
315
|
+
"creating",
|
|
316
|
+
"R2 bucket",
|
|
317
|
+
bucketName
|
|
291
318
|
);
|
|
292
319
|
}
|
|
293
320
|
|
|
@@ -469,10 +496,6 @@ export async function updatePublicAccess(
|
|
|
469
496
|
allowPublicAccess: boolean,
|
|
470
497
|
jurisdiction?: string
|
|
471
498
|
): Promise<void> {
|
|
472
|
-
console.log(
|
|
473
|
-
`/accounts/${api.accountId}/r2/buckets/${bucketName}/domains/managed`
|
|
474
|
-
);
|
|
475
|
-
|
|
476
499
|
const headers = withJurisdiction({}, jurisdiction);
|
|
477
500
|
|
|
478
501
|
const response = await api.put(
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { Context } from "../context";
|
|
2
2
|
import { Resource } from "../resource";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
CloudflareApi,
|
|
5
|
+
createCloudflareApi,
|
|
6
|
+
type CloudflareApiOptions,
|
|
7
|
+
} from "./api";
|
|
8
|
+
import { handleApiError } from "./api-error";
|
|
4
9
|
|
|
5
10
|
/**
|
|
6
11
|
* Properties for creating or updating a CustomDomain
|
|
7
12
|
*/
|
|
8
|
-
export interface CustomDomainProps {
|
|
13
|
+
export interface CustomDomainProps extends CloudflareApiOptions {
|
|
9
14
|
/**
|
|
10
15
|
* The domain name to bind to the worker
|
|
11
16
|
*/
|
|
@@ -89,7 +94,7 @@ export const CustomDomain = Resource(
|
|
|
89
94
|
props: CustomDomainProps
|
|
90
95
|
): Promise<CustomDomain> {
|
|
91
96
|
// Create Cloudflare API client with automatic account discovery
|
|
92
|
-
const api = await createCloudflareApi();
|
|
97
|
+
const api = await createCloudflareApi(props);
|
|
93
98
|
|
|
94
99
|
// Validate required properties
|
|
95
100
|
if (!props.name) {
|
|
@@ -5,7 +5,11 @@ import type {
|
|
|
5
5
|
DnsRecordWithMetadata,
|
|
6
6
|
} from "../dns/record";
|
|
7
7
|
import { Resource } from "../resource";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type CloudflareApi,
|
|
10
|
+
type CloudflareApiOptions,
|
|
11
|
+
createCloudflareApi,
|
|
12
|
+
} from "./api";
|
|
9
13
|
import type { CloudflareResponse } from "./response";
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -48,7 +52,7 @@ export interface DnsRecord extends DnsRecordWithMetadata {}
|
|
|
48
52
|
/**
|
|
49
53
|
* Properties for managing multiple DNS records
|
|
50
54
|
*/
|
|
51
|
-
export interface DnsRecordsProps {
|
|
55
|
+
export interface DnsRecordsProps extends CloudflareApiOptions {
|
|
52
56
|
/**
|
|
53
57
|
* Zone ID or domain name where records will be created
|
|
54
58
|
*/
|
|
@@ -127,7 +131,7 @@ export const DnsRecords = Resource(
|
|
|
127
131
|
props: DnsRecordsProps
|
|
128
132
|
): Promise<DnsRecords> {
|
|
129
133
|
// Create Cloudflare API client
|
|
130
|
-
const api = await createCloudflareApi();
|
|
134
|
+
const api = await createCloudflareApi(props);
|
|
131
135
|
|
|
132
136
|
// Get zone ID if domain name was provided
|
|
133
137
|
const zoneId = props.zoneId;
|