alchemy 0.76.0 → 0.77.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 +111 -30
- package/bin/services/execute-alchemy.ts +10 -1
- package/lib/aws/ec2/index.d.ts +1 -0
- package/lib/aws/ec2/index.d.ts.map +1 -1
- package/lib/aws/ec2/index.js +1 -0
- package/lib/aws/ec2/index.js.map +1 -1
- package/lib/aws/index.d.ts +1 -7
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -8
- package/lib/aws/index.js.map +1 -1
- package/lib/cloudflare/auth.d.ts.map +1 -1
- package/lib/cloudflare/auth.js +1 -0
- package/lib/cloudflare/auth.js.map +1 -1
- package/lib/cloudflare/bucket-custom-domain.d.ts +90 -0
- package/lib/cloudflare/bucket-custom-domain.d.ts.map +1 -0
- package/lib/cloudflare/bucket-custom-domain.js +81 -0
- package/lib/cloudflare/bucket-custom-domain.js.map +1 -0
- package/lib/cloudflare/bucket.d.ts +28 -4
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +41 -14
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.d.ts.map +1 -1
- package/lib/cloudflare/cloudflare-env-proxy.js +0 -3
- package/lib/cloudflare/cloudflare-env-proxy.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/index.d.ts +1 -0
- package/lib/cloudflare/index.d.ts.map +1 -1
- package/lib/cloudflare/index.js +1 -0
- package/lib/cloudflare/index.js.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/build-worker-options.js +3 -1
- package/lib/cloudflare/miniflare/build-worker-options.js.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -1
- package/lib/cloudflare/miniflare/remote-binding-proxy.js +73 -9
- package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -1
- package/lib/cloudflare/react-router/plugin.d.ts +2 -1
- package/lib/cloudflare/react-router/plugin.d.ts.map +1 -1
- package/lib/cloudflare/react-router/plugin.js +6 -14
- package/lib/cloudflare/react-router/plugin.js.map +1 -1
- package/lib/cloudflare/vite/plugin.d.ts.map +1 -1
- package/lib/cloudflare/vite/plugin.js +0 -3
- package/lib/cloudflare/vite/plugin.js.map +1 -1
- package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
- package/lib/cloudflare/worker-bundle.js +17 -0
- package/lib/cloudflare/worker-bundle.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +6 -4
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
- package/lib/cloudflare/worker-subdomain.js +0 -1
- package/lib/cloudflare/worker-subdomain.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +28 -38
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +2 -281
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +23 -11
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/encrypt.d.ts +28 -3
- package/lib/encrypt.d.ts.map +1 -1
- package/lib/encrypt.js +77 -3
- package/lib/encrypt.js.map +1 -1
- package/lib/planetscale/role.d.ts +9 -0
- package/lib/planetscale/role.d.ts.map +1 -1
- package/lib/planetscale/role.js +18 -3
- package/lib/planetscale/role.js.map +1 -1
- package/lib/prisma-postgres/api.d.ts.map +1 -1
- package/lib/prisma-postgres/api.js +1 -0
- package/lib/prisma-postgres/api.js.map +1 -1
- package/lib/scope.d.ts +5 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +23 -0
- package/lib/scope.js.map +1 -1
- package/lib/serde.js +1 -1
- package/lib/serde.js.map +1 -1
- package/lib/util/telemetry.js +4 -0
- package/lib/util/telemetry.js.map +1 -1
- package/package.json +12 -10
- package/src/aws/ec2/index.ts +1 -0
- package/src/aws/index.ts +1 -9
- package/src/cloudflare/auth.ts +1 -0
- package/src/cloudflare/bucket-custom-domain.ts +281 -0
- package/src/cloudflare/bucket.ts +85 -18
- package/src/cloudflare/cloudflare-env-proxy.ts +0 -3
- package/src/cloudflare/compatibility-date.gen.ts +1 -1
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/miniflare/build-worker-options.ts +3 -1
- package/src/cloudflare/miniflare/remote-binding-proxy.ts +80 -9
- package/src/cloudflare/react-router/plugin.ts +9 -21
- package/src/cloudflare/vite/plugin.ts +0 -3
- package/src/cloudflare/worker-bundle.ts +28 -1
- package/src/cloudflare/worker-metadata.ts +8 -4
- package/src/cloudflare/worker-subdomain.ts +0 -1
- package/src/cloudflare/worker.ts +32 -45
- package/src/cloudflare/wrangler.json.ts +62 -397
- package/src/encrypt.ts +115 -3
- package/src/planetscale/role.ts +30 -3
- package/src/prisma-postgres/api.ts +1 -0
- package/src/scope.ts +27 -0
- package/src/serde.ts +1 -1
- package/src/util/telemetry.ts +5 -0
- package/workers/cloudflare-state-store.js +4802 -8
- package/workers/dofs-state-store.js +632 -2
- package/workers/remote-binding-proxy.js +1929 -3
- package/workers/tunnel-proxy.js +58 -6
- package/workers/tunnel-proxy.ts +1 -1
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type { Context } from "../context";
|
|
2
|
+
import { Resource } from "../resource";
|
|
3
|
+
import { diff } from "../util/diff";
|
|
4
|
+
import {
|
|
5
|
+
CloudflareApi,
|
|
6
|
+
createCloudflareApi,
|
|
7
|
+
type CloudflareApiOptions,
|
|
8
|
+
} from "./api";
|
|
9
|
+
import { CloudflareApiError } from "./api-error";
|
|
10
|
+
import { extractCloudflareResult } from "./api-response";
|
|
11
|
+
import { withJurisdiction, type R2BucketJurisdiction } from "./bucket";
|
|
12
|
+
import { inferZoneIdFromPattern } from "./route";
|
|
13
|
+
import type { Zone } from "./zone";
|
|
14
|
+
|
|
15
|
+
export interface R2BucketCustomDomainProps
|
|
16
|
+
extends CloudflareApiOptions,
|
|
17
|
+
R2BucketCustomDomainOptions {
|
|
18
|
+
/**
|
|
19
|
+
* The bucket to attach the domain to.
|
|
20
|
+
*/
|
|
21
|
+
bucketName: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The jurisdiction of the bucket.
|
|
25
|
+
* @default "default"
|
|
26
|
+
*/
|
|
27
|
+
jurisdiction?: R2BucketJurisdiction;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Development mode properties
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
dev?: {
|
|
34
|
+
remote?: boolean;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface R2BucketCustomDomainOptions {
|
|
39
|
+
/**
|
|
40
|
+
* The domain name to attach to the bucket.
|
|
41
|
+
*/
|
|
42
|
+
domain: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether the domain is enabled.
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The zone to attach the domain to.
|
|
52
|
+
*/
|
|
53
|
+
zone?: string | Zone;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* An allowlist of ciphers for TLS termination. These ciphers must be in the BoringSSL format.
|
|
57
|
+
*/
|
|
58
|
+
ciphers?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The minimum TLS version to support.
|
|
62
|
+
*/
|
|
63
|
+
minTLS?: "1.0" | "1.1" | "1.2" | "1.3" | (string & {});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Whether to adopt an existing custom domain binding during creation.
|
|
67
|
+
* If false and the domain already exists, creation will fail.
|
|
68
|
+
* This only applies during the create phase.
|
|
69
|
+
* @default false
|
|
70
|
+
*/
|
|
71
|
+
adopt?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether to delete the custom domain binding during deletion.
|
|
75
|
+
* If false, the custom domain binding will remain but the resource will be removed from state
|
|
76
|
+
* @default true
|
|
77
|
+
*/
|
|
78
|
+
delete?: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type R2BucketCustomDomain = {
|
|
82
|
+
/**
|
|
83
|
+
* The domain name.
|
|
84
|
+
*/
|
|
85
|
+
domain: string;
|
|
86
|
+
/**
|
|
87
|
+
* Whether the domain is enabled.
|
|
88
|
+
*/
|
|
89
|
+
enabled: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* The zone ID.
|
|
92
|
+
*/
|
|
93
|
+
zoneId: string;
|
|
94
|
+
/**
|
|
95
|
+
* The ciphers to support.
|
|
96
|
+
*/
|
|
97
|
+
ciphers?: string[];
|
|
98
|
+
/**
|
|
99
|
+
* The minimum TLS version to support.
|
|
100
|
+
*/
|
|
101
|
+
minTLS?: "1.0" | "1.1" | "1.2" | "1.3" | (string & {});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const R2BucketCustomDomain = Resource(
|
|
105
|
+
"cloudflare::R2BucketCustomDomain",
|
|
106
|
+
async function (
|
|
107
|
+
this: Context<R2BucketCustomDomain>,
|
|
108
|
+
_id: string,
|
|
109
|
+
props: R2BucketCustomDomainProps,
|
|
110
|
+
): Promise<R2BucketCustomDomain> {
|
|
111
|
+
if (this.scope.local && !props.dev?.remote && this.phase !== "delete") {
|
|
112
|
+
return {
|
|
113
|
+
domain: props.domain,
|
|
114
|
+
enabled: props.enabled ?? false,
|
|
115
|
+
zoneId:
|
|
116
|
+
typeof props.zone === "string"
|
|
117
|
+
? props.zone
|
|
118
|
+
: (props.zone?.id ?? "noop-zone"),
|
|
119
|
+
ciphers: props.ciphers,
|
|
120
|
+
minTLS: props.minTLS,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const adopt = props.adopt ?? this.scope.adopt;
|
|
124
|
+
const api = await createCloudflareApi(props);
|
|
125
|
+
const payload: R2BucketCustomDomain = {
|
|
126
|
+
domain: props.domain,
|
|
127
|
+
enabled: props.enabled ?? true,
|
|
128
|
+
zoneId:
|
|
129
|
+
typeof props.zone === "string"
|
|
130
|
+
? props.zone
|
|
131
|
+
: (props.zone?.id ??
|
|
132
|
+
(await inferZoneIdFromPattern(api, props.domain))),
|
|
133
|
+
ciphers: props.ciphers,
|
|
134
|
+
minTLS: props.minTLS,
|
|
135
|
+
};
|
|
136
|
+
switch (this.phase) {
|
|
137
|
+
case "create": {
|
|
138
|
+
await createBucketCustomDomain(
|
|
139
|
+
api,
|
|
140
|
+
props.bucketName,
|
|
141
|
+
props.jurisdiction,
|
|
142
|
+
payload,
|
|
143
|
+
).catch(async (error) => {
|
|
144
|
+
if (
|
|
145
|
+
error instanceof CloudflareApiError &&
|
|
146
|
+
error.status === 409 &&
|
|
147
|
+
adopt
|
|
148
|
+
) {
|
|
149
|
+
return await updateBucketCustomDomain(
|
|
150
|
+
api,
|
|
151
|
+
props.bucketName,
|
|
152
|
+
props.jurisdiction,
|
|
153
|
+
payload,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
});
|
|
158
|
+
return payload;
|
|
159
|
+
}
|
|
160
|
+
case "update": {
|
|
161
|
+
if (
|
|
162
|
+
diff(this.output, payload).some(
|
|
163
|
+
(property) => property === "domain" || property === "zoneId",
|
|
164
|
+
)
|
|
165
|
+
) {
|
|
166
|
+
return this.replace();
|
|
167
|
+
}
|
|
168
|
+
await updateBucketCustomDomain(
|
|
169
|
+
api,
|
|
170
|
+
props.bucketName,
|
|
171
|
+
props.jurisdiction,
|
|
172
|
+
payload,
|
|
173
|
+
);
|
|
174
|
+
return payload;
|
|
175
|
+
}
|
|
176
|
+
case "delete": {
|
|
177
|
+
if (props.delete !== false) {
|
|
178
|
+
await deleteBucketCustomDomain(
|
|
179
|
+
api,
|
|
180
|
+
props.bucketName,
|
|
181
|
+
props.domain,
|
|
182
|
+
props.jurisdiction,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
return this.destroy();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
async function createBucketCustomDomain(
|
|
192
|
+
api: CloudflareApi,
|
|
193
|
+
bucket: string,
|
|
194
|
+
jurisdiction: R2BucketJurisdiction | undefined,
|
|
195
|
+
payload: R2BucketCustomDomain,
|
|
196
|
+
) {
|
|
197
|
+
return await extractCloudflareResult(
|
|
198
|
+
`create custom domain for bucket "${bucket}"`,
|
|
199
|
+
api.post(
|
|
200
|
+
`/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom`,
|
|
201
|
+
payload,
|
|
202
|
+
{ headers: withJurisdiction({ jurisdiction }) },
|
|
203
|
+
),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function updateBucketCustomDomain(
|
|
208
|
+
api: CloudflareApi,
|
|
209
|
+
bucket: string,
|
|
210
|
+
jurisdiction: R2BucketJurisdiction | undefined,
|
|
211
|
+
payload: R2BucketCustomDomain,
|
|
212
|
+
) {
|
|
213
|
+
return await extractCloudflareResult(
|
|
214
|
+
`update custom domain for bucket "${bucket}"`,
|
|
215
|
+
api.put(
|
|
216
|
+
`/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(payload.domain)}`,
|
|
217
|
+
payload,
|
|
218
|
+
{ headers: withJurisdiction({ jurisdiction }) },
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function deleteBucketCustomDomain(
|
|
224
|
+
api: CloudflareApi,
|
|
225
|
+
bucket: string,
|
|
226
|
+
domain: string,
|
|
227
|
+
jurisdiction: R2BucketJurisdiction | undefined,
|
|
228
|
+
) {
|
|
229
|
+
return await extractCloudflareResult(
|
|
230
|
+
`delete custom domain for bucket "${bucket}"`,
|
|
231
|
+
api.delete(
|
|
232
|
+
`/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(domain)}`,
|
|
233
|
+
{ headers: withJurisdiction({ jurisdiction }) },
|
|
234
|
+
),
|
|
235
|
+
).catch((error) => {
|
|
236
|
+
if (!(error instanceof CloudflareApiError && error.status === 404)) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function getBucketCustomDomain(
|
|
243
|
+
api: CloudflareApi,
|
|
244
|
+
bucket: string,
|
|
245
|
+
domain: string,
|
|
246
|
+
jurisdiction: R2BucketJurisdiction | undefined,
|
|
247
|
+
) {
|
|
248
|
+
return await extractCloudflareResult<
|
|
249
|
+
R2BucketCustomDomain & {
|
|
250
|
+
zoneName?: string;
|
|
251
|
+
status: {
|
|
252
|
+
ownership:
|
|
253
|
+
| "pending"
|
|
254
|
+
| "active"
|
|
255
|
+
| "deactivated"
|
|
256
|
+
| "blocked"
|
|
257
|
+
| "error"
|
|
258
|
+
| "unknown";
|
|
259
|
+
ssl:
|
|
260
|
+
| "initializing"
|
|
261
|
+
| "pending"
|
|
262
|
+
| "active"
|
|
263
|
+
| "deactivated"
|
|
264
|
+
| "error"
|
|
265
|
+
| "unknown";
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
>(
|
|
269
|
+
`get custom domain for bucket "${bucket}"`,
|
|
270
|
+
api.get(
|
|
271
|
+
`/accounts/${api.accountId}/r2/buckets/${bucket}/domains/custom/${encodeURIComponent(domain)}`,
|
|
272
|
+
{
|
|
273
|
+
headers: withJurisdiction({ jurisdiction }),
|
|
274
|
+
},
|
|
275
|
+
),
|
|
276
|
+
).catch((error) => {
|
|
277
|
+
if (!(error instanceof CloudflareApiError && error.status === 404)) {
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
type CloudflareApi,
|
|
18
18
|
type CloudflareApiOptions,
|
|
19
19
|
} from "./api.ts";
|
|
20
|
+
import {
|
|
21
|
+
R2BucketCustomDomain,
|
|
22
|
+
type R2BucketCustomDomainOptions,
|
|
23
|
+
} from "./bucket-custom-domain.ts";
|
|
20
24
|
import { deleteMiniflareBinding } from "./miniflare/delete.ts";
|
|
21
25
|
import { getDefaultPersistPath } from "./miniflare/paths.ts";
|
|
22
26
|
|
|
@@ -56,9 +60,25 @@ export interface BucketProps extends CloudflareApiOptions {
|
|
|
56
60
|
/**
|
|
57
61
|
* Whether to allow public access through the r2.dev subdomain
|
|
58
62
|
* Only for development purposes - use custom domains for production
|
|
63
|
+
* @deprecated Use `devDomain` instead
|
|
59
64
|
*/
|
|
60
65
|
allowPublicAccess?: boolean;
|
|
61
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Whether to allow public access through the r2.dev subdomain
|
|
69
|
+
* Only for development purposes - use custom domains for production
|
|
70
|
+
*/
|
|
71
|
+
devDomain?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The custom domain(s) to attach to the bucket.
|
|
75
|
+
*/
|
|
76
|
+
domains?:
|
|
77
|
+
| string
|
|
78
|
+
| R2BucketCustomDomainOptions
|
|
79
|
+
| string[]
|
|
80
|
+
| R2BucketCustomDomainOptions[];
|
|
81
|
+
|
|
62
82
|
/**
|
|
63
83
|
* Whether to delete the bucket.
|
|
64
84
|
* If set to false, the bucket will remain but the resource will be removed from state
|
|
@@ -307,7 +327,10 @@ export type R2Bucket = _R2Bucket & {
|
|
|
307
327
|
/**
|
|
308
328
|
* Output returned after R2 Bucket creation/update
|
|
309
329
|
*/
|
|
310
|
-
type _R2Bucket = Omit<
|
|
330
|
+
type _R2Bucket = Omit<
|
|
331
|
+
BucketProps,
|
|
332
|
+
"delete" | "dev" | "domains" | "devDomain"
|
|
333
|
+
> & {
|
|
311
334
|
/**
|
|
312
335
|
* Resource type identifier
|
|
313
336
|
*/
|
|
@@ -330,8 +353,19 @@ type _R2Bucket = Omit<BucketProps, "delete" | "dev"> & {
|
|
|
330
353
|
|
|
331
354
|
/**
|
|
332
355
|
* The `r2.dev` subdomain for the bucket, if `allowPublicAccess` is true
|
|
356
|
+
* @deprecated Use `devDomain` instead
|
|
357
|
+
*/
|
|
358
|
+
domain?: string;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The `r2.dev` subdomain for the bucket, if `devDomain` is true
|
|
333
362
|
*/
|
|
334
|
-
|
|
363
|
+
devDomain: string | undefined;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* The custom domains for the bucket, if applicable
|
|
367
|
+
*/
|
|
368
|
+
domains: string[] | undefined;
|
|
335
369
|
|
|
336
370
|
/**
|
|
337
371
|
* Development mode properties
|
|
@@ -590,10 +624,14 @@ const _R2Bucket = Resource(
|
|
|
590
624
|
props.name ?? (this.output?.name || this.scope.createPhysicalName(id));
|
|
591
625
|
|
|
592
626
|
if (this.phase === "update" && this.output?.name !== bucketName) {
|
|
593
|
-
this.replace(
|
|
627
|
+
this.replace(
|
|
628
|
+
// force replacement if custom domain is enabled; otherwise, replace will fail because the bucket has child resources
|
|
629
|
+
!!this.output.domains?.length,
|
|
630
|
+
);
|
|
594
631
|
}
|
|
595
632
|
|
|
596
|
-
const
|
|
633
|
+
const isDevDomainEnabled =
|
|
634
|
+
props.devDomain === true || props.allowPublicAccess === true;
|
|
597
635
|
const isLocal = this.scope.local && !props.dev?.remote;
|
|
598
636
|
const dev = {
|
|
599
637
|
id: this.output?.dev?.id ?? bucketName,
|
|
@@ -602,14 +640,42 @@ const _R2Bucket = Resource(
|
|
|
602
640
|
} satisfies _R2Bucket["dev"];
|
|
603
641
|
const adopt = props.adopt ?? this.scope.adopt;
|
|
604
642
|
|
|
643
|
+
async function createDomains(domain: NonNullable<BucketProps["domains"]>) {
|
|
644
|
+
return Promise.all(
|
|
645
|
+
(Array.isArray(domain) ? domain : [domain]).map(async (domain) => {
|
|
646
|
+
const options: R2BucketCustomDomainOptions =
|
|
647
|
+
typeof domain === "string"
|
|
648
|
+
? { domain, adopt, delete: props.delete }
|
|
649
|
+
: {
|
|
650
|
+
...domain,
|
|
651
|
+
adopt: domain.adopt ?? adopt,
|
|
652
|
+
delete: domain.delete ?? props.delete,
|
|
653
|
+
};
|
|
654
|
+
const output = await R2BucketCustomDomain(options.domain, {
|
|
655
|
+
...options,
|
|
656
|
+
accountId: props.accountId,
|
|
657
|
+
apiKey: props.apiKey,
|
|
658
|
+
apiToken: props.apiToken,
|
|
659
|
+
email: props.email,
|
|
660
|
+
baseUrl: props.baseUrl,
|
|
661
|
+
profile: props.profile,
|
|
662
|
+
bucketName: bucketName,
|
|
663
|
+
jurisdiction: props.jurisdiction,
|
|
664
|
+
dev,
|
|
665
|
+
});
|
|
666
|
+
return output.domain;
|
|
667
|
+
}),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
605
671
|
if (isLocal) {
|
|
606
672
|
return {
|
|
607
673
|
name: bucketName,
|
|
608
674
|
location: this.output?.location ?? "",
|
|
609
675
|
creationDate: this.output?.creationDate ?? new Date(),
|
|
610
676
|
jurisdiction: this.output?.jurisdiction ?? "default",
|
|
611
|
-
|
|
612
|
-
|
|
677
|
+
devDomain: this.output?.devDomain ?? this.output?.domain,
|
|
678
|
+
domains: props.domains ? await createDomains(props.domains) : undefined,
|
|
613
679
|
type: "r2_bucket",
|
|
614
680
|
accountId: this.output?.accountId ?? "",
|
|
615
681
|
cors: props.cors,
|
|
@@ -648,10 +714,10 @@ const _R2Bucket = Resource(
|
|
|
648
714
|
throw err;
|
|
649
715
|
},
|
|
650
716
|
);
|
|
651
|
-
const
|
|
717
|
+
const devDomain = await putManagedDomain(
|
|
652
718
|
api,
|
|
653
719
|
bucketName,
|
|
654
|
-
|
|
720
|
+
isDevDomainEnabled,
|
|
655
721
|
props.jurisdiction,
|
|
656
722
|
);
|
|
657
723
|
if (props.cors?.length) {
|
|
@@ -673,13 +739,14 @@ const _R2Bucket = Resource(
|
|
|
673
739
|
if (props.dataCatalog) {
|
|
674
740
|
dataCatalog = await enableDataCatalog(api, bucketName);
|
|
675
741
|
}
|
|
742
|
+
|
|
676
743
|
return {
|
|
677
744
|
name: bucketName,
|
|
678
745
|
location: bucket.location,
|
|
679
746
|
creationDate: new Date(bucket.creation_date),
|
|
680
747
|
jurisdiction: bucket.jurisdiction,
|
|
681
|
-
|
|
682
|
-
|
|
748
|
+
devDomain,
|
|
749
|
+
domains: props.domains ? await createDomains(props.domains) : undefined,
|
|
683
750
|
type: "r2_bucket",
|
|
684
751
|
accountId: api.accountId,
|
|
685
752
|
lifecycle: props.lifecycle,
|
|
@@ -701,12 +768,12 @@ const _R2Bucket = Resource(
|
|
|
701
768
|
if (!this.output.catalog && props.dataCatalog) {
|
|
702
769
|
this.output.catalog = await enableDataCatalog(api, bucketName);
|
|
703
770
|
}
|
|
704
|
-
let
|
|
705
|
-
if (!!
|
|
706
|
-
|
|
771
|
+
let devDomain = this.output.devDomain ?? this.output.domain;
|
|
772
|
+
if (!!devDomain !== isDevDomainEnabled) {
|
|
773
|
+
devDomain = await putManagedDomain(
|
|
707
774
|
api,
|
|
708
775
|
bucketName,
|
|
709
|
-
|
|
776
|
+
isDevDomainEnabled,
|
|
710
777
|
props.jurisdiction,
|
|
711
778
|
);
|
|
712
779
|
}
|
|
@@ -723,12 +790,12 @@ const _R2Bucket = Resource(
|
|
|
723
790
|
}
|
|
724
791
|
return {
|
|
725
792
|
...this.output,
|
|
726
|
-
|
|
793
|
+
devDomain,
|
|
794
|
+
domains: props.domains ? await createDomains(props.domains) : undefined,
|
|
727
795
|
dev,
|
|
728
796
|
cors: props.cors,
|
|
729
797
|
lifecycle: props.lifecycle,
|
|
730
798
|
lock: props.lock,
|
|
731
|
-
domain,
|
|
732
799
|
};
|
|
733
800
|
}
|
|
734
801
|
},
|
|
@@ -1087,7 +1154,7 @@ export async function putBucketLifecycleRules(
|
|
|
1087
1154
|
export async function getBucketLifecycleRules(
|
|
1088
1155
|
api: CloudflareApi,
|
|
1089
1156
|
bucketName: string,
|
|
1090
|
-
props:
|
|
1157
|
+
props: { jurisdiction?: string },
|
|
1091
1158
|
): Promise<R2BucketLifecycleRule[]> {
|
|
1092
1159
|
const res = await api.get(
|
|
1093
1160
|
`/accounts/${api.accountId}/r2/buckets/${bucketName}/lifecycle`,
|
|
@@ -1140,7 +1207,7 @@ export async function putBucketLockRules(
|
|
|
1140
1207
|
export async function getBucketLockRules(
|
|
1141
1208
|
api: CloudflareApi,
|
|
1142
1209
|
bucketName: string,
|
|
1143
|
-
props:
|
|
1210
|
+
props: { jurisdiction?: R2BucketJurisdiction },
|
|
1144
1211
|
): Promise<R2BucketLockRule[]> {
|
|
1145
1212
|
const res = await api.get(
|
|
1146
1213
|
`/accounts/${api.accountId}/r2/buckets/${bucketName}/lock`,
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from "./astro/astro.ts";
|
|
|
15
15
|
export * from "./bindings.ts";
|
|
16
16
|
export * from "./bound.ts";
|
|
17
17
|
export * from "./browser-rendering.ts";
|
|
18
|
+
export * from "./bucket-custom-domain.ts";
|
|
18
19
|
export * from "./bucket-object.ts";
|
|
19
20
|
export * from "./bucket.ts";
|
|
20
21
|
export * from "./bun-spa/bun-spa.ts";
|
|
@@ -342,7 +342,9 @@ export const buildWorkerOptions = async (
|
|
|
342
342
|
const dlq = eventSource.settings?.deadLetterQueue;
|
|
343
343
|
(options.queueConsumers ??= {})[queue.name] = {
|
|
344
344
|
maxBatchSize: eventSource.settings?.batchSize,
|
|
345
|
-
maxBatchTimeout: eventSource.settings?.maxWaitTimeMs
|
|
345
|
+
maxBatchTimeout: eventSource.settings?.maxWaitTimeMs
|
|
346
|
+
? eventSource.settings?.maxWaitTimeMs / 1000
|
|
347
|
+
: undefined,
|
|
346
348
|
maxRetries: eventSource.settings?.maxRetries,
|
|
347
349
|
retryDelay: eventSource.settings?.retryDelay,
|
|
348
350
|
deadLetterQueue: typeof dlq === "string" ? dlq : dlq?.name,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { RemoteProxyConnectionString } from "miniflare";
|
|
2
2
|
import { WebSocket } from "ws";
|
|
3
|
+
import { Scope } from "../../scope.ts";
|
|
3
4
|
import { HTTPServer } from "../../util/http.ts";
|
|
5
|
+
import { memoize } from "../../util/memoize.ts";
|
|
4
6
|
import { extractCloudflareResult } from "../api-response.ts";
|
|
5
7
|
import type { CloudflareApi } from "../api.ts";
|
|
6
8
|
import type { WorkerBindingSpec } from "../bindings.ts";
|
|
@@ -37,7 +39,7 @@ export async function createRemoteProxyWorker(input: {
|
|
|
37
39
|
bindings: WorkerBindingSpec[];
|
|
38
40
|
}): Promise<RemoteBindingProxy> {
|
|
39
41
|
const script = await getInternalWorkerBundle("remote-binding-proxy");
|
|
40
|
-
const [token,
|
|
42
|
+
const [token, { host, accessToken }] = await Promise.all([
|
|
41
43
|
createWorkersPreviewToken(input.api, {
|
|
42
44
|
name: input.name,
|
|
43
45
|
metadata: {
|
|
@@ -52,18 +54,27 @@ export async function createRemoteProxyWorker(input: {
|
|
|
52
54
|
minimal_mode: true,
|
|
53
55
|
},
|
|
54
56
|
}),
|
|
55
|
-
getAccountSubdomain(input.api)
|
|
57
|
+
getAccountSubdomain(input.api).then(async (subdomain) => {
|
|
58
|
+
const host = `${input.name}.${subdomain}.workers.dev`;
|
|
59
|
+
return {
|
|
60
|
+
host,
|
|
61
|
+
accessToken: await getAccessToken(host),
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
56
64
|
]);
|
|
57
65
|
|
|
58
|
-
const
|
|
66
|
+
const baseHeaders: Record<string, string> = {
|
|
67
|
+
"cf-workers-preview-token": token,
|
|
68
|
+
host,
|
|
69
|
+
...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
|
|
70
|
+
};
|
|
59
71
|
|
|
60
72
|
const server = new HTTPServer({
|
|
61
73
|
websocket: async (req) => {
|
|
62
74
|
const input = new URL(req.url);
|
|
63
75
|
const proxied = new URL(input.pathname + input.search, `https://${host}`);
|
|
64
76
|
const headers: Record<string, string> = {
|
|
65
|
-
|
|
66
|
-
host,
|
|
77
|
+
...baseHeaders,
|
|
67
78
|
};
|
|
68
79
|
// We don't want to include all headers because some can mess with websocket connections.
|
|
69
80
|
// However, it's important to include `mf-` prefixed headers to configure bindings for miniflare to work.
|
|
@@ -81,8 +92,9 @@ export async function createRemoteProxyWorker(input: {
|
|
|
81
92
|
const proxied = new URL(url.pathname + url.search, `https://${host}`);
|
|
82
93
|
|
|
83
94
|
const headers = new Headers(req.headers);
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
for (const [key, value] of Object.entries(baseHeaders)) {
|
|
96
|
+
headers.set(key, value);
|
|
97
|
+
}
|
|
86
98
|
headers.delete("cf-connecting-ip");
|
|
87
99
|
|
|
88
100
|
const res = await fetch(proxied, {
|
|
@@ -146,8 +158,13 @@ async function createWorkersPreviewToken(
|
|
|
146
158
|
|
|
147
159
|
async function prewarm(url: string, previewToken: string) {
|
|
148
160
|
try {
|
|
161
|
+
const accessToken = await getAccessToken(new URL(url).hostname);
|
|
149
162
|
await fetch(url, {
|
|
150
|
-
|
|
163
|
+
method: "POST",
|
|
164
|
+
headers: {
|
|
165
|
+
"cf-workers-preview-token": previewToken,
|
|
166
|
+
...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
|
|
167
|
+
},
|
|
151
168
|
});
|
|
152
169
|
} catch {
|
|
153
170
|
// Ignore prewarm errors
|
|
@@ -162,7 +179,12 @@ async function createWorkersPreviewSession(api: CloudflareApi) {
|
|
|
162
179
|
"create workers preview session",
|
|
163
180
|
api.get(`/accounts/${api.accountId}/workers/subdomain/edge-preview`),
|
|
164
181
|
);
|
|
165
|
-
const
|
|
182
|
+
const accessToken = await getAccessToken(new URL(exchange_url).hostname);
|
|
183
|
+
const res = await fetch(exchange_url, {
|
|
184
|
+
headers: {
|
|
185
|
+
...(accessToken ? { cookie: `CF_Authorization=${accessToken}` } : {}),
|
|
186
|
+
},
|
|
187
|
+
});
|
|
166
188
|
if (!res.ok) {
|
|
167
189
|
throw new Error(
|
|
168
190
|
`Failed to create workers preview session: ${res.status} ${res.statusText}`,
|
|
@@ -171,3 +193,52 @@ async function createWorkersPreviewSession(api: CloudflareApi) {
|
|
|
171
193
|
const json: WorkersPreviewSession = await res.json();
|
|
172
194
|
return json;
|
|
173
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* If the given domain uses Cloudflare Access, fetches the access token for the domain.
|
|
199
|
+
* Otherwise, returns undefined.
|
|
200
|
+
* @see https://github.com/cloudflare/workers-sdk/blob/a5eb5134f73d3983655d325a4de71c6370c57faa/packages/wrangler/src/user/access.ts#L10
|
|
201
|
+
*/
|
|
202
|
+
const getAccessToken = memoize(async (hostname: string) => {
|
|
203
|
+
if (!(await domainUsesAccess(hostname))) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const result = await Scope.current
|
|
207
|
+
.exec(`access-${hostname}`, `cloudflared access login ${hostname}`)
|
|
208
|
+
.catch(() => {
|
|
209
|
+
throw new Error(
|
|
210
|
+
[
|
|
211
|
+
`The \`cloudflared\` CLI is not installed, but is required to access the domain "${hostname}".`,
|
|
212
|
+
`Please install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation and run \`cloudflare access login ${hostname}\`.`,
|
|
213
|
+
].join("\n"),
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
const matches = result.stdout.match(/fetched your token:\n\n(.*)/m);
|
|
217
|
+
if (matches?.[1]) {
|
|
218
|
+
return matches[1];
|
|
219
|
+
}
|
|
220
|
+
const error = new Error(
|
|
221
|
+
`Failed to get access token for domain "${hostname}".`,
|
|
222
|
+
);
|
|
223
|
+
Object.assign(error, result);
|
|
224
|
+
throw error;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Returns true if the domain uses Cloudflare Access.
|
|
229
|
+
*/
|
|
230
|
+
async function domainUsesAccess(hostname: string) {
|
|
231
|
+
try {
|
|
232
|
+
const response = await fetch(`https://${hostname}`, {
|
|
233
|
+
method: "HEAD",
|
|
234
|
+
redirect: "manual",
|
|
235
|
+
signal: AbortSignal.timeout(1000),
|
|
236
|
+
});
|
|
237
|
+
return !!(
|
|
238
|
+
response.status === 302 &&
|
|
239
|
+
response.headers.get("location")?.includes("cloudflareaccess.com")
|
|
240
|
+
);
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|