alchemy 0.12.9 → 0.12.11
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/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +17 -12
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bucket.d.ts +16 -3
- package/lib/cloudflare/bucket.d.ts.map +1 -1
- package/lib/cloudflare/bucket.js +31 -6
- package/lib/cloudflare/bucket.js.map +1 -1
- package/lib/cloudflare/external.d.ts +2 -0
- package/lib/cloudflare/external.d.ts.map +1 -0
- package/lib/cloudflare/external.js +30 -0
- package/lib/cloudflare/external.js.map +1 -0
- 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/pipeline.d.ts +7 -1
- package/lib/cloudflare/pipeline.d.ts.map +1 -1
- package/lib/cloudflare/pipeline.js +1 -1
- package/lib/cloudflare/pipeline.js.map +1 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts +2 -2
- package/lib/cloudflare/r2-rest-state-store.d.ts.map +1 -1
- package/lib/cloudflare/r2-rest-state-store.js +13 -5
- package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
- package/lib/cloudflare/redwood.d.ts +1 -33
- package/lib/cloudflare/redwood.d.ts.map +1 -1
- package/lib/cloudflare/redwood.js +7 -46
- package/lib/cloudflare/redwood.js.map +1 -1
- package/lib/cloudflare/website.d.ts +4 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +10 -2
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +17 -33
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +6 -0
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +1 -1
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/esbuild/bundle.d.ts +4 -4
- package/lib/esbuild/bundle.d.ts.map +1 -1
- package/lib/esbuild/bundle.js +9 -7
- package/lib/esbuild/bundle.js.map +1 -1
- package/lib/os/exec.d.ts +15 -15
- package/lib/os/exec.d.ts.map +1 -1
- package/lib/os/exec.js +47 -26
- package/lib/os/exec.js.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/api.ts +15 -13
- package/src/cloudflare/bucket.ts +56 -8
- package/src/cloudflare/external.ts +30 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/pipeline.ts +9 -2
- package/src/cloudflare/r2-rest-state-store.ts +24 -6
- package/src/cloudflare/redwood.ts +8 -46
- package/src/cloudflare/website.ts +19 -3
- package/src/cloudflare/worker.ts +22 -42
- package/src/cloudflare/wrangler.json.ts +8 -1
- package/src/esbuild/bundle.ts +11 -9
- package/src/os/exec.ts +61 -34
package/lib/os/exec.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { promisify } from "node:util";
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
3
2
|
import { Resource } from "../resource.js";
|
|
4
|
-
const execAsync = promisify(exec);
|
|
5
3
|
/**
|
|
6
4
|
* Execute a shell command
|
|
7
5
|
*
|
|
8
6
|
* @example
|
|
9
|
-
* // Run a simple command
|
|
7
|
+
* // Run a simple command with inherited stdio (default)
|
|
10
8
|
* const result = await Exec("list-files", {
|
|
11
9
|
* command: "ls -la"
|
|
12
10
|
* });
|
|
13
11
|
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Run a command and capture output instead of inheriting stdio
|
|
14
|
+
* const result = await Exec("list-files", {
|
|
15
|
+
* command: "ls -la",
|
|
16
|
+
* inheritStdio: false
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
14
19
|
* console.log(result.stdout);
|
|
15
20
|
*
|
|
16
21
|
* @example
|
|
@@ -22,13 +27,6 @@ const execAsync = promisify(exec);
|
|
|
22
27
|
* });
|
|
23
28
|
*
|
|
24
29
|
* @example
|
|
25
|
-
* // Run a command with a larger buffer for output
|
|
26
|
-
* const logs = await Exec("get-logs", {
|
|
27
|
-
* command: "cat large-log-file.log",
|
|
28
|
-
* maxBuffer: 10 * 1024 * 1024 // 10MB
|
|
29
|
-
* });
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
30
|
* // Run a memoized command that only re-executes when the command changes
|
|
33
31
|
* const memoizedCmd = await Exec("status-check", {
|
|
34
32
|
* command: "git status",
|
|
@@ -59,30 +57,53 @@ export const Exec = Resource("os::Exec", {
|
|
|
59
57
|
let stdout = "";
|
|
60
58
|
let stderr = "";
|
|
61
59
|
let exitCode = 0;
|
|
60
|
+
// Default to inheriting stdio unless explicitly set to false
|
|
61
|
+
const inheritStdio = props.inheritStdio !== false;
|
|
62
62
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// Parse the command into command and args
|
|
64
|
+
const [cmd, ...args] = props.command.split(/\s+/);
|
|
65
|
+
// Use spawn for better stdio control
|
|
66
|
+
const childProcess = spawn(cmd, args, {
|
|
66
67
|
cwd: props.cwd || process.cwd(),
|
|
67
68
|
env: { ...process.env, ...props.env },
|
|
68
|
-
|
|
69
|
+
shell: true, // Use shell to handle complex commands
|
|
70
|
+
stdio: inheritStdio ? "inherit" : "pipe", // Inherit stdio when requested
|
|
69
71
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (!inheritStdio) {
|
|
73
|
+
// If not inheriting stdio, collect output manually
|
|
74
|
+
childProcess.stdout?.on("data", (data) => {
|
|
75
|
+
stdout += data.toString();
|
|
76
|
+
});
|
|
77
|
+
childProcess.stderr?.on("data", (data) => {
|
|
78
|
+
stderr += data.toString();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// Wait for the process to complete
|
|
82
|
+
exitCode = await new Promise((resolve, reject) => {
|
|
83
|
+
childProcess.on("close", (code) => {
|
|
84
|
+
resolve(code || 0);
|
|
85
|
+
});
|
|
86
|
+
childProcess.on("error", (err) => {
|
|
87
|
+
if (props.throwOnError) {
|
|
88
|
+
reject(err);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
stderr += err.toString();
|
|
92
|
+
resolve(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
if (exitCode !== 0 && props.throwOnError) {
|
|
97
|
+
throw new Error(`Command failed with exit code ${exitCode}: ${props.command}`);
|
|
98
|
+
}
|
|
74
99
|
}
|
|
75
100
|
catch (error) {
|
|
76
|
-
console.log("error", error);
|
|
77
101
|
if (props.throwOnError) {
|
|
78
102
|
throw error;
|
|
79
103
|
}
|
|
80
104
|
// If not throwing, capture the error information
|
|
81
|
-
exitCode =
|
|
82
|
-
|
|
83
|
-
stderr = error.stderr || String(error);
|
|
84
|
-
console.log(stdout);
|
|
85
|
-
console.error(stderr);
|
|
105
|
+
exitCode = 1;
|
|
106
|
+
stderr += String(error);
|
|
86
107
|
}
|
|
87
108
|
// Return the execution result
|
|
88
109
|
return this({
|
|
@@ -90,9 +111,9 @@ export const Exec = Resource("os::Exec", {
|
|
|
90
111
|
command: props.command,
|
|
91
112
|
cwd: props.cwd,
|
|
92
113
|
env: props.env,
|
|
93
|
-
maxBuffer: props.maxBuffer,
|
|
94
114
|
throwOnError: props.throwOnError,
|
|
95
115
|
memoize: props.memoize,
|
|
116
|
+
inheritStdio,
|
|
96
117
|
exitCode,
|
|
97
118
|
stdout,
|
|
98
119
|
stderr,
|
package/lib/os/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/os/exec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/cloudflare/api.ts
CHANGED
|
@@ -117,22 +117,18 @@ export class CloudflareApi {
|
|
|
117
117
|
|
|
118
118
|
// Use withExponentialBackoff for automatic retry on network errors
|
|
119
119
|
return withExponentialBackoff(
|
|
120
|
-
() =>
|
|
121
|
-
fetch(`${this.baseUrl}${path}`, {
|
|
120
|
+
async () => {
|
|
121
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
122
122
|
...init,
|
|
123
123
|
headers,
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
errorMsg.includes("socket connection was closed") ||
|
|
130
|
-
errorMsg.includes("ECONNRESET") ||
|
|
131
|
-
errorMsg.includes("ETIMEDOUT") ||
|
|
132
|
-
errorMsg.includes("ECONNREFUSED");
|
|
133
|
-
|
|
134
|
-
return isNetworkError || error.status?.toString().startsWith("5");
|
|
124
|
+
});
|
|
125
|
+
if (response.status.toString().startsWith("5")) {
|
|
126
|
+
throw new InternalError("5xx error");
|
|
127
|
+
}
|
|
128
|
+
return response;
|
|
135
129
|
},
|
|
130
|
+
// transient errors should be retried aggressively
|
|
131
|
+
(error) => error instanceof InternalError,
|
|
136
132
|
5, // Maximum 5 attempts (1 initial + 4 retries)
|
|
137
133
|
1000 // Start with 1s delay, will exponentially increase
|
|
138
134
|
);
|
|
@@ -202,3 +198,9 @@ export class CloudflareApi {
|
|
|
202
198
|
return this.fetch(path, { ...init, method: "DELETE" });
|
|
203
199
|
}
|
|
204
200
|
}
|
|
201
|
+
|
|
202
|
+
class InternalError extends Error {
|
|
203
|
+
constructor(message: string) {
|
|
204
|
+
super(message);
|
|
205
|
+
}
|
|
206
|
+
}
|
package/src/cloudflare/bucket.ts
CHANGED
|
@@ -79,6 +79,11 @@ export interface BucketProps {
|
|
|
79
79
|
* Secret Access Key to use for the bucket
|
|
80
80
|
*/
|
|
81
81
|
secretAccessKey?: Secret;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Whether to adopt an existing bucket
|
|
85
|
+
*/
|
|
86
|
+
adopt?: boolean;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
/**
|
|
@@ -163,7 +168,6 @@ export const R2Bucket = Resource(
|
|
|
163
168
|
const bucketName = props.name || this.id;
|
|
164
169
|
|
|
165
170
|
if (this.phase === "delete") {
|
|
166
|
-
console.log("Deleting R2 bucket:", bucketName);
|
|
167
171
|
if (props.delete !== false) {
|
|
168
172
|
if (props.empty) {
|
|
169
173
|
console.log("Emptying R2 bucket:", bucketName);
|
|
@@ -175,8 +179,6 @@ export const R2Bucket = Resource(
|
|
|
175
179
|
await emptyBucket(r2Client, bucketName, props.jurisdiction);
|
|
176
180
|
}
|
|
177
181
|
|
|
178
|
-
// Delete R2 bucket
|
|
179
|
-
console.log("Deleting R2 bucket:", bucketName);
|
|
180
182
|
await deleteBucket(api, bucketName, props);
|
|
181
183
|
}
|
|
182
184
|
|
|
@@ -184,8 +186,17 @@ export const R2Bucket = Resource(
|
|
|
184
186
|
return this.destroy();
|
|
185
187
|
} else {
|
|
186
188
|
if (this.phase === "create") {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
try {
|
|
190
|
+
await createBucket(api, bucketName, props);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (err instanceof CloudflareApiError && err.status === 409) {
|
|
193
|
+
if (!props.adopt) {
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
189
200
|
}
|
|
190
201
|
|
|
191
202
|
await updatePublicAccess(
|
|
@@ -253,10 +264,15 @@ export function createR2Client(config?: R2ClientConfig): Promise<R2Client> {
|
|
|
253
264
|
}
|
|
254
265
|
|
|
255
266
|
interface CloudflareBucketResponse {
|
|
267
|
+
/**
|
|
268
|
+
* The bucket information returned from the Cloudflare REST API
|
|
269
|
+
* @see https://developers.cloudflare.com/api/node/resources/r2/subresources/buckets/models/bucket/#(schema)
|
|
270
|
+
*/
|
|
256
271
|
result: {
|
|
257
|
-
name: string;
|
|
258
|
-
location?: string;
|
|
259
272
|
creation_date: string;
|
|
273
|
+
location?: "apac" | "eeur" | "enam" | "weur" | "wnam" | "oc";
|
|
274
|
+
name: string;
|
|
275
|
+
storage_class?: "Standard" | "InfrequentAccess";
|
|
260
276
|
};
|
|
261
277
|
success: boolean;
|
|
262
278
|
errors: Array<{ code: number; message: string }>;
|
|
@@ -291,13 +307,45 @@ export function withJurisdiction(
|
|
|
291
307
|
return result;
|
|
292
308
|
}
|
|
293
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Get a bucket
|
|
312
|
+
*/
|
|
313
|
+
export async function getBucket(
|
|
314
|
+
api: CloudflareApi,
|
|
315
|
+
bucketName: string,
|
|
316
|
+
props: BucketProps = {}
|
|
317
|
+
): Promise<CloudflareBucketResponse> {
|
|
318
|
+
const headers = withJurisdiction({}, props);
|
|
319
|
+
const getResponse = await api.get(
|
|
320
|
+
`/accounts/${api.accountId}/r2/buckets/${bucketName}`,
|
|
321
|
+
{ headers }
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!getResponse.ok) {
|
|
325
|
+
return await handleApiError(getResponse, "get", "R2 bucket", bucketName);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (getResponse.status === 200) {
|
|
329
|
+
return (await getResponse.json()) as CloudflareBucketResponse;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const errorData: any = await getResponse.json().catch(() => ({
|
|
333
|
+
errors: [{ message: getResponse.statusText }],
|
|
334
|
+
}));
|
|
335
|
+
|
|
336
|
+
throw new CloudflareApiError(
|
|
337
|
+
`Error getting R2 bucket '${bucketName}': ${errorData.errors?.[0]?.message || getResponse.statusText}`,
|
|
338
|
+
getResponse
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
294
342
|
/**
|
|
295
343
|
* Create a new bucket
|
|
296
344
|
*/
|
|
297
345
|
export async function createBucket(
|
|
298
346
|
api: CloudflareApi,
|
|
299
347
|
bucketName: string,
|
|
300
|
-
props: BucketProps
|
|
348
|
+
props: BucketProps = {}
|
|
301
349
|
): Promise<CloudflareBucketResponse> {
|
|
302
350
|
// Create new R2 bucket
|
|
303
351
|
const createPayload: any = {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// https://developers.cloudflare.com/workers/runtime-apis/nodejs/#supported-nodejs-apis
|
|
2
|
+
const nodejs_compat = [
|
|
3
|
+
"node:async_hooks",
|
|
4
|
+
"node:assert",
|
|
5
|
+
"node:buffer",
|
|
6
|
+
"node:console",
|
|
7
|
+
"node:crypto",
|
|
8
|
+
"node:diagnostics_channel",
|
|
9
|
+
"node:dns",
|
|
10
|
+
"node:events",
|
|
11
|
+
"node:inspector",
|
|
12
|
+
"node:net",
|
|
13
|
+
"node:path",
|
|
14
|
+
"node:perf_hooks",
|
|
15
|
+
"node:process",
|
|
16
|
+
"node:querystring",
|
|
17
|
+
"node:stream",
|
|
18
|
+
"node:string_decoder",
|
|
19
|
+
"node:timers",
|
|
20
|
+
"node:tls", // partially supported
|
|
21
|
+
"node:url",
|
|
22
|
+
"node:util",
|
|
23
|
+
"node:zlib",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export const external = [
|
|
27
|
+
...nodejs_compat,
|
|
28
|
+
...nodejs_compat.map((p) => p.split(":")[1]),
|
|
29
|
+
"cloudflare:workers",
|
|
30
|
+
];
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./custom-domain.js";
|
|
|
9
9
|
export * from "./d1-database.js";
|
|
10
10
|
export * from "./dns-records.js";
|
|
11
11
|
export * from "./durable-object-namespace.js";
|
|
12
|
+
export * from "./external.js";
|
|
12
13
|
export * from "./hyperdrive.js";
|
|
13
14
|
export * from "./kv-namespace.js";
|
|
14
15
|
export * from "./local-dev-cloudflare-shim.js";
|
|
@@ -181,8 +181,10 @@ export type PipelineDestination = R2DestinationConfig;
|
|
|
181
181
|
export interface PipelineProps extends CloudflareApiOptions {
|
|
182
182
|
/**
|
|
183
183
|
* Name of the pipeline
|
|
184
|
+
*
|
|
185
|
+
* @default id
|
|
184
186
|
*/
|
|
185
|
-
name
|
|
187
|
+
name?: string;
|
|
186
188
|
|
|
187
189
|
/**
|
|
188
190
|
* Source configuration for the pipeline
|
|
@@ -232,6 +234,11 @@ export interface Pipeline<T extends PipelineRecord = PipelineRecord>
|
|
|
232
234
|
*/
|
|
233
235
|
id: string;
|
|
234
236
|
|
|
237
|
+
/**
|
|
238
|
+
* The name of the pipeline
|
|
239
|
+
*/
|
|
240
|
+
name: string;
|
|
241
|
+
|
|
235
242
|
/**
|
|
236
243
|
* HTTP endpoint URL for the pipeline
|
|
237
244
|
*/
|
|
@@ -315,7 +322,7 @@ export const Pipeline = Resource("cloudflare::Pipeline", async function <
|
|
|
315
322
|
Pipeline<T>
|
|
316
323
|
> {
|
|
317
324
|
const api = await createCloudflareApi(props);
|
|
318
|
-
const pipelineName = props.name
|
|
325
|
+
const pipelineName = props.name ?? id;
|
|
319
326
|
|
|
320
327
|
if (this.phase === "delete") {
|
|
321
328
|
if (props.delete !== false) {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type CloudflareApiOptions,
|
|
9
9
|
createCloudflareApi,
|
|
10
10
|
} from "./api.js";
|
|
11
|
+
import { createBucket, getBucket } from "./bucket.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Options for CloudflareR2StateStore
|
|
@@ -23,7 +24,7 @@ export interface CloudflareR2StateStoreOptions extends CloudflareApiOptions {
|
|
|
23
24
|
* The R2 bucket name to use
|
|
24
25
|
* Required - the bucket must already exist
|
|
25
26
|
*/
|
|
26
|
-
bucketName
|
|
27
|
+
bucketName?: string;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -44,7 +45,7 @@ export class R2RestStateStore implements StateStore {
|
|
|
44
45
|
*/
|
|
45
46
|
constructor(
|
|
46
47
|
public readonly scope: Scope,
|
|
47
|
-
private readonly options: CloudflareR2StateStoreOptions
|
|
48
|
+
private readonly options: CloudflareR2StateStoreOptions = {}
|
|
48
49
|
) {
|
|
49
50
|
// Use the scope's chain to build the prefix, similar to how FileSystemStateStore builds its directory
|
|
50
51
|
const scopePath = scope.chain.join("/");
|
|
@@ -52,10 +53,7 @@ export class R2RestStateStore implements StateStore {
|
|
|
52
53
|
? `${options.prefix}${scopePath}/`
|
|
53
54
|
: `alchemy/${scopePath}/`;
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
throw new Error("bucketName is required for CloudflareR2StateStore");
|
|
57
|
-
}
|
|
58
|
-
this.bucketName = options.bucketName;
|
|
56
|
+
this.bucketName = options.bucketName ?? "alchemy-state";
|
|
59
57
|
|
|
60
58
|
// We'll initialize the API in init() to allow for async creation
|
|
61
59
|
this.api = null as any;
|
|
@@ -70,6 +68,26 @@ export class R2RestStateStore implements StateStore {
|
|
|
70
68
|
// Create Cloudflare API client with automatic account discovery
|
|
71
69
|
this.api = await createCloudflareApi(this.options);
|
|
72
70
|
|
|
71
|
+
// Check if the alchemy state bucket exists
|
|
72
|
+
try {
|
|
73
|
+
await withExponentialBackoff(
|
|
74
|
+
() => getBucket(this.api, this.bucketName),
|
|
75
|
+
isRetryableError,
|
|
76
|
+
5,
|
|
77
|
+
1000
|
|
78
|
+
);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
// If not, create the alchemy state bucket
|
|
81
|
+
if (error instanceof CloudflareApiError && error.status === 404) {
|
|
82
|
+
await withExponentialBackoff(
|
|
83
|
+
() => createBucket(this.api, this.bucketName),
|
|
84
|
+
isRetryableError,
|
|
85
|
+
5,
|
|
86
|
+
1000
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
this.initialized = true;
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -23,7 +23,7 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
|
|
|
23
23
|
*
|
|
24
24
|
* @example
|
|
25
25
|
* // Deploy with a database binding
|
|
26
|
-
* import { D1Database } from
|
|
26
|
+
* import { D1Database } from alchemy/cloudflare";
|
|
27
27
|
*
|
|
28
28
|
* const database = await D1Database("redwood-db");
|
|
29
29
|
*
|
|
@@ -33,38 +33,6 @@ export type Redwood<B extends Bindings> = B extends { ASSETS: any }
|
|
|
33
33
|
* }
|
|
34
34
|
* });
|
|
35
35
|
*
|
|
36
|
-
* @example
|
|
37
|
-
* // Deploy with custom build command and environment variables
|
|
38
|
-
* const redwoodApp = await Redwood("custom-redwood", {
|
|
39
|
-
* command: "bun run test && RWSDK_DEPLOY=1 bun run build:production",
|
|
40
|
-
* bindings: {
|
|
41
|
-
* API_KEY: alchemy.secret("api-key-secret")
|
|
42
|
-
* },
|
|
43
|
-
* vars: {
|
|
44
|
-
* NODE_ENV: "production",
|
|
45
|
-
* APP_ENV: "staging"
|
|
46
|
-
* }
|
|
47
|
-
* });
|
|
48
|
-
*
|
|
49
|
-
* @example
|
|
50
|
-
* // Deploy with custom paths and additional compatibility flags
|
|
51
|
-
* const redwoodApp = await Redwood("redwood-custom-paths", {
|
|
52
|
-
* main: "custom/worker.js",
|
|
53
|
-
* assets: "custom/static",
|
|
54
|
-
* compatibilityFlags: ["nodejs_compat", "fetch_refused_to_set_cookies"]
|
|
55
|
-
* });
|
|
56
|
-
*
|
|
57
|
-
* @example
|
|
58
|
-
* // Deploy with custom bundle configuration
|
|
59
|
-
* const redwoodApp = await Redwood("redwood-custom-bundle", {
|
|
60
|
-
* bundle: {
|
|
61
|
-
* options: {
|
|
62
|
-
* external: ["node:events", "node:stream", "node:crypto"],
|
|
63
|
-
* format: "esm"
|
|
64
|
-
* }
|
|
65
|
-
* }
|
|
66
|
-
* });
|
|
67
|
-
*
|
|
68
36
|
* @param id - Unique identifier for the RedwoodJS application
|
|
69
37
|
* @param props - Configuration properties for the RedwoodJS deployment
|
|
70
38
|
* @returns A Cloudflare Worker resource representing the deployed RedwoodJS application
|
|
@@ -76,21 +44,15 @@ export async function Redwood<B extends Bindings>(
|
|
|
76
44
|
return Website(id, {
|
|
77
45
|
...props,
|
|
78
46
|
command: props?.command ?? "bun run clean && RWSDK_DEPLOY=1 bun run build",
|
|
79
|
-
wrangler:
|
|
80
|
-
|
|
47
|
+
wrangler:
|
|
48
|
+
props?.wrangler === false
|
|
49
|
+
? false
|
|
50
|
+
: {
|
|
51
|
+
main: props?.main ?? path.join("src", "worker.tsx"),
|
|
52
|
+
},
|
|
53
|
+
main: props?.main ?? path.join("dist", "worker", "worker.js"),
|
|
81
54
|
assets: props?.assets ?? path.join("dist", "client"),
|
|
82
55
|
compatibilityFlags: ["nodejs_compat", ...(props?.compatibilityFlags ?? [])],
|
|
83
56
|
compatibilityDate: props?.compatibilityDate ?? "2025-04-02",
|
|
84
|
-
bundle: {
|
|
85
|
-
...props?.bundle,
|
|
86
|
-
options: {
|
|
87
|
-
...props?.bundle?.options,
|
|
88
|
-
external: [
|
|
89
|
-
"node:events",
|
|
90
|
-
"node:stream",
|
|
91
|
-
...(props?.bundle?.options?.external ?? []),
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
57
|
});
|
|
96
58
|
}
|
|
@@ -45,7 +45,14 @@ export interface WebsiteProps<B extends Bindings>
|
|
|
45
45
|
*
|
|
46
46
|
* @default - no wrangler.jsonc file is written
|
|
47
47
|
*/
|
|
48
|
-
wrangler?:
|
|
48
|
+
wrangler?:
|
|
49
|
+
| boolean
|
|
50
|
+
| string
|
|
51
|
+
| {
|
|
52
|
+
path?: string;
|
|
53
|
+
// override main
|
|
54
|
+
main?: string;
|
|
55
|
+
};
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
export type Website<B extends Bindings> = B extends { ASSETS: any }
|
|
@@ -66,9 +73,17 @@ export async function Website<B extends Bindings>(
|
|
|
66
73
|
|
|
67
74
|
const cwd = path.resolve(props.cwd || process.cwd());
|
|
68
75
|
const fileName =
|
|
69
|
-
typeof props.wrangler === "boolean"
|
|
76
|
+
typeof props.wrangler === "boolean"
|
|
77
|
+
? "wrangler.jsonc"
|
|
78
|
+
: typeof props.wrangler === "string"
|
|
79
|
+
? props.wrangler
|
|
80
|
+
: (props.wrangler?.path ?? "wrangler.jsonc");
|
|
70
81
|
const wranglerPath =
|
|
71
82
|
fileName && path.relative(cwd, path.join(cwd, fileName));
|
|
83
|
+
const wranglerMain =
|
|
84
|
+
typeof props.wrangler === "object"
|
|
85
|
+
? (props.wrangler.main ?? props.main)
|
|
86
|
+
: props.main;
|
|
72
87
|
|
|
73
88
|
if (props.wrangler) {
|
|
74
89
|
try {
|
|
@@ -79,7 +94,7 @@ export async function Website<B extends Bindings>(
|
|
|
79
94
|
JSON.stringify(
|
|
80
95
|
{
|
|
81
96
|
name: id,
|
|
82
|
-
main:
|
|
97
|
+
main: wranglerMain,
|
|
83
98
|
compatibility_date: new Date().toISOString().split("T")[0],
|
|
84
99
|
},
|
|
85
100
|
null,
|
|
@@ -128,6 +143,7 @@ export default {
|
|
|
128
143
|
await WranglerJson("wrangler.jsonc", {
|
|
129
144
|
path: wranglerPath,
|
|
130
145
|
worker,
|
|
146
|
+
main: wranglerMain,
|
|
131
147
|
});
|
|
132
148
|
}
|
|
133
149
|
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type { Bindings, WorkerBindingSpec } from "./bindings.js";
|
|
|
17
17
|
import type { Bound } from "./bound.js";
|
|
18
18
|
import type { DurableObjectNamespace } from "./durable-object-namespace.js";
|
|
19
19
|
import { type EventSource, isQueueEventSource } from "./event-source.js";
|
|
20
|
+
import { external } from "./external.js";
|
|
20
21
|
import {
|
|
21
22
|
QueueConsumer,
|
|
22
23
|
deleteQueueConsumer,
|
|
@@ -576,8 +577,6 @@ async function putWorker(
|
|
|
576
577
|
})
|
|
577
578
|
);
|
|
578
579
|
|
|
579
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
580
|
-
|
|
581
580
|
// Upload worker script with bindings
|
|
582
581
|
const uploadResponse = await api.put(
|
|
583
582
|
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
@@ -591,36 +590,22 @@ async function putWorker(
|
|
|
591
590
|
|
|
592
591
|
// Check if the upload was successful
|
|
593
592
|
if (!uploadResponse.ok) {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (
|
|
601
|
-
uploadResponse.status === 400 &&
|
|
602
|
-
errorMessage.includes("not found")
|
|
603
|
-
) {
|
|
604
|
-
throw new NotFoundError(errorMessage);
|
|
605
|
-
}
|
|
606
|
-
throw new Error(errorMessage);
|
|
593
|
+
await handleApiError(
|
|
594
|
+
uploadResponse,
|
|
595
|
+
"uploading worker script",
|
|
596
|
+
"worker",
|
|
597
|
+
workerName
|
|
598
|
+
);
|
|
607
599
|
}
|
|
608
600
|
|
|
609
601
|
return formData;
|
|
610
602
|
},
|
|
611
|
-
(err) => err
|
|
603
|
+
(err) => err.status === 404 || err.status === 500 || err.status === 503,
|
|
612
604
|
10,
|
|
613
605
|
100
|
|
614
606
|
);
|
|
615
607
|
}
|
|
616
608
|
|
|
617
|
-
class NotFoundError extends Error {
|
|
618
|
-
constructor(message: string) {
|
|
619
|
-
super(message);
|
|
620
|
-
this.name = "NotFoundError";
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
609
|
interface WorkerMetadata {
|
|
625
610
|
compatibility_date: string;
|
|
626
611
|
compatibility_flags?: string[];
|
|
@@ -889,6 +874,9 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
889
874
|
// For service worker format (CJS)
|
|
890
875
|
meta.body_part = scriptName;
|
|
891
876
|
}
|
|
877
|
+
if (process.env.DEBUG) {
|
|
878
|
+
console.log(meta);
|
|
879
|
+
}
|
|
892
880
|
return meta;
|
|
893
881
|
}
|
|
894
882
|
|
|
@@ -931,34 +919,26 @@ async function assertWorkerDoesNotExist<B extends Bindings>(
|
|
|
931
919
|
}
|
|
932
920
|
|
|
933
921
|
async function bundleWorkerScript<B extends Bindings>(props: WorkerProps) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
// Create and use a Bundle resource with worker-optimized configuration
|
|
937
|
-
const defaultBundleOptions: Omit<BundleProps, "entryPoint"> = {
|
|
922
|
+
const bundle = await Bundle("bundle", {
|
|
923
|
+
entryPoint: props.entrypoint!,
|
|
938
924
|
format: props.format === "cjs" ? "cjs" : "esm", // Use the specified format or default to ESM
|
|
939
|
-
target: "
|
|
940
|
-
platform: "
|
|
925
|
+
target: "esnext",
|
|
926
|
+
platform: "neutral",
|
|
941
927
|
minify: true,
|
|
928
|
+
...(props.bundle || {}),
|
|
942
929
|
options: {
|
|
930
|
+
...(props.bundle?.options || {}),
|
|
943
931
|
keepNames: true, // Important for Durable Object classes
|
|
944
932
|
loader: {
|
|
945
933
|
".sql": "text",
|
|
946
934
|
".json": "json",
|
|
947
935
|
},
|
|
948
936
|
},
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
...(props.bundle || {}),
|
|
955
|
-
};
|
|
956
|
-
|
|
957
|
-
// Create the bundle
|
|
958
|
-
const bundle = await Bundle("bundle", {
|
|
959
|
-
entryPoint: props.entrypoint!,
|
|
960
|
-
...bundleOptions,
|
|
961
|
-
external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
|
|
937
|
+
external: [
|
|
938
|
+
...external,
|
|
939
|
+
...(props.bundle?.external ?? []),
|
|
940
|
+
...(props.bundle?.options?.external ?? []),
|
|
941
|
+
],
|
|
962
942
|
});
|
|
963
943
|
|
|
964
944
|
try {
|