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.
- package/bin/alchemy.mjs +508 -316
- package/lib/alchemy.d.ts.map +1 -1
- package/lib/alchemy.js +8 -2
- package/lib/alchemy.js.map +1 -1
- package/lib/build-date.d.ts +1 -1
- package/lib/build-date.js +1 -1
- package/lib/cloudflare/astro.d.ts +1 -1
- package/lib/cloudflare/astro.d.ts.map +1 -1
- package/lib/cloudflare/astro.js +1 -1
- package/lib/cloudflare/astro.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +1 -0
- package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker-dev.js +2 -3
- package/lib/cloudflare/bundle/bundle-worker-dev.js.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.d.ts +1 -0
- package/lib/cloudflare/bundle/bundle-worker.d.ts.map +1 -1
- package/lib/cloudflare/bundle/bundle-worker.js +2 -3
- package/lib/cloudflare/bundle/bundle-worker.js.map +1 -1
- package/lib/cloudflare/d1-database.d.ts.map +1 -1
- package/lib/cloudflare/d1-database.js +17 -0
- package/lib/cloudflare/d1-database.js.map +1 -1
- package/lib/cloudflare/d1-migrations.d.ts +5 -2
- package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
- package/lib/cloudflare/d1-migrations.js +166 -14
- package/lib/cloudflare/d1-migrations.js.map +1 -1
- package/lib/cloudflare/website.d.ts.map +1 -1
- package/lib/cloudflare/website.js +11 -22
- package/lib/cloudflare/website.js.map +1 -1
- package/lib/cloudflare/worker/http-server.d.ts.map +1 -1
- package/lib/cloudflare/worker/http-server.js +2 -1
- package/lib/cloudflare/worker/http-server.js.map +1 -1
- package/lib/cloudflare/worker/miniflare.d.ts +1 -0
- package/lib/cloudflare/worker/miniflare.d.ts.map +1 -1
- package/lib/cloudflare/worker/miniflare.js +2 -1
- package/lib/cloudflare/worker/miniflare.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +11 -1
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +31 -16
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +28 -10
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/internal/docs/providers.js +2 -0
- package/lib/internal/docs/providers.js.map +1 -1
- package/lib/os/exec.d.ts +15 -1
- package/lib/os/exec.d.ts.map +1 -1
- package/lib/os/exec.js +30 -3
- package/lib/os/exec.js.map +1 -1
- package/lib/scope.d.ts.map +1 -1
- package/lib/scope.js +4 -1
- package/lib/scope.js.map +1 -1
- package/lib/util/deferred-promise.d.ts.map +1 -1
- package/lib/util/deferred-promise.js +2 -1
- package/lib/util/deferred-promise.js.map +1 -1
- package/lib/util/promise-with-resolvers.d.ts +14 -0
- package/lib/util/promise-with-resolvers.d.ts.map +1 -0
- package/lib/util/promise-with-resolvers.js +19 -0
- package/lib/util/promise-with-resolvers.js.map +1 -0
- package/package.json +4 -3
- package/src/alchemy.ts +8 -2
- package/src/build-date.ts +1 -1
- package/src/cloudflare/astro.ts +1 -1
- package/src/cloudflare/bundle/bundle-worker-dev.ts +3 -4
- package/src/cloudflare/bundle/bundle-worker.ts +3 -4
- package/src/cloudflare/d1-database.ts +17 -0
- package/src/cloudflare/d1-migrations.ts +224 -14
- package/src/cloudflare/website.ts +14 -30
- package/src/cloudflare/worker/http-server.ts +2 -1
- package/src/cloudflare/worker/miniflare.ts +5 -1
- package/src/cloudflare/worker.ts +57 -27
- package/src/cloudflare/wrangler.json.ts +40 -11
- package/src/internal/docs/providers.ts +2 -0
- package/src/os/exec.ts +32 -4
- package/src/scope.ts +5 -1
- package/src/util/deferred-promise.ts +3 -1
- 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
|
|
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
|
-
|
|
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
|
|
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
|
|
232
|
+
const sql = `SELECT name FROM ${options.migrationsTable};`;
|
|
74
233
|
|
|
75
234
|
const result = await executeD1SQL(options, sql);
|
|
76
235
|
|
|
77
|
-
const
|
|
78
|
-
|
|
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
|
|
307
|
+
const migrationName = migration.id;
|
|
127
308
|
|
|
128
|
-
if (applied.has(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
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
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
182
|
+
const wranglerPath = path.relative(cwd, wranglerJsonPath);
|
|
183
|
+
const wranglerDir = path.dirname(wranglerPath);
|
|
184
|
+
|
|
201
185
|
await WranglerJson("wrangler.jsonc", {
|
|
202
|
-
path:
|
|
186
|
+
path: wranglerPath,
|
|
203
187
|
worker: workerProps,
|
|
204
|
-
main:
|
|
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:
|
|
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 } =
|
|
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 =
|
|
47
|
+
const promise = promiseWithResolvers<HTTPServer>();
|
|
44
48
|
const [, server] = await Promise.all([
|
|
45
49
|
this.writer.write({ worker, promise }),
|
|
46
50
|
promise.promise,
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -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 ??
|
|
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
|
-
//
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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") {
|