alchemy 0.1.12 → 0.1.14
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/README.md +12 -2
- package/lib/$.d.ts +12 -0
- package/lib/$.d.ts.map +1 -0
- package/lib/$.js +42 -0
- package/lib/$.js.map +1 -0
- package/lib/alchemize.d.ts.map +1 -1
- package/lib/alchemize.js +7 -0
- package/lib/alchemize.js.map +1 -1
- package/lib/apply.d.ts +2 -2
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +1 -1
- package/lib/apply.js.map +1 -1
- package/lib/aws/account-id.d.ts +5 -0
- package/lib/aws/account-id.d.ts.map +1 -0
- package/lib/aws/account-id.js +10 -0
- package/lib/aws/account-id.js.map +1 -0
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/oidc/github-oidc-provider.d.ts +11 -0
- package/lib/aws/oidc/github-oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/github-oidc-provider.js +22 -0
- package/lib/aws/oidc/github-oidc-provider.js.map +1 -0
- package/lib/aws/oidc/index.d.ts +3 -0
- package/lib/aws/oidc/index.d.ts.map +1 -0
- package/lib/aws/oidc/index.js +3 -0
- package/lib/aws/oidc/index.js.map +1 -0
- package/lib/aws/oidc/oidc-provider.d.ts +60 -0
- package/lib/aws/oidc/oidc-provider.d.ts.map +1 -0
- package/lib/aws/oidc/oidc-provider.js +140 -0
- package/lib/aws/oidc/oidc-provider.js.map +1 -0
- package/lib/aws/queue.d.ts +24 -0
- package/lib/aws/queue.d.ts.map +1 -1
- package/lib/aws/queue.js +6 -1
- package/lib/aws/queue.js.map +1 -1
- package/lib/aws/role.d.ts.map +1 -1
- package/lib/aws/role.js +49 -1
- package/lib/aws/role.js.map +1 -1
- package/lib/aws/ses.d.ts.map +1 -1
- package/lib/aws/ses.js +20 -28
- package/lib/aws/ses.js.map +1 -1
- package/lib/aws/table.d.ts.map +1 -1
- package/lib/aws/table.js +95 -73
- package/lib/aws/table.js.map +1 -1
- package/lib/cloudflare/api.d.ts +4 -0
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +6 -0
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +19 -4
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js +0 -5
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +20 -0
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -0
- package/lib/cloudflare/durable-object-namespace.js +21 -0
- package/lib/cloudflare/durable-object-namespace.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/kv-namespace.d.ts +3 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -1
- package/lib/cloudflare/kv-namespace.js +5 -1
- package/lib/cloudflare/kv-namespace.js.map +1 -1
- package/lib/cloudflare/static-site-router.d.ts +11 -2
- package/lib/cloudflare/static-site-router.d.ts.map +1 -1
- package/lib/cloudflare/static-site-router.js +98 -76
- package/lib/cloudflare/static-site-router.js.map +1 -1
- package/lib/cloudflare/static-site.d.ts +15 -15
- package/lib/cloudflare/static-site.d.ts.map +1 -1
- package/lib/cloudflare/static-site.js +45 -47
- package/lib/cloudflare/static-site.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +130 -0
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -0
- package/lib/cloudflare/worker-metadata.js +2 -0
- package/lib/cloudflare/worker-metadata.js.map +1 -0
- package/lib/cloudflare/worker-migration.d.ts +110 -0
- package/lib/cloudflare/worker-migration.d.ts.map +1 -0
- package/lib/cloudflare/worker-migration.js +11 -0
- package/lib/cloudflare/worker-migration.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +34 -13
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +341 -321
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/destroy.d.ts +1 -1
- package/lib/destroy.d.ts.map +1 -1
- package/lib/destroy.js +2 -1
- package/lib/destroy.js.map +1 -1
- package/lib/esbuild.d.ts +1 -1
- package/lib/esbuild.d.ts.map +1 -1
- package/lib/esbuild.js +3 -1
- package/lib/esbuild.js.map +1 -1
- package/lib/github/client.d.ts +34 -0
- package/lib/github/client.d.ts.map +1 -0
- package/lib/github/client.js +91 -0
- package/lib/github/client.js.map +1 -0
- package/lib/github/index.d.ts +2 -0
- package/lib/github/index.d.ts.map +1 -0
- package/lib/github/index.js +2 -0
- package/lib/github/index.js.map +1 -0
- package/lib/github/secret.d.ts +46 -0
- package/lib/github/secret.d.ts.map +1 -0
- package/lib/github/secret.js +103 -0
- package/lib/github/secret.js.map +1 -0
- package/lib/global.d.ts +0 -2
- package/lib/global.d.ts.map +1 -1
- package/lib/global.js +0 -2
- package/lib/global.js.map +1 -1
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/output.d.ts +4 -3
- package/lib/output.d.ts.map +1 -1
- package/lib/output.js +10 -3
- package/lib/output.js.map +1 -1
- package/lib/print.d.ts +7 -0
- package/lib/print.d.ts.map +1 -0
- package/lib/print.js +10 -0
- package/lib/print.js.map +1 -0
- package/lib/resource.d.ts +8 -4
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +125 -91
- package/lib/resource.js.map +1 -1
- package/lib/scope.d.ts +3 -0
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +4 -12
- package/lib/scope.js.map +1 -1
- package/lib/utils/retry.d.ts +13 -0
- package/lib/utils/retry.d.ts.map +1 -0
- package/lib/utils/retry.js +31 -0
- package/lib/utils/retry.js.map +1 -0
- package/package.json +12 -3
- package/src/$.ts +69 -0
- package/src/alchemize.ts +12 -0
- package/src/apply.ts +4 -4
- package/src/aws/account-id.ts +11 -0
- package/src/aws/index.ts +1 -0
- package/src/aws/oidc/github-oidc-provider.ts +34 -0
- package/src/aws/oidc/index.ts +2 -0
- package/src/aws/oidc/oidc-provider.ts +253 -0
- package/src/aws/queue.ts +23 -1
- package/src/aws/role.ts +69 -0
- package/src/aws/ses.ts +27 -31
- package/src/aws/table.ts +113 -82
- package/src/cloudflare/api.ts +6 -0
- package/src/cloudflare/bindings.ts +43 -26
- package/src/cloudflare/durable-object-namespace.ts +37 -0
- package/src/cloudflare/index.ts +1 -0
- package/src/cloudflare/kv-namespace.ts +12 -1
- package/src/cloudflare/static-site-router.ts +124 -82
- package/src/cloudflare/static-site.ts +66 -69
- package/src/cloudflare/worker-metadata.ts +154 -0
- package/src/cloudflare/worker-migration.ts +145 -0
- package/src/cloudflare/worker.ts +557 -429
- package/src/destroy.ts +2 -2
- package/src/esbuild.ts +3 -0
- package/src/github/client.ts +115 -0
- package/src/github/index.ts +1 -0
- package/src/github/secret.ts +172 -0
- package/src/global.ts +0 -3
- package/src/index.ts +2 -1
- package/src/output.ts +24 -8
- package/src/print.ts +18 -0
- package/src/resource.ts +156 -115
- package/src/scope.ts +7 -12
- package/src/utils/retry.ts +36 -0
package/src/cloudflare/worker.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import * as fs from "fs/promises";
|
|
2
2
|
import { apply } from "../apply";
|
|
3
3
|
import { Bundle, type BundleProps } from "../esbuild";
|
|
4
|
+
import type { Resolved } from "../output";
|
|
4
5
|
import { type Context, Resource } from "../resource";
|
|
5
|
-
import { createCloudflareApi } from "./api";
|
|
6
|
-
import type {
|
|
7
|
-
|
|
6
|
+
import { type CloudflareApi, createCloudflareApi } from "./api";
|
|
7
|
+
import type { Bindings, WorkerBindingSpec } from "./bindings";
|
|
8
|
+
import type { DurableObjectNamespace } from "./durable-object-namespace";
|
|
9
|
+
import { isDurableObjectNamespace } from "./durable-object-namespace";
|
|
10
|
+
import { isKVNamespace } from "./kv-namespace";
|
|
11
|
+
import type { WorkerScriptMetadata } from "./worker-metadata";
|
|
12
|
+
import type { SingleStepMigration } from "./worker-migration";
|
|
8
13
|
/**
|
|
9
14
|
* Properties for creating or updating a Worker
|
|
10
15
|
*/
|
|
@@ -22,17 +27,11 @@ export interface WorkerProps {
|
|
|
22
27
|
*/
|
|
23
28
|
entrypoint?: string;
|
|
24
29
|
|
|
25
|
-
/**
|
|
26
|
-
* Reference to a pre-configured Bundle resource
|
|
27
|
-
* One of script, entryPoint, or bundle must be provided
|
|
28
|
-
*/
|
|
29
|
-
bundle?: Bundle;
|
|
30
|
-
|
|
31
30
|
/**
|
|
32
31
|
* Bundle options when using entryPoint
|
|
33
32
|
* Ignored if bundle is provided
|
|
34
33
|
*/
|
|
35
|
-
|
|
34
|
+
bundle?: Omit<BundleProps, "entryPoint">;
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Module format for the worker script
|
|
@@ -57,13 +56,15 @@ export interface WorkerProps {
|
|
|
57
56
|
/**
|
|
58
57
|
* Bindings to attach to the worker
|
|
59
58
|
*/
|
|
60
|
-
bindings?:
|
|
59
|
+
bindings?: Bindings;
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* Environment variables to attach to the worker
|
|
63
|
+
* These will be converted to plain_text bindings
|
|
65
64
|
*/
|
|
66
|
-
|
|
65
|
+
env?: {
|
|
66
|
+
[key: string]: string;
|
|
67
|
+
};
|
|
67
68
|
|
|
68
69
|
/**
|
|
69
70
|
* Whether to enable a workers.dev URL for this worker
|
|
@@ -71,12 +72,32 @@ export interface WorkerProps {
|
|
|
71
72
|
* @default false
|
|
72
73
|
*/
|
|
73
74
|
url?: boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Observability configuration for the worker
|
|
78
|
+
* Controls whether worker logs are enabled
|
|
79
|
+
* @default { enabled: true }
|
|
80
|
+
*/
|
|
81
|
+
observability?: {
|
|
82
|
+
/**
|
|
83
|
+
* Whether to enable worker logs
|
|
84
|
+
* @default true
|
|
85
|
+
*/
|
|
86
|
+
enabled?: boolean;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Migrations to apply to the worker
|
|
91
|
+
*/
|
|
92
|
+
migrations?: SingleStepMigration;
|
|
74
93
|
}
|
|
75
94
|
|
|
76
95
|
/**
|
|
77
96
|
* Output returned after Worker creation/update
|
|
78
97
|
*/
|
|
79
98
|
export interface WorkerOutput extends Omit<WorkerProps, "url"> {
|
|
99
|
+
type: "service";
|
|
100
|
+
|
|
80
101
|
/**
|
|
81
102
|
* The ID of the worker
|
|
82
103
|
*/
|
|
@@ -97,472 +118,579 @@ export interface WorkerOutput extends Omit<WorkerProps, "url"> {
|
|
|
97
118
|
* Format: {name}.{subdomain}.workers.dev
|
|
98
119
|
*/
|
|
99
120
|
url?: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The bindings that were created
|
|
124
|
+
*/
|
|
125
|
+
bindings: Bindings;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function isWorker(resource: any): resource is Resolved<Worker> {
|
|
129
|
+
return resource && typeof resource === "object" && resource.type === "worker";
|
|
100
130
|
}
|
|
101
131
|
|
|
102
132
|
export class Worker extends Resource(
|
|
103
133
|
"cloudflare::Worker",
|
|
134
|
+
{
|
|
135
|
+
alwaysUpdate: true,
|
|
136
|
+
},
|
|
104
137
|
async (ctx: Context<WorkerOutput>, props: WorkerProps) => {
|
|
105
138
|
// Create Cloudflare API client with automatic account discovery
|
|
106
139
|
const api = await createCloudflareApi();
|
|
107
140
|
|
|
108
|
-
// Validate that a name is provided
|
|
109
|
-
if (!props.name) {
|
|
110
|
-
throw new Error("Worker name is required - must be explicitly specified");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
141
|
// Use the provided name
|
|
114
142
|
const workerName = props.name;
|
|
115
143
|
|
|
116
144
|
// Validate input - we need either script, entryPoint, or bundle
|
|
117
|
-
if (!props.script && !props.entrypoint
|
|
118
|
-
throw new Error("One of script
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// For create operations, check if a worker with the same name already exists
|
|
122
|
-
if (ctx.event === "create") {
|
|
123
|
-
const checkExistsResponse = await api.get(
|
|
124
|
-
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
if (checkExistsResponse.ok) {
|
|
128
|
-
throw new Error(
|
|
129
|
-
`Worker with name '${workerName}' already exists. Please use a unique name.`,
|
|
130
|
-
);
|
|
131
|
-
} else if (checkExistsResponse.status !== 404) {
|
|
132
|
-
// If it's neither a success nor a 404, it's an unexpected error
|
|
133
|
-
const errorData: any = await checkExistsResponse.json().catch(() => ({
|
|
134
|
-
errors: [{ message: checkExistsResponse.statusText }],
|
|
135
|
-
}));
|
|
136
|
-
throw new Error(
|
|
137
|
-
`Error checking if worker exists: ${errorData.errors?.[0]?.message || checkExistsResponse.statusText}`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
// If it's a 404, that means the worker doesn't exist yet, which is what we want
|
|
145
|
+
if (!props.script && !props.entrypoint) {
|
|
146
|
+
throw new Error("One of script or entryPoint must be provided");
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
if (ctx.event === "delete") {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
return deleteWorker(ctx, api, workerName);
|
|
151
|
+
} else if (ctx.event === "create") {
|
|
152
|
+
await assertWorkerDoesNotExist(ctx, api, workerName);
|
|
153
|
+
}
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
const oldBindings = await ctx.get<Bindings>("bindings");
|
|
156
|
+
|
|
157
|
+
const scriptMetadata = await prepareWorkerMetadata(ctx, oldBindings, props);
|
|
158
|
+
|
|
159
|
+
// Get the script content - either from props.script, or by bundling
|
|
160
|
+
const scriptContent =
|
|
161
|
+
props.script ?? (await bundleWorkerScript(ctx, props));
|
|
162
|
+
|
|
163
|
+
// Upload the worker script
|
|
164
|
+
await putWorker(ctx, api, workerName, scriptContent, scriptMetadata);
|
|
165
|
+
|
|
166
|
+
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
167
|
+
await ctx.set("bindings", props.bindings);
|
|
168
|
+
|
|
169
|
+
// Handle routes if requested
|
|
170
|
+
await setupRoutes(api, workerName, props.routes || []);
|
|
171
|
+
|
|
172
|
+
// Handle worker URL if requested
|
|
173
|
+
const workerUrl = await configureURL(
|
|
174
|
+
ctx,
|
|
175
|
+
api,
|
|
176
|
+
workerName,
|
|
177
|
+
props.url ?? false,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Get current timestamp
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
|
|
183
|
+
// Construct the output
|
|
184
|
+
const output: WorkerOutput = {
|
|
185
|
+
type: "service",
|
|
186
|
+
id: workerName,
|
|
187
|
+
name: workerName,
|
|
188
|
+
script: scriptContent,
|
|
189
|
+
format: props.format || "esm", // Include format in the output
|
|
190
|
+
routes: props.routes || [],
|
|
191
|
+
bindings: props.bindings ?? {},
|
|
192
|
+
env: props.env,
|
|
193
|
+
observability: scriptMetadata.observability,
|
|
194
|
+
createdAt: now,
|
|
195
|
+
updatedAt: now,
|
|
196
|
+
url: workerUrl,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
return output;
|
|
200
|
+
},
|
|
201
|
+
) {}
|
|
159
202
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
async function deleteWorker(
|
|
204
|
+
ctx: Context<WorkerOutput>,
|
|
205
|
+
api: CloudflareApi,
|
|
206
|
+
workerName: string,
|
|
207
|
+
) {
|
|
208
|
+
// Delete worker
|
|
209
|
+
const deleteResponse = await api.delete(
|
|
210
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
// Check for success (2xx status code)
|
|
214
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
215
|
+
const errorData: any = await deleteResponse
|
|
216
|
+
.json()
|
|
217
|
+
.catch(() => ({ errors: [{ message: deleteResponse.statusText }] }));
|
|
218
|
+
console.error(
|
|
219
|
+
"Error deleting worker:",
|
|
220
|
+
errorData.errors?.[0]?.message || deleteResponse.statusText,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Also remove any associated routes if they exist
|
|
225
|
+
if (ctx.output?.routes && ctx.output.routes.length > 0 && api.zoneId) {
|
|
226
|
+
// First get existing routes to find their IDs
|
|
227
|
+
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
228
|
+
|
|
229
|
+
if (!routesResponse.ok) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Could not fetch routes for cleanup: ${routesResponse.status} ${routesResponse.statusText}`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
const routesData: any = await routesResponse.json();
|
|
235
|
+
const existingRoutes = routesData.result || [];
|
|
236
|
+
|
|
237
|
+
for (const route of existingRoutes) {
|
|
238
|
+
if (ctx.output.routes.includes(route.pattern)) {
|
|
239
|
+
// Delete the route
|
|
240
|
+
const routeDeleteResponse = await api.delete(
|
|
241
|
+
`/zones/${api.zoneId}/workers/routes/${route.id}`,
|
|
165
242
|
);
|
|
166
243
|
|
|
167
|
-
if (
|
|
168
|
-
const routesData: any = await routesResponse.json();
|
|
169
|
-
const existingRoutes = routesData.result || [];
|
|
170
|
-
|
|
171
|
-
for (const route of existingRoutes) {
|
|
172
|
-
if (ctx.output.routes.includes(route.pattern)) {
|
|
173
|
-
// Delete the route
|
|
174
|
-
const routeDeleteResponse = await api.delete(
|
|
175
|
-
`/zones/${api.zoneId}/workers/routes/${route.id}`,
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
if (!routeDeleteResponse.ok) {
|
|
179
|
-
console.warn(
|
|
180
|
-
`Failed to delete route ${route.pattern}: ${routeDeleteResponse.status} ${routeDeleteResponse.statusText}`,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} else {
|
|
244
|
+
if (!routeDeleteResponse.ok) {
|
|
186
245
|
console.warn(
|
|
187
|
-
|
|
188
|
-
routesResponse.status,
|
|
189
|
-
routesResponse.statusText,
|
|
246
|
+
`Failed to delete route ${route.pattern}: ${routeDeleteResponse.status} ${routeDeleteResponse.statusText}`,
|
|
190
247
|
);
|
|
191
248
|
}
|
|
192
249
|
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Disable the URL if it was enabled
|
|
254
|
+
if (ctx.output?.url) {
|
|
255
|
+
try {
|
|
256
|
+
await api.post(
|
|
257
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
258
|
+
JSON.stringify({ enabled: false }),
|
|
259
|
+
{
|
|
260
|
+
headers: { "Content-Type": "application/json" },
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.warn("Failed to disable worker URL during deletion:", error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
193
267
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
await api.post(
|
|
198
|
-
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
199
|
-
JSON.stringify({ enabled: false }),
|
|
200
|
-
{
|
|
201
|
-
headers: { "Content-Type": "application/json" },
|
|
202
|
-
},
|
|
203
|
-
);
|
|
204
|
-
} catch (error) {
|
|
205
|
-
console.warn("Failed to disable worker URL during deletion:", error);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Return minimal output for deleted state
|
|
210
|
-
return {
|
|
211
|
-
...props,
|
|
212
|
-
id: "",
|
|
213
|
-
createdAt: 0,
|
|
214
|
-
updatedAt: 0,
|
|
215
|
-
url: undefined,
|
|
216
|
-
};
|
|
217
|
-
} else {
|
|
218
|
-
// Get the script content - either from props.script, or by bundling
|
|
219
|
-
let scriptContent: string;
|
|
220
|
-
|
|
221
|
-
if (props.script) {
|
|
222
|
-
// Use the provided script
|
|
223
|
-
scriptContent = props.script;
|
|
224
|
-
} else if (props.bundle) {
|
|
225
|
-
// Use a pre-configured Bundle resource
|
|
226
|
-
const bundleOutput = await apply(props.bundle);
|
|
227
|
-
scriptContent = await fs.readFile(bundleOutput.path, "utf-8");
|
|
228
|
-
} else if (props.entrypoint) {
|
|
229
|
-
// Create and use a Bundle resource with worker-optimized configuration
|
|
230
|
-
const defaultBundleOptions: Omit<BundleProps, "entryPoint"> = {
|
|
231
|
-
format: props.format === "cjs" ? "cjs" : "esm", // Use the specified format or default to ESM
|
|
232
|
-
target: "es2020",
|
|
233
|
-
platform: "browser",
|
|
234
|
-
minify: true,
|
|
235
|
-
options: {
|
|
236
|
-
keepNames: true, // Important for Durable Object classes
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
// Merge with user-provided options
|
|
241
|
-
const bundleOptions = {
|
|
242
|
-
...defaultBundleOptions,
|
|
243
|
-
...(props.bundleOptions || {}),
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Create the bundle
|
|
247
|
-
const bundle = new Bundle("bundle", {
|
|
248
|
-
entryPoint: props.entrypoint,
|
|
249
|
-
...bundleOptions,
|
|
250
|
-
});
|
|
268
|
+
// Return minimal output for deleted state
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
251
271
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
272
|
+
async function putWorker(
|
|
273
|
+
ctx: Context<WorkerOutput>,
|
|
274
|
+
api: CloudflareApi,
|
|
275
|
+
workerName: string,
|
|
276
|
+
scriptContent: string,
|
|
277
|
+
scriptMetadata: WorkerMetadata,
|
|
278
|
+
) {
|
|
279
|
+
const scriptName = scriptMetadata.main_module ?? scriptMetadata.body_part!;
|
|
280
|
+
|
|
281
|
+
// Create FormData for the upload
|
|
282
|
+
const formData = new FormData();
|
|
283
|
+
|
|
284
|
+
// Add the actual script content as a named file part
|
|
285
|
+
formData.append(
|
|
286
|
+
scriptName,
|
|
287
|
+
new Blob([scriptContent], {
|
|
288
|
+
type: scriptMetadata.main_module
|
|
289
|
+
? "application/javascript+module"
|
|
290
|
+
: "application/javascript",
|
|
291
|
+
}),
|
|
292
|
+
scriptName,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// console.log("Script Metadata:", JSON.stringify(scriptMetadata, null, 2));
|
|
296
|
+
|
|
297
|
+
// Add metadata as JSON
|
|
298
|
+
formData.append(
|
|
299
|
+
"metadata",
|
|
300
|
+
new Blob([JSON.stringify(scriptMetadata)], {
|
|
301
|
+
type: "application/json",
|
|
302
|
+
}),
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Upload worker script with bindings
|
|
306
|
+
const uploadResponse = await api.put(
|
|
307
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
308
|
+
formData,
|
|
309
|
+
{
|
|
310
|
+
headers: {
|
|
311
|
+
"Content-Type": "multipart/form-data",
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// Check if the upload was successful
|
|
317
|
+
if (!uploadResponse.ok) {
|
|
318
|
+
const errorData: any = await uploadResponse
|
|
319
|
+
.json()
|
|
320
|
+
.catch(() => ({ errors: [{ message: uploadResponse.statusText }] }));
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Error uploading worker script '${workerName}': ${errorData.errors?.[0]?.message || uploadResponse.statusText}`,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return formData;
|
|
327
|
+
}
|
|
267
328
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
case "kv_namespace":
|
|
279
|
-
bindingData.namespace_id = (binding as any).namespace_id;
|
|
280
|
-
break;
|
|
281
|
-
case "durable_object_namespace":
|
|
282
|
-
bindingData.class_name = (binding as any).class_name;
|
|
283
|
-
if ((binding as any).namespace_id) {
|
|
284
|
-
bindingData.namespace_id = (binding as any).namespace_id;
|
|
285
|
-
}
|
|
286
|
-
if ((binding as any).script_name) {
|
|
287
|
-
bindingData.script_name = (binding as any).script_name;
|
|
288
|
-
}
|
|
289
|
-
if ((binding as any).environment) {
|
|
290
|
-
bindingData.environment = (binding as any).environment;
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
case "secret_text":
|
|
294
|
-
bindingData.text = (binding as any).text;
|
|
295
|
-
break;
|
|
296
|
-
case "r2_bucket":
|
|
297
|
-
bindingData.bucket_name = (binding as any).bucket_name;
|
|
298
|
-
break;
|
|
299
|
-
case "analytics_engine":
|
|
300
|
-
bindingData.dataset = (binding as any).dataset;
|
|
301
|
-
break;
|
|
302
|
-
case "service":
|
|
303
|
-
bindingData.service = (binding as any).service;
|
|
304
|
-
if ((binding as any).environment) {
|
|
305
|
-
bindingData.environment = (binding as any).environment;
|
|
306
|
-
}
|
|
307
|
-
if ((binding as any).namespace) {
|
|
308
|
-
bindingData.namespace = (binding as any).namespace;
|
|
309
|
-
}
|
|
310
|
-
break;
|
|
311
|
-
case "d1":
|
|
312
|
-
bindingData.id = (binding as any).id;
|
|
313
|
-
break;
|
|
314
|
-
case "queue":
|
|
315
|
-
bindingData.queue_name = (binding as any).queue_name;
|
|
316
|
-
break;
|
|
317
|
-
case "json":
|
|
318
|
-
bindingData.json = (binding as any).json;
|
|
319
|
-
break;
|
|
320
|
-
case "hyperdrive":
|
|
321
|
-
bindingData.id = (binding as any).id;
|
|
322
|
-
break;
|
|
323
|
-
case "vectorize":
|
|
324
|
-
bindingData.index_name = (binding as any).index_name;
|
|
325
|
-
break;
|
|
326
|
-
case "dispatch_namespace":
|
|
327
|
-
bindingData.namespace = (binding as any).namespace;
|
|
328
|
-
if ((binding as any).outbound) {
|
|
329
|
-
bindingData.outbound = (binding as any).outbound;
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
case "mtls_certificate":
|
|
333
|
-
bindingData.certificate_id = (binding as any).certificate_id;
|
|
334
|
-
break;
|
|
335
|
-
case "plain_text":
|
|
336
|
-
bindingData.text = (binding as any).text;
|
|
337
|
-
break;
|
|
338
|
-
case "wasm_module":
|
|
339
|
-
bindingData.module = (binding as any).module;
|
|
340
|
-
break;
|
|
341
|
-
case "tail_consumer":
|
|
342
|
-
bindingData.service = (binding as any).service;
|
|
343
|
-
break;
|
|
344
|
-
}
|
|
329
|
+
interface WorkerMetadata {
|
|
330
|
+
bindings: WorkerBindingSpec[];
|
|
331
|
+
observability: {
|
|
332
|
+
enabled: boolean;
|
|
333
|
+
};
|
|
334
|
+
migrations?: SingleStepMigration;
|
|
335
|
+
main_module?: string;
|
|
336
|
+
body_part?: string;
|
|
337
|
+
tags?: string[];
|
|
338
|
+
}
|
|
345
339
|
|
|
346
|
-
|
|
340
|
+
async function prepareWorkerMetadata(
|
|
341
|
+
ctx: Context<WorkerOutput>,
|
|
342
|
+
oldBindings: Bindings | undefined,
|
|
343
|
+
props: WorkerProps,
|
|
344
|
+
): Promise<WorkerMetadata> {
|
|
345
|
+
// Prepare metadata with bindings
|
|
346
|
+
const meta: WorkerMetadata = {
|
|
347
|
+
bindings: [],
|
|
348
|
+
observability: {
|
|
349
|
+
enabled: props.observability?.enabled !== false,
|
|
350
|
+
},
|
|
351
|
+
tags: [`alchemy:id:${ctx.resourceFQN}`],
|
|
352
|
+
migrations: {
|
|
353
|
+
new_classes: props.migrations?.new_classes ?? [],
|
|
354
|
+
deleted_classes: props.migrations?.deleted_classes ?? [],
|
|
355
|
+
renamed_classes: props.migrations?.renamed_classes ?? [],
|
|
356
|
+
transferred_classes: props.migrations?.transferred_classes ?? [],
|
|
357
|
+
new_sqlite_classes: props.migrations?.new_sqlite_classes ?? [],
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const bindings = (props.bindings ?? {}) as Resolved<Bindings>;
|
|
362
|
+
|
|
363
|
+
// Convert bindings to the format expected by the API
|
|
364
|
+
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
365
|
+
// Create a copy of the binding to avoid modifying the original
|
|
366
|
+
|
|
367
|
+
if (isKVNamespace(binding)) {
|
|
368
|
+
meta.bindings.push({
|
|
369
|
+
type: "kv_namespace",
|
|
370
|
+
name: bindingName,
|
|
371
|
+
namespace_id: binding.id,
|
|
372
|
+
});
|
|
373
|
+
} else if (typeof binding === "string") {
|
|
374
|
+
meta.bindings.push({
|
|
375
|
+
type: "plain_text",
|
|
376
|
+
name: bindingName,
|
|
377
|
+
text: binding,
|
|
378
|
+
});
|
|
379
|
+
} else if (binding.type === "service") {
|
|
380
|
+
meta.bindings.push({
|
|
381
|
+
type: "service",
|
|
382
|
+
name: bindingName,
|
|
383
|
+
service: binding.id,
|
|
384
|
+
});
|
|
385
|
+
} else if (isDurableObjectNamespace(binding)) {
|
|
386
|
+
const stableId = binding.id;
|
|
387
|
+
const className = binding.className;
|
|
388
|
+
|
|
389
|
+
meta.bindings.push({
|
|
390
|
+
type: "durable_object_namespace",
|
|
391
|
+
name: bindingName,
|
|
392
|
+
class_name: className,
|
|
393
|
+
script_name: binding.scriptName,
|
|
394
|
+
environment: binding.environment,
|
|
395
|
+
namespace_id: binding.namespaceId,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const oldBinding: DurableObjectNamespace | undefined = Object.values(
|
|
399
|
+
oldBindings ?? {},
|
|
400
|
+
)
|
|
401
|
+
?.filter(isDurableObjectNamespace)
|
|
402
|
+
?.find((b) => b.id === stableId);
|
|
403
|
+
|
|
404
|
+
if (!oldBinding) {
|
|
405
|
+
if (binding.sqlite) {
|
|
406
|
+
meta.migrations!.new_sqlite_classes!.push(className);
|
|
407
|
+
} else {
|
|
408
|
+
meta.migrations!.new_classes!.push(className);
|
|
347
409
|
}
|
|
410
|
+
} else if (oldBinding.className !== className) {
|
|
411
|
+
meta.migrations!.renamed_classes!.push({
|
|
412
|
+
from: oldBinding.className,
|
|
413
|
+
to: className,
|
|
414
|
+
});
|
|
348
415
|
}
|
|
416
|
+
} else {
|
|
417
|
+
// @ts-expect-error - we should never reach here
|
|
418
|
+
throw new Error(`Unsupported binding type: ${binding.type}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Convert env variables to plain_text bindings
|
|
423
|
+
if (props.env) {
|
|
424
|
+
for (const [key, value] of Object.entries(props.env)) {
|
|
425
|
+
meta.bindings.push({
|
|
426
|
+
name: key,
|
|
427
|
+
type: "plain_text",
|
|
428
|
+
text: value,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Determine if we're using ESM or service worker format
|
|
434
|
+
const isEsModule = props.format !== "cjs"; // Default to ESM unless CJS is specified
|
|
435
|
+
const scriptName = isEsModule ? "worker.js" : "script";
|
|
436
|
+
|
|
437
|
+
if (isEsModule) {
|
|
438
|
+
// For ES modules format
|
|
439
|
+
meta.main_module = scriptName;
|
|
440
|
+
} else {
|
|
441
|
+
// For service worker format (CJS)
|
|
442
|
+
meta.body_part = scriptName;
|
|
443
|
+
}
|
|
444
|
+
return meta;
|
|
445
|
+
}
|
|
349
446
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
(binding) =>
|
|
368
|
-
binding.type === "durable_object_namespace" &&
|
|
369
|
-
// Only include self-referencing DOs (ones defined in this script)
|
|
370
|
-
(binding as any).script_name === props.name,
|
|
447
|
+
async function assertWorkerDoesNotExist(
|
|
448
|
+
ctx: Context<WorkerOutput>,
|
|
449
|
+
api: CloudflareApi,
|
|
450
|
+
workerName: string,
|
|
451
|
+
) {
|
|
452
|
+
const response = await api.get(
|
|
453
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}`,
|
|
454
|
+
);
|
|
455
|
+
if (response.status === 404) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
if (response.status === 200) {
|
|
459
|
+
const metadata = await getWorkerScriptMetadata(api, workerName);
|
|
460
|
+
|
|
461
|
+
if (!metadata) {
|
|
462
|
+
throw new Error(
|
|
463
|
+
`Worker exists but failed to fetch metadata: ${response.status} ${response.statusText}`,
|
|
371
464
|
);
|
|
465
|
+
}
|
|
372
466
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
renamed_classes: [],
|
|
381
|
-
// Version 2 migrations
|
|
382
|
-
new_sqlite_classes: [],
|
|
383
|
-
updated_sqlite_classes: [],
|
|
384
|
-
renamed_sqlite_classes: [],
|
|
385
|
-
};
|
|
386
|
-
}
|
|
467
|
+
if (
|
|
468
|
+
metadata.default_environment?.script.tags.includes(
|
|
469
|
+
`alchemy:id:${ctx.resourceFQN}`,
|
|
470
|
+
)
|
|
471
|
+
) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
387
474
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
475
|
+
throw new Error(
|
|
476
|
+
`Worker with name '${workerName}' already exists. Please use a unique name.`,
|
|
477
|
+
);
|
|
478
|
+
} else {
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Error checking if worker exists: ${response.status} ${response.statusText}`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
395
484
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
485
|
+
async function bundleWorkerScript(
|
|
486
|
+
ctx: Context<WorkerOutput>,
|
|
487
|
+
props: WorkerProps,
|
|
488
|
+
) {
|
|
489
|
+
// Get the script content - either from props.script, or by bundling
|
|
490
|
+
|
|
491
|
+
// Create and use a Bundle resource with worker-optimized configuration
|
|
492
|
+
const defaultBundleOptions: Omit<BundleProps, "entryPoint"> = {
|
|
493
|
+
format: props.format === "cjs" ? "cjs" : "esm", // Use the specified format or default to ESM
|
|
494
|
+
target: "es2020",
|
|
495
|
+
platform: "browser",
|
|
496
|
+
minify: true,
|
|
497
|
+
options: {
|
|
498
|
+
keepNames: true, // Important for Durable Object classes
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
// Merge with user-provided options
|
|
503
|
+
const bundleOptions = {
|
|
504
|
+
...defaultBundleOptions,
|
|
505
|
+
...(props.bundle || {}),
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
// Create the bundle
|
|
509
|
+
const bundle = new Bundle("bundle", {
|
|
510
|
+
entryPoint: props.entrypoint!,
|
|
511
|
+
...bundleOptions,
|
|
512
|
+
external: [...(bundleOptions.external ?? []), "cloudflare:workers"],
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Get the bundled content
|
|
516
|
+
const bundleOutput = await apply(bundle, {
|
|
517
|
+
quiet: ctx.quiet,
|
|
518
|
+
});
|
|
519
|
+
try {
|
|
520
|
+
return await fs.readFile(bundleOutput.path, "utf-8");
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.error("Error reading bundle:", error);
|
|
523
|
+
throw new Error("Error reading bundle");
|
|
524
|
+
}
|
|
525
|
+
}
|
|
402
526
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
527
|
+
async function setupRoutes(
|
|
528
|
+
api: CloudflareApi,
|
|
529
|
+
workerName: string,
|
|
530
|
+
routes: string[],
|
|
531
|
+
) {
|
|
532
|
+
// Set up routes if provided
|
|
533
|
+
if (routes && routes.length > 0 && api.zoneId) {
|
|
534
|
+
// First get existing routes
|
|
535
|
+
const routesResponse = await api.get(`/zones/${api.zoneId}/workers/routes`);
|
|
536
|
+
|
|
537
|
+
if (!routesResponse.ok) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Could not fetch routes: ${routesResponse.status} ${routesResponse.statusText}`,
|
|
409
540
|
);
|
|
541
|
+
}
|
|
542
|
+
const routesData: any = await routesResponse.json();
|
|
543
|
+
const existingRoutes = routesData.result || [];
|
|
410
544
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
545
|
+
// For each desired route
|
|
546
|
+
for (const pattern of routes) {
|
|
547
|
+
const existingRoute = existingRoutes.find(
|
|
548
|
+
(r: any) => r.pattern === pattern,
|
|
415
549
|
);
|
|
416
550
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
throw new Error(
|
|
423
|
-
`Error uploading worker script: ${errorData.errors?.[0]?.message || uploadResponse.statusText}`,
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Set up routes if provided
|
|
428
|
-
if (props.routes && props.routes.length > 0 && api.zoneId) {
|
|
429
|
-
// First get existing routes
|
|
430
|
-
const routesResponse = await api.get(
|
|
431
|
-
`/zones/${api.zoneId}/workers/routes`,
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
if (routesResponse.ok) {
|
|
435
|
-
const routesData: any = await routesResponse.json();
|
|
436
|
-
const existingRoutes = routesData.result || [];
|
|
437
|
-
|
|
438
|
-
// For each desired route
|
|
439
|
-
for (const pattern of props.routes) {
|
|
440
|
-
const existingRoute = existingRoutes.find(
|
|
441
|
-
(r: any) => r.pattern === pattern,
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
if (existingRoute) {
|
|
445
|
-
// Update if script name doesn't match
|
|
446
|
-
if (existingRoute.script !== workerName) {
|
|
447
|
-
const updateRouteResponse = await api.put(
|
|
448
|
-
`/zones/${api.zoneId}/workers/routes/${existingRoute.id}`,
|
|
449
|
-
{
|
|
450
|
-
pattern,
|
|
451
|
-
script: workerName,
|
|
452
|
-
},
|
|
453
|
-
);
|
|
454
|
-
|
|
455
|
-
if (!updateRouteResponse.ok) {
|
|
456
|
-
console.warn(
|
|
457
|
-
`Failed to update route ${pattern}: ${updateRouteResponse.status} ${updateRouteResponse.statusText}`,
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} else {
|
|
462
|
-
// Create new route
|
|
463
|
-
const createRouteResponse = await api.post(
|
|
464
|
-
`/zones/${api.zoneId}/workers/routes`,
|
|
465
|
-
{
|
|
466
|
-
pattern,
|
|
467
|
-
script: workerName,
|
|
468
|
-
},
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
if (!createRouteResponse.ok) {
|
|
472
|
-
console.warn(
|
|
473
|
-
`Failed to create route ${pattern}: ${createRouteResponse.status} ${createRouteResponse.statusText}`,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
} else {
|
|
479
|
-
console.warn(
|
|
480
|
-
"Could not fetch routes:",
|
|
481
|
-
routesResponse.status,
|
|
482
|
-
routesResponse.statusText,
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Handle worker URL if requested
|
|
488
|
-
let workerUrl;
|
|
489
|
-
if (props.url) {
|
|
490
|
-
try {
|
|
491
|
-
// Enable the workers.dev subdomain for this worker
|
|
492
|
-
await api.post(
|
|
493
|
-
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
494
|
-
{ enabled: true, previews_enabled: true },
|
|
551
|
+
if (existingRoute) {
|
|
552
|
+
// Update if script name doesn't match
|
|
553
|
+
if (existingRoute.script !== workerName) {
|
|
554
|
+
const updateRouteResponse = await api.put(
|
|
555
|
+
`/zones/${api.zoneId}/workers/routes/${existingRoute.id}`,
|
|
495
556
|
{
|
|
496
|
-
|
|
557
|
+
pattern,
|
|
558
|
+
script: workerName,
|
|
497
559
|
},
|
|
498
560
|
);
|
|
499
561
|
|
|
500
|
-
|
|
501
|
-
const subdomainResponse = await api.get(
|
|
502
|
-
`/accounts/${api.accountId}/workers/subdomain`,
|
|
503
|
-
);
|
|
504
|
-
|
|
505
|
-
if (subdomainResponse.ok) {
|
|
506
|
-
const subdomainData: {
|
|
507
|
-
result: {
|
|
508
|
-
subdomain: string;
|
|
509
|
-
};
|
|
510
|
-
} = await subdomainResponse.json();
|
|
511
|
-
const subdomain = subdomainData.result?.subdomain;
|
|
512
|
-
|
|
513
|
-
if (subdomain) {
|
|
514
|
-
workerUrl = `${workerName}.${subdomain}.workers.dev`;
|
|
515
|
-
|
|
516
|
-
// Add a delay when the subdomain is first created.
|
|
517
|
-
// This is to prevent an issue where a negative cache-hit
|
|
518
|
-
// causes the subdomain to be unavailable for 30 seconds.
|
|
519
|
-
if (ctx.event === "create" || !ctx.output?.url) {
|
|
520
|
-
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
} else {
|
|
562
|
+
if (!updateRouteResponse.ok) {
|
|
524
563
|
console.warn(
|
|
525
|
-
|
|
526
|
-
subdomainResponse.status,
|
|
527
|
-
subdomainResponse.statusText,
|
|
564
|
+
`Failed to update route ${pattern}: ${updateRouteResponse.status} ${updateRouteResponse.statusText}`,
|
|
528
565
|
);
|
|
529
566
|
}
|
|
530
|
-
} catch (error) {
|
|
531
|
-
console.warn("Failed to enable worker URL:", error);
|
|
532
567
|
}
|
|
533
|
-
} else
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
568
|
+
} else {
|
|
569
|
+
// Create new route
|
|
570
|
+
const createRouteResponse = await api.post(
|
|
571
|
+
`/zones/${api.zoneId}/workers/routes`,
|
|
572
|
+
{
|
|
573
|
+
pattern,
|
|
574
|
+
script: workerName,
|
|
575
|
+
},
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
if (!createRouteResponse.ok) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
`Failed to create route ${pattern}: ${createRouteResponse.status} ${createRouteResponse.statusText}`,
|
|
542
581
|
);
|
|
543
|
-
} catch (error) {
|
|
544
|
-
console.warn("Failed to disable worker URL:", error);
|
|
545
582
|
}
|
|
546
583
|
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
547
587
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
588
|
+
async function configureURL(
|
|
589
|
+
ctx: Context<WorkerOutput>,
|
|
590
|
+
api: CloudflareApi,
|
|
591
|
+
workerName: string,
|
|
592
|
+
url: boolean,
|
|
593
|
+
) {
|
|
594
|
+
let workerUrl;
|
|
595
|
+
if (url) {
|
|
596
|
+
// Enable the workers.dev subdomain for this worker
|
|
597
|
+
await api.post(
|
|
598
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
599
|
+
{ enabled: true, previews_enabled: true },
|
|
600
|
+
{
|
|
601
|
+
headers: { "Content-Type": "application/json" },
|
|
602
|
+
},
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
// Get the account's workers.dev subdomain
|
|
606
|
+
const subdomainResponse = await api.get(
|
|
607
|
+
`/accounts/${api.accountId}/workers/subdomain`,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
if (!subdomainResponse.ok) {
|
|
611
|
+
throw new Error(
|
|
612
|
+
`Could not fetch workers.dev subdomain: ${subdomainResponse.status} ${subdomainResponse.statusText}`,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const subdomainData: {
|
|
616
|
+
result: {
|
|
617
|
+
subdomain: string;
|
|
563
618
|
};
|
|
619
|
+
} = await subdomainResponse.json();
|
|
620
|
+
const subdomain = subdomainData.result?.subdomain;
|
|
564
621
|
|
|
565
|
-
|
|
622
|
+
if (subdomain) {
|
|
623
|
+
workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
|
|
624
|
+
|
|
625
|
+
// Add a delay when the subdomain is first created.
|
|
626
|
+
// This is to prevent an issue where a negative cache-hit
|
|
627
|
+
// causes the subdomain to be unavailable for 30 seconds.
|
|
628
|
+
if (ctx.event === "create" || !ctx.output?.url) {
|
|
629
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
630
|
+
}
|
|
566
631
|
}
|
|
567
|
-
}
|
|
568
|
-
|
|
632
|
+
} else if (url === false && ctx.output?.url) {
|
|
633
|
+
// Explicitly disable URL if it was previously enabled
|
|
634
|
+
const response = await api.post(
|
|
635
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`,
|
|
636
|
+
JSON.stringify({ enabled: false }),
|
|
637
|
+
{
|
|
638
|
+
headers: { "Content-Type": "application/json" },
|
|
639
|
+
},
|
|
640
|
+
);
|
|
641
|
+
if (!response.ok) {
|
|
642
|
+
throw new Error(
|
|
643
|
+
`Failed to disable worker URL: ${response.status} ${response.statusText}`,
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return workerUrl;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
async function getWorkerScriptMetadata(
|
|
651
|
+
api: CloudflareApi,
|
|
652
|
+
workerName: string,
|
|
653
|
+
): Promise<WorkerScriptMetadata | undefined> {
|
|
654
|
+
const response = await api.get(
|
|
655
|
+
`/accounts/${api.accountId}/workers/services/${workerName}`,
|
|
656
|
+
);
|
|
657
|
+
if (response.status === 404) {
|
|
658
|
+
return undefined;
|
|
659
|
+
}
|
|
660
|
+
if (!response.ok) {
|
|
661
|
+
throw new Error(
|
|
662
|
+
`Error getting worker script metadata: ${response.status} ${response.statusText}`,
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
return ((await response.json()) as any).result as WorkerScriptMetadata;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function getWorkerBindings(
|
|
669
|
+
api: CloudflareApi,
|
|
670
|
+
workerName: string,
|
|
671
|
+
environment = "production",
|
|
672
|
+
) {
|
|
673
|
+
const response = await api.get(
|
|
674
|
+
`/accounts/${api.accountId}/workers/services/${workerName}/environments/${environment}/bindings`,
|
|
675
|
+
{
|
|
676
|
+
headers: {
|
|
677
|
+
Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
|
|
678
|
+
"Content-Type": "application/json",
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
);
|
|
682
|
+
|
|
683
|
+
if (response.status === 404) {
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (!response.ok) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`Failed to fetch bindings: ${response.status} ${response.statusText}`,
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const data: any = await response.json();
|
|
694
|
+
|
|
695
|
+
return data.result;
|
|
696
|
+
}
|