alchemy 0.42.1 → 0.43.1

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.
Files changed (77) hide show
  1. package/bin/alchemy.mjs +508 -316
  2. package/lib/alchemy.d.ts.map +1 -1
  3. package/lib/alchemy.js +8 -2
  4. package/lib/alchemy.js.map +1 -1
  5. package/lib/build-date.d.ts +1 -1
  6. package/lib/build-date.js +1 -1
  7. package/lib/cloudflare/astro.d.ts +1 -1
  8. package/lib/cloudflare/astro.d.ts.map +1 -1
  9. package/lib/cloudflare/astro.js +1 -1
  10. package/lib/cloudflare/astro.js.map +1 -1
  11. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +1 -0
  12. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
  13. package/lib/cloudflare/bundle/bundle-worker-dev.js +2 -3
  14. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
  15. package/lib/cloudflare/bundle/bundle-worker.d.ts +1 -0
  16. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
  17. package/lib/cloudflare/bundle/bundle-worker.js +2 -3
  18. package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
  19. package/lib/cloudflare/d1-database.d.ts.map +1 -1
  20. package/lib/cloudflare/d1-database.js +17 -0
  21. package/lib/cloudflare/d1-database.js.map +1 -1
  22. package/lib/cloudflare/d1-migrations.d.ts +5 -2
  23. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  24. package/lib/cloudflare/d1-migrations.js +166 -14
  25. package/lib/cloudflare/d1-migrations.js.map +1 -1
  26. package/lib/cloudflare/website.d.ts.map +1 -1
  27. package/lib/cloudflare/website.js +11 -22
  28. package/lib/cloudflare/website.js.map +1 -1
  29. package/lib/cloudflare/worker/http-server.d.ts.map +1 -1
  30. package/lib/cloudflare/worker/http-server.js +2 -1
  31. package/lib/cloudflare/worker/http-server.js.map +1 -1
  32. package/lib/cloudflare/worker/miniflare.d.ts +1 -0
  33. package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
  34. package/lib/cloudflare/worker/miniflare.js +2 -1
  35. package/lib/cloudflare/worker/miniflare.js.map +1 -1
  36. package/lib/cloudflare/worker.d.ts +11 -1
  37. package/lib/cloudflare/worker.d.ts.map +1 -1
  38. package/lib/cloudflare/worker.js +31 -16
  39. package/lib/cloudflare/worker.js.map +1 -1
  40. package/lib/cloudflare/wrangler.json.d.ts +1 -1
  41. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  42. package/lib/cloudflare/wrangler.json.js +28 -10
  43. package/lib/cloudflare/wrangler.json.js.map +1 -1
  44. package/lib/internal/docs/providers.js +2 -0
  45. package/lib/internal/docs/providers.js.map +1 -1
  46. package/lib/os/exec.d.ts +15 -1
  47. package/lib/os/exec.d.ts.map +1 -1
  48. package/lib/os/exec.js +30 -3
  49. package/lib/os/exec.js.map +1 -1
  50. package/lib/scope.d.ts.map +1 -1
  51. package/lib/scope.js +4 -1
  52. package/lib/scope.js.map +1 -1
  53. package/lib/util/deferred-promise.d.ts.map +1 -1
  54. package/lib/util/deferred-promise.js +2 -1
  55. package/lib/util/deferred-promise.js.map +1 -1
  56. package/lib/util/promise-with-resolvers.d.ts +14 -0
  57. package/lib/util/promise-with-resolvers.d.ts.map +1 -0
  58. package/lib/util/promise-with-resolvers.js +19 -0
  59. package/lib/util/promise-with-resolvers.js.map +1 -0
  60. package/package.json +4 -3
  61. package/src/alchemy.ts +8 -2
  62. package/src/build-date.ts +1 -1
  63. package/src/cloudflare/astro.ts +1 -1
  64. package/src/cloudflare/bundle/bundle-worker-dev.ts +3 -4
  65. package/src/cloudflare/bundle/bundle-worker.ts +3 -4
  66. package/src/cloudflare/d1-database.ts +17 -0
  67. package/src/cloudflare/d1-migrations.ts +224 -14
  68. package/src/cloudflare/website.ts +14 -30
  69. package/src/cloudflare/worker/http-server.ts +2 -1
  70. package/src/cloudflare/worker/miniflare.ts +5 -1
  71. package/src/cloudflare/worker.ts +57 -27
  72. package/src/cloudflare/wrangler.json.ts +40 -11
  73. package/src/internal/docs/providers.ts +2 -0
  74. package/src/os/exec.ts +32 -4
  75. package/src/scope.ts +5 -1
  76. package/src/util/deferred-promise.ts +3 -1
  77. package/src/util/promise-with-resolvers.ts +30 -0
@@ -22,6 +22,131 @@ async function readMigrationFile(filePath: string): Promise<string> {
22
22
  return fs.readFile(filePath, "utf-8");
23
23
  }
24
24
 
25
+ /**
26
+ * Detects the current schema of the migration table.
27
+ * Returns info about the table structure to determine if migration is needed.
28
+ */
29
+ async function detectMigrationTableSchema(
30
+ options: D1MigrationOptions,
31
+ ): Promise<{
32
+ exists: boolean;
33
+ hasIdColumn: boolean;
34
+ hasNameColumn: boolean;
35
+ isLegacySchema: boolean;
36
+ columns: Array<{ name: string; type: string; pk: number }>;
37
+ }> {
38
+ try {
39
+ // Check if the table exists and get its schema
40
+ const pragmaSQL = `PRAGMA table_info(${options.migrationsTable});`;
41
+ const result = await executeD1SQL(options, pragmaSQL);
42
+ const columns = result?.result[0]?.results || [];
43
+
44
+ if (columns.length === 0) {
45
+ return {
46
+ exists: false,
47
+ hasIdColumn: false,
48
+ hasNameColumn: false,
49
+ isLegacySchema: false,
50
+ columns: [],
51
+ };
52
+ }
53
+
54
+ const columnNames = columns.map((col: any) => col.name);
55
+ const hasIdColumn = columnNames.includes("id");
56
+ const hasNameColumn = columnNames.includes("name");
57
+
58
+ // Legacy schema has only 2 columns and is missing the proper 3-column structure
59
+ // Wrangler needs (id, name, applied_at) - if we don't have both id and name columns, it's legacy
60
+ const isLegacySchema =
61
+ columns.length === 2 && !(hasIdColumn && hasNameColumn);
62
+
63
+ return {
64
+ exists: true,
65
+ hasIdColumn,
66
+ hasNameColumn,
67
+ isLegacySchema,
68
+ columns: columns.map((col: any) => ({
69
+ name: col.name,
70
+ type: col.type,
71
+ pk: col.pk,
72
+ })),
73
+ };
74
+ } catch (_error) {
75
+ return {
76
+ exists: false,
77
+ hasIdColumn: false,
78
+ hasNameColumn: false,
79
+ isLegacySchema: false,
80
+ columns: [],
81
+ };
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Migrates a legacy 2-column migration table to the wrangler-compatible 3-column format.
87
+ * Legacy: (id, applied_at) or (name, applied_at)
88
+ * New: (id, name, applied_at) where id is primary key and name is migration filename
89
+ */
90
+ async function migrateLegacySchema(
91
+ options: D1MigrationOptions,
92
+ schema: { columns: Array<{ name: string; type: string; pk: number }> },
93
+ ): Promise<void> {
94
+ logger.log(
95
+ `Migrating legacy migration table ${options.migrationsTable} to wrangler-compatible schema...`,
96
+ );
97
+
98
+ // Determine the current primary column name (could be 'id' or something else)
99
+ const primaryColumn =
100
+ schema.columns.find((col) => col.pk === 1)?.name || schema.columns[0]?.name;
101
+
102
+ if (!primaryColumn) {
103
+ throw new Error("Cannot migrate legacy migration table: no columns found");
104
+ }
105
+
106
+ const tempTableName = `${options.migrationsTable}_temp_migration`;
107
+
108
+ try {
109
+ // 1. Create new table with correct schema
110
+ const createNewTableSQL = `CREATE TABLE ${tempTableName} (
111
+ id TEXT PRIMARY KEY,
112
+ name TEXT NOT NULL,
113
+ applied_at TEXT NOT NULL
114
+ );`;
115
+ await executeD1SQL(options, createNewTableSQL);
116
+
117
+ // 2. Copy data from old table to new table
118
+ // Use row_number() to generate sequential IDs, and use the old primary column value as the name
119
+ const copyDataSQL = `
120
+ INSERT INTO ${tempTableName} (id, name, applied_at)
121
+ SELECT
122
+ printf('%05d', row_number() OVER (ORDER BY applied_at)) as id,
123
+ ${primaryColumn} as name,
124
+ applied_at
125
+ FROM ${options.migrationsTable}
126
+ ORDER BY applied_at;
127
+ `;
128
+ await executeD1SQL(options, copyDataSQL);
129
+
130
+ // 3. Drop old table
131
+ const dropOldTableSQL = `DROP TABLE ${options.migrationsTable};`;
132
+ await executeD1SQL(options, dropOldTableSQL);
133
+
134
+ // 4. Rename new table
135
+ const renameTableSQL = `ALTER TABLE ${tempTableName} RENAME TO ${options.migrationsTable};`;
136
+ await executeD1SQL(options, renameTableSQL);
137
+
138
+ logger.log(
139
+ "Successfully migrated migration table to wrangler-compatible schema",
140
+ );
141
+ } catch (error) {
142
+ // If migration fails, try to clean up temp table
143
+ try {
144
+ await executeD1SQL(options, `DROP TABLE IF EXISTS ${tempTableName};`);
145
+ } catch {}
146
+ throw new Error(`Failed to migrate legacy migration table: ${error}`);
147
+ }
148
+ }
149
+
25
150
  /**
26
151
  * Reads migration SQL files from the migrationsDir, sorted by filename.
27
152
  * @param migrationsDir Directory containing .sql migration files
@@ -54,28 +179,64 @@ export async function listMigrationsFiles(
54
179
  }
55
180
 
56
181
  /**
57
- * Ensures the migrations table exists in the D1 database.
182
+ * Ensures the migrations table exists in the D1 database with wrangler-compatible schema.
183
+ * Handles migration from legacy 2-column schema to 3-column schema if needed.
58
184
  */
59
185
  export async function ensureMigrationsTable(
60
186
  options: D1MigrationOptions,
61
187
  ): Promise<void> {
62
- const createTableSQL = `CREATE TABLE IF NOT EXISTS ${options.migrationsTable} (id TEXT PRIMARY KEY, applied_at TEXT);`;
188
+ const schema = await detectMigrationTableSchema(options);
189
+
190
+ // If table doesn't exist, create it with the correct 3-column schema
191
+ if (!schema.exists) {
192
+ const createTableSQL = `CREATE TABLE ${options.migrationsTable} (
193
+ id TEXT PRIMARY KEY,
194
+ name TEXT NOT NULL,
195
+ applied_at TEXT NOT NULL
196
+ );`;
197
+ await executeD1SQL(options, createTableSQL);
198
+ logger.log(
199
+ `Created migration table ${options.migrationsTable} with wrangler-compatible schema`,
200
+ );
201
+ return;
202
+ }
63
203
 
64
- await executeD1SQL(options, createTableSQL);
204
+ // If table has legacy schema, migrate it
205
+ if (schema.isLegacySchema) {
206
+ await migrateLegacySchema(options, schema);
207
+ return;
208
+ }
209
+
210
+ // If table exists but doesn't have the correct 3-column structure, we need to handle it
211
+ if (!schema.hasIdColumn || !schema.hasNameColumn) {
212
+ logger.log(
213
+ `Migration table ${options.migrationsTable} has incomplete schema - attempting migration...`,
214
+ );
215
+ await migrateLegacySchema(options, schema);
216
+ return;
217
+ }
218
+
219
+ // Table already has correct schema
220
+ logger.log(
221
+ `Migration table ${options.migrationsTable} already has correct schema`,
222
+ );
65
223
  }
66
224
 
67
225
  /**
68
- * Gets the list of applied migration IDs from the migrations table.
226
+ * Gets the list of applied migration names from the migrations table.
227
+ * Uses the 'name' column which contains the migration filename.
69
228
  */
70
229
  export async function getAppliedMigrations(
71
230
  options: D1MigrationOptions,
72
231
  ): Promise<Set<string>> {
73
- const sql = `SELECT id FROM ${options.migrationsTable};`;
232
+ const sql = `SELECT name FROM ${options.migrationsTable};`;
74
233
 
75
234
  const result = await executeD1SQL(options, sql);
76
235
 
77
- const ids = (result?.result[0]?.results || []).map((row: any) => row.id);
78
- return new Set(ids);
236
+ const migrationNames = (result?.result[0]?.results || []).map(
237
+ (row: any) => row.name,
238
+ );
239
+ return new Set(migrationNames);
79
240
  }
80
241
 
81
242
  /**
@@ -115,6 +276,7 @@ export async function executeD1SQL(
115
276
 
116
277
  /**
117
278
  * Applies all pending migrations from the provided files to the D1 database.
279
+ * Uses wrangler-compatible 3-column schema (id, name, applied_at).
118
280
  */
119
281
  export async function applyMigrations(
120
282
  options: D1MigrationOptions,
@@ -122,17 +284,65 @@ export async function applyMigrations(
122
284
  await ensureMigrationsTable(options);
123
285
  const applied = await getAppliedMigrations(options);
124
286
 
287
+ // Determine the starting point for sequential IDs by querying existing IDs from the database
288
+ const existingIdsResult = await executeD1SQL(
289
+ options,
290
+ `SELECT id FROM ${options.migrationsTable} ORDER BY id;`,
291
+ );
292
+
293
+ const existingIds = (existingIdsResult?.result[0]?.results || []).map(
294
+ (row: any) => row.id,
295
+ );
296
+
297
+ let maxNumeric = 0;
298
+ for (const id of existingIds) {
299
+ if (/^\d+$/.test(id)) {
300
+ const num = Number.parseInt(id, 10);
301
+ maxNumeric = Math.max(maxNumeric, num);
302
+ }
303
+ }
304
+ let nextSeq = maxNumeric + 1;
305
+
125
306
  for (const migration of options.migrationsFiles) {
126
- const migrationId = migration.id;
307
+ const migrationName = migration.id;
127
308
 
128
- if (applied.has(migrationId)) continue;
309
+ if (applied.has(migrationName)) continue;
129
310
 
130
- // Run the migration
311
+ // Run the migration SQL
131
312
  await executeD1SQL(options, migration.sql);
132
- // Record as applied
133
- const insertSQL = `INSERT INTO ${options.migrationsTable} (id, applied_at) VALUES ('${migrationId.replace("'", "''")}', datetime('now'));`;
134
- await executeD1SQL(options, insertSQL);
135
313
 
136
- logger.log(`Applied migration: ${migrationId}`);
314
+ // Generate a migration id: prefer sequential zero-padded numeric ids (e.g. 00014)
315
+ // to keep consistency with legacy/imported data. If we cannot produce a numeric id
316
+ // (e.g. existing IDs are not numeric), fall back to a unique timestamp-based ID.
317
+ let migrationId: string;
318
+ if (nextSeq > 0) {
319
+ migrationId = nextSeq.toString().padStart(5, "0");
320
+ nextSeq += 1;
321
+ } else {
322
+ migrationId =
323
+ Date.now().toString() + Math.random().toString(36).substr(2, 9);
324
+ }
325
+
326
+ const insertSQL = `INSERT INTO ${options.migrationsTable} (id, name, applied_at) VALUES (?, ?, datetime('now'));`;
327
+
328
+ // Use parameterised query to record the migration
329
+ const response = await options.api.post(
330
+ `/accounts/${options.accountId}/d1/database/${options.databaseId}/query`,
331
+ {
332
+ sql: insertSQL,
333
+ params: [migrationId, migrationName],
334
+ },
335
+ );
336
+
337
+ if (!response.ok) {
338
+ await handleApiError(
339
+ response,
340
+ "inserting migration record",
341
+ "D1 database",
342
+ options.databaseId,
343
+ );
344
+ }
345
+
346
+ logger.log(`Applied migration: ${migrationName}`);
137
347
  }
138
348
  }
@@ -123,7 +123,6 @@ export async function Website<B extends Bindings>(
123
123
  parent: Scope.current,
124
124
  },
125
125
  async (scope) => {
126
- // directory from which all relative paths are resolved
127
126
  const cwd = path.resolve(props.cwd || process.cwd());
128
127
 
129
128
  function resolveAbsPath<S extends string | undefined>(f: S): S {
@@ -132,7 +131,12 @@ export async function Website<B extends Bindings>(
132
131
  ) as S;
133
132
  }
134
133
 
135
- // absolute path to the wrangler.jsonc file
134
+ const mainPath = resolveAbsPath(props.main);
135
+ const assetsDirPath = resolveAbsPath(
136
+ typeof props.assets === "string"
137
+ ? props.assets
138
+ : (props.assets?.dist ?? "dist"),
139
+ );
136
140
  const wranglerJsonPath = resolveAbsPath(
137
141
  typeof wrangler === "boolean"
138
142
  ? "wrangler.jsonc"
@@ -141,23 +145,6 @@ export async function Website<B extends Bindings>(
141
145
  : (wrangler?.path ?? "wrangler.jsonc"),
142
146
  );
143
147
 
144
- // absolute path to the worker entrypoint
145
- const mainPath = resolveAbsPath(props.main);
146
-
147
- // absolute path to the worker entrypoint (if different in wrangler.jsonc)
148
- const wranglerMainPath = resolveAbsPath(
149
- typeof wrangler === "object"
150
- ? (wrangler.main ?? props.main!)
151
- : props.main,
152
- );
153
-
154
- // absolute path to the assets directory
155
- const assetsDirPath = resolveAbsPath(
156
- typeof props.assets === "string"
157
- ? props.assets
158
- : (props.assets?.dist ?? "dist"),
159
- );
160
-
161
148
  const workerName = props.name ?? id;
162
149
 
163
150
  const workerProps = {
@@ -165,10 +152,8 @@ export async function Website<B extends Bindings>(
165
152
  compatibilityDate:
166
153
  props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
167
154
  name: workerName,
168
- entrypoint: mainPath
169
- ? // this path should be relative to the process.cwd() because it is used by esbuild
170
- path.relative(process.cwd(), mainPath)
171
- : undefined,
155
+ cwd: path.relative(process.cwd(), cwd),
156
+ entrypoint: mainPath,
172
157
  assets: {
173
158
  html_handling: "auto-trailing-slash",
174
159
  not_found_handling: props.spa ? "single-page-application" : "none",
@@ -189,24 +174,23 @@ export default {
189
174
  ? {
190
175
  command: props.dev.command,
191
176
  url: props.dev.url,
192
- cwd,
193
177
  }
194
178
  : undefined,
195
179
  } as WorkerProps<any> & { name: string };
196
180
 
197
181
  if (wrangler) {
198
- // paths in wrangler.jsonc must be relative to it
199
- const relativeToWrangler = <S extends string | undefined>(f: S): S =>
200
- (f ? path.relative(path.dirname(wranglerJsonPath), f) : f) as S;
182
+ const wranglerPath = path.relative(cwd, wranglerJsonPath);
183
+ const wranglerDir = path.dirname(wranglerPath);
184
+
201
185
  await WranglerJson("wrangler.jsonc", {
202
- path: wranglerJsonPath,
186
+ path: wranglerPath,
203
187
  worker: workerProps,
204
- main: relativeToWrangler(wranglerMainPath),
188
+ main: mainPath ? path.relative(wranglerDir, mainPath) : undefined,
205
189
  // hard-code the assets directory because we haven't yet included the assets binding
206
190
  assets: {
207
191
  binding: "ASSETS",
208
192
  // path must be relative to the wrangler.jsonc file
209
- directory: relativeToWrangler(assetsDirPath),
193
+ directory: path.relative(wranglerDir, assetsDirPath),
210
194
  },
211
195
  });
212
196
  }
@@ -1,6 +1,7 @@
1
1
  import http from "node:http";
2
2
  import type { AddressInfo } from "node:net";
3
3
  import { Readable } from "node:stream";
4
+ import { promiseWithResolvers } from "../../util/promise-with-resolvers.ts";
4
5
 
5
6
  export class HTTPServer {
6
7
  server: http.Server;
@@ -10,7 +11,7 @@ export class HTTPServer {
10
11
  port?: number;
11
12
  fetch: (request: Request) => Promise<Response>;
12
13
  }) {
13
- const { promise, resolve } = Promise.withResolvers<void>();
14
+ const { promise, resolve } = promiseWithResolvers<void>();
14
15
  this.ready = promise;
15
16
  this.server = http
16
17
  .createServer(async (req, res) => {
@@ -7,6 +7,10 @@ import type {
7
7
  import path from "node:path";
8
8
  import { findOpenPort } from "../../util/find-open-port.ts";
9
9
  import { logger } from "../../util/logger.ts";
10
+ import {
11
+ promiseWithResolvers,
12
+ type PromiseWithResolvers,
13
+ } from "../../util/promise-with-resolvers.ts";
10
14
  import { HTTPServer } from "./http-server.ts";
11
15
  import {
12
16
  buildMiniflareWorkerOptions,
@@ -40,7 +44,7 @@ class MiniflareServer {
40
44
  writer = this.stream.getWriter();
41
45
 
42
46
  async push(worker: MiniflareWorkerOptions) {
43
- const promise = Promise.withResolvers<HTTPServer>();
47
+ const promise = promiseWithResolvers<HTTPServer>();
44
48
  const [, server] = await Promise.all([
45
49
  this.writer.write({ worker, promise }),
46
50
  promise.promise,
@@ -135,6 +135,12 @@ export interface BaseWorkerProps<
135
135
  /**
136
136
  * The root directory of the project
137
137
  */
138
+ cwd?: string;
139
+
140
+ /**
141
+ * The root directory of the project
142
+ * @deprecated Use `cwd` instead
143
+ */
138
144
  projectRoot?: string;
139
145
 
140
146
  /**
@@ -466,6 +472,12 @@ export type Worker<
466
472
  */
467
473
  name: string;
468
474
 
475
+ /**
476
+ * The root directory of the project
477
+ * @default process.cwd()
478
+ */
479
+ cwd: string;
480
+
469
481
  /**
470
482
  * Time at which the worker was created
471
483
  */
@@ -967,6 +979,15 @@ export const _Worker = Resource(
967
979
  // Use the provided name
968
980
  const workerName = props.name ?? id;
969
981
 
982
+ if (props.projectRoot) {
983
+ logger.warn("projectRoot is deprecated, use cwd instead");
984
+ props.cwd = props.projectRoot;
985
+ }
986
+
987
+ const cwd = props.cwd ? path.resolve(props.cwd) : process.cwd();
988
+ const relativeCwd =
989
+ cwd === process.cwd() ? undefined : path.relative(process.cwd(), cwd);
990
+
970
991
  const compatibilityDate =
971
992
  props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
972
993
  const compatibilityFlags = props.compatibilityFlags ?? [];
@@ -979,7 +1000,7 @@ export const _Worker = Resource(
979
1000
  upsertDevCommand({
980
1001
  id,
981
1002
  command: props.dev.command,
982
- cwd: props.dev.cwd ?? process.cwd(),
1003
+ cwd: props.dev.cwd ?? cwd,
983
1004
  env: props.env ?? {},
984
1005
  });
985
1006
  return this({
@@ -987,6 +1008,7 @@ export const _Worker = Resource(
987
1008
  id,
988
1009
  entrypoint: props.entrypoint,
989
1010
  name: workerName,
1011
+ cwd: relativeCwd,
990
1012
  compatibilityDate,
991
1013
  compatibilityFlags,
992
1014
  format: props.format || "esm", // Include format in the output
@@ -1026,6 +1048,7 @@ export const _Worker = Resource(
1026
1048
  {
1027
1049
  ...props,
1028
1050
  entrypoint: props.entrypoint,
1051
+ cwd,
1029
1052
  compatibilityDate,
1030
1053
  compatibilityFlags,
1031
1054
  },
@@ -1124,6 +1147,7 @@ export const _Worker = Resource(
1124
1147
  (await bundleWorkerScript({
1125
1148
  name: workerName,
1126
1149
  ...props,
1150
+ cwd,
1127
1151
  compatibilityDate,
1128
1152
  compatibilityFlags,
1129
1153
  }));
@@ -1142,6 +1166,7 @@ export const _Worker = Resource(
1142
1166
  id,
1143
1167
  entrypoint: props.entrypoint,
1144
1168
  name: workerName,
1169
+ cwd: relativeCwd,
1145
1170
  compatibilityDate,
1146
1171
  compatibilityFlags,
1147
1172
  format: props.format || "esm", // Include format in the output
@@ -1172,6 +1197,7 @@ export const _Worker = Resource(
1172
1197
  (await bundleWorkerScript({
1173
1198
  ...props,
1174
1199
  name: workerName,
1200
+ cwd,
1175
1201
  compatibilityDate,
1176
1202
  compatibilityFlags,
1177
1203
  }));
@@ -1348,31 +1374,34 @@ export const _Worker = Resource(
1348
1374
  };
1349
1375
 
1350
1376
  if (this.phase === "delete") {
1351
- // Delete any queue consumers attached to this worker first
1352
- await deleteQueueConsumers(api, workerName);
1353
-
1354
- await withExponentialBackoff(
1355
- () =>
1356
- deleteWorker(api, {
1357
- workerName,
1358
- namespace:
1359
- typeof props.namespace === "string"
1360
- ? props.namespace
1361
- : props.namespace?.namespaceId,
1362
- url: this.output.url,
1363
- }),
1364
- (err) =>
1365
- (err.status === 400 &&
1366
- err.message.includes(
1367
- "is still referenced by service bindings in Workers",
1368
- )) ||
1369
- err.status === 500 ||
1370
- err.status === 503,
1371
- 10,
1372
- 100,
1373
- );
1377
+ // only delete the worker if it's not a version
1378
+ if (!props.version) {
1379
+ // Delete any queue consumers attached to this worker first
1380
+ await deleteQueueConsumers(api, workerName);
1381
+
1382
+ await withExponentialBackoff(
1383
+ () =>
1384
+ deleteWorker(api, {
1385
+ workerName,
1386
+ namespace:
1387
+ typeof props.namespace === "string"
1388
+ ? props.namespace
1389
+ : props.namespace?.namespaceId,
1390
+ url: this.output.url,
1391
+ }),
1392
+ (err) =>
1393
+ (err.status === 400 &&
1394
+ err.message.includes(
1395
+ "is still referenced by service bindings in Workers",
1396
+ )) ||
1397
+ err.status === 500 ||
1398
+ err.status === 503,
1399
+ 10,
1400
+ 100,
1401
+ );
1374
1402
 
1375
- return this.destroy();
1403
+ return this.destroy();
1404
+ }
1376
1405
  }
1377
1406
 
1378
1407
  if (this.phase === "create") {
@@ -1425,7 +1454,7 @@ export const _Worker = Resource(
1425
1454
  : customDomain.zoneId,
1426
1455
  adopt:
1427
1456
  typeof customDomain === "string"
1428
- ? false
1457
+ ? (props.adopt ?? false)
1429
1458
  : (customDomain.adopt ?? props.adopt),
1430
1459
  });
1431
1460
  }),
@@ -1454,7 +1483,7 @@ export const _Worker = Resource(
1454
1483
  typeof routeConfig === "string" ? undefined : routeConfig.zoneId, // Route resource will handle inference if not provided
1455
1484
  adopt:
1456
1485
  typeof routeConfig === "string"
1457
- ? false
1486
+ ? (props.adopt ?? false)
1458
1487
  : (routeConfig.adopt ?? props.adopt),
1459
1488
  accountId: props.accountId,
1460
1489
  apiKey: props.apiKey,
@@ -1497,6 +1526,7 @@ export const _Worker = Resource(
1497
1526
  id,
1498
1527
  entrypoint: props.entrypoint,
1499
1528
  name: workerName,
1529
+ cwd: relativeCwd,
1500
1530
  compatibilityDate,
1501
1531
  compatibilityFlags,
1502
1532
  format: props.format || "esm", // Include format in the output
@@ -27,7 +27,7 @@ export interface WranglerJsonProps {
27
27
  /**
28
28
  * Path to write the wrangler.json file to
29
29
  *
30
- * @default cwd/wrangler.json
30
+ * @default worker.cwd/wrangler.json
31
31
  */
32
32
  path?: string;
33
33
 
@@ -89,14 +89,27 @@ export const WranglerJson = Resource(
89
89
  _id: string,
90
90
  props: WranglerJsonProps,
91
91
  ): Promise<WranglerJson> {
92
- // Default path is wrangler.json in current directory
93
- const filePath = props.path || "wrangler.jsonc";
94
-
95
92
  if (this.phase === "delete") {
96
93
  return this.destroy();
97
94
  }
98
95
 
99
- if (props.worker.entrypoint === undefined) {
96
+ const cwd = props.worker.cwd
97
+ ? path.resolve(props.worker.cwd)
98
+ : process.cwd();
99
+
100
+ const toAbsolute = <T extends string | undefined>(input: T): T => {
101
+ return (input ? path.resolve(cwd, input) : undefined) as T;
102
+ };
103
+
104
+ const main = toAbsolute(props.main ?? props.worker.entrypoint);
105
+ let filePath = toAbsolute(props.path ?? cwd);
106
+ if (!path.basename(filePath).match(".json")) {
107
+ filePath = path.join(filePath, props.name ?? "wrangler.jsonc");
108
+ }
109
+
110
+ const dirname = path.dirname(filePath);
111
+
112
+ if (!main) {
100
113
  throw new Error(
101
114
  "Worker must have an entrypoint to generate a wrangler.json",
102
115
  );
@@ -107,16 +120,27 @@ export const WranglerJson = Resource(
107
120
  const spec: WranglerJsonSpec = {
108
121
  name: worker.name,
109
122
  // Use entrypoint as main if it exists
110
- main: props.main ?? worker.entrypoint,
123
+ main: path.relative(dirname, main),
111
124
  // see: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
112
125
  compatibility_date: worker.compatibilityDate,
113
126
  compatibility_flags: props.worker.compatibilityFlags,
114
- assets: props.assets,
127
+ assets: props.assets
128
+ ? {
129
+ directory: toAbsolute(props.assets.directory),
130
+ binding: props.assets.binding,
131
+ }
132
+ : undefined,
115
133
  };
116
134
 
117
135
  // Process bindings if they exist
118
136
  if (worker.bindings) {
119
- processBindings(spec, worker.bindings, worker.eventSources, worker.name);
137
+ processBindings(
138
+ spec,
139
+ worker.bindings,
140
+ worker.eventSources,
141
+ worker.name,
142
+ cwd,
143
+ );
120
144
  }
121
145
 
122
146
  // Add environment variables as vars
@@ -128,13 +152,17 @@ export const WranglerJson = Resource(
128
152
  spec.triggers = { crons: worker.crons };
129
153
  }
130
154
 
131
- await fs.mkdir(path.dirname(filePath), { recursive: true });
155
+ if (spec.assets) {
156
+ spec.assets.directory = path.relative(dirname, spec.assets.directory);
157
+ }
158
+
159
+ await fs.mkdir(dirname, { recursive: true });
132
160
  await fs.writeFile(filePath, await formatJson(spec));
133
161
 
134
162
  // Return the resource
135
163
  return this({
136
164
  ...props,
137
- path: filePath,
165
+ path: path.relative(cwd, filePath),
138
166
  spec,
139
167
  createdAt: Date.now(),
140
168
  updatedAt: Date.now(),
@@ -381,6 +409,7 @@ function processBindings(
381
409
  bindings: Bindings,
382
410
  eventSources: EventSource[] | undefined,
383
411
  workerName: string,
412
+ workerCwd: string,
384
413
  ): void {
385
414
  // Arrays to collect different binding types
386
415
  const kvNamespaces: { binding: string; id: string; preview_id: string }[] =
@@ -527,7 +556,7 @@ function processBindings(
527
556
  secrets.push(bindingName);
528
557
  } else if (binding.type === "assets") {
529
558
  spec.assets = {
530
- directory: binding.path,
559
+ directory: path.resolve(workerCwd, binding.path),
531
560
  binding: bindingName,
532
561
  };
533
562
  } else if (binding.type === "workflow") {