wrangler 2.2.2 → 2.2.3
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/package.json +2 -2
- package/src/__tests__/d1.test.ts +12 -10
- package/src/__tests__/generate.test.ts +3 -3
- package/src/__tests__/index.test.ts +2 -2
- package/src/__tests__/queues.test.ts +1 -1
- package/src/config/environment.ts +6 -0
- package/src/create-worker-upload-form.ts +11 -8
- package/src/d1/constants.ts +2 -0
- package/src/d1/create.tsx +18 -9
- package/src/d1/execute.tsx +94 -49
- package/src/d1/index.ts +24 -1
- package/src/d1/migrations.tsx +446 -0
- package/src/d1/options.ts +10 -0
- package/src/d1/types.tsx +10 -0
- package/src/d1/utils.ts +10 -1
- package/src/dev/local.tsx +59 -30
- package/src/dev/start-server.ts +13 -7
- package/src/dialogs.tsx +14 -8
- package/src/index.tsx +1 -1
- package/src/worker.ts +5 -1
- package/templates/d1-beta-facade.js +47 -25
- package/wrangler-dist/cli.d.ts +6 -0
- package/wrangler-dist/cli.js +1384 -956
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { Box, render, Text } from "ink";
|
|
4
|
+
import Table from "ink-table";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { withConfig } from "../config";
|
|
7
|
+
import { confirm } from "../dialogs";
|
|
8
|
+
import { logger } from "../logger";
|
|
9
|
+
import { requireAuth } from "../user";
|
|
10
|
+
import { createBackup } from "./backups";
|
|
11
|
+
import { DEFAULT_MIGRATION_PATH } from "./constants";
|
|
12
|
+
import { executeSql } from "./execute";
|
|
13
|
+
import { Database } from "./options";
|
|
14
|
+
import { d1BetaWarning, getDatabaseInfoFromConfig } from "./utils";
|
|
15
|
+
import type { ConfigFields, DevConfig, Environment } from "../config";
|
|
16
|
+
import type { ParseError } from "../parse";
|
|
17
|
+
import type { BaseSqlExecuteArgs, QueryResult } from "./execute";
|
|
18
|
+
import type { Migration } from "./types";
|
|
19
|
+
import type { Argv } from "yargs";
|
|
20
|
+
|
|
21
|
+
async function getMigrationsPath(
|
|
22
|
+
projectPath: string,
|
|
23
|
+
migrationsFolderPath: string,
|
|
24
|
+
createIfMissing: boolean
|
|
25
|
+
): Promise<string> {
|
|
26
|
+
const dir = path.resolve(projectPath, migrationsFolderPath);
|
|
27
|
+
if (fs.existsSync(dir)) return dir;
|
|
28
|
+
|
|
29
|
+
const warning = `No migrations folder found.${
|
|
30
|
+
migrationsFolderPath === DEFAULT_MIGRATION_PATH
|
|
31
|
+
? " Set `migrations_dir` in wrangler.toml to choose a different path."
|
|
32
|
+
: ""
|
|
33
|
+
}`;
|
|
34
|
+
|
|
35
|
+
if (createIfMissing && (await confirm(`${warning}\nOk to create ${dir}?`))) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
return dir;
|
|
38
|
+
} else {
|
|
39
|
+
logger.warn(warning);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new Error(`No migrations present at ${dir}.`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getUnappliedMigrations(
|
|
46
|
+
migrationsTableName: string,
|
|
47
|
+
migrationsPath: string,
|
|
48
|
+
local: undefined | boolean,
|
|
49
|
+
config: ConfigFields<DevConfig> & Environment,
|
|
50
|
+
name: string,
|
|
51
|
+
persistTo: undefined | string
|
|
52
|
+
): Promise<Array<string>> {
|
|
53
|
+
const appliedMigrations = (
|
|
54
|
+
await listAppliedMigrations(
|
|
55
|
+
migrationsTableName,
|
|
56
|
+
local,
|
|
57
|
+
config,
|
|
58
|
+
name,
|
|
59
|
+
persistTo
|
|
60
|
+
)
|
|
61
|
+
).map((migration) => {
|
|
62
|
+
return migration.name;
|
|
63
|
+
});
|
|
64
|
+
const projectMigrations = getMigrationNames(migrationsPath);
|
|
65
|
+
|
|
66
|
+
const unappliedMigrations: Array<string> = [];
|
|
67
|
+
|
|
68
|
+
for (const migration of projectMigrations) {
|
|
69
|
+
if (!appliedMigrations.includes(migration)) {
|
|
70
|
+
unappliedMigrations.push(migration);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return unappliedMigrations;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function ListOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
|
|
78
|
+
return Database(yargs);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const ListHandler = withConfig<BaseSqlExecuteArgs>(
|
|
82
|
+
async ({ config, database, local, persistTo }): Promise<void> => {
|
|
83
|
+
await requireAuth({});
|
|
84
|
+
logger.log(d1BetaWarning);
|
|
85
|
+
|
|
86
|
+
const databaseInfo = await getDatabaseInfoFromConfig(config, database);
|
|
87
|
+
if (!databaseInfo) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Can't find a DB with name/binding '${database}' in local config. Check info in wrangler.toml...`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!config.configPath) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const { migrationsTableName, migrationsFolderPath } = databaseInfo;
|
|
97
|
+
|
|
98
|
+
const migrationsPath = await getMigrationsPath(
|
|
99
|
+
path.dirname(config.configPath),
|
|
100
|
+
migrationsFolderPath,
|
|
101
|
+
false
|
|
102
|
+
);
|
|
103
|
+
await initMigrationsTable(
|
|
104
|
+
migrationsTableName,
|
|
105
|
+
local,
|
|
106
|
+
config,
|
|
107
|
+
database,
|
|
108
|
+
persistTo
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
const unappliedMigrations = (
|
|
112
|
+
await getUnappliedMigrations(
|
|
113
|
+
migrationsTableName,
|
|
114
|
+
migrationsPath,
|
|
115
|
+
local,
|
|
116
|
+
config,
|
|
117
|
+
database,
|
|
118
|
+
persistTo
|
|
119
|
+
)
|
|
120
|
+
).map((migration) => {
|
|
121
|
+
return {
|
|
122
|
+
Name: migration,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (unappliedMigrations.length === 0) {
|
|
127
|
+
render(<Text>✅ No migrations to apply!</Text>);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
render(
|
|
132
|
+
<Box flexDirection="column">
|
|
133
|
+
<Text>Migrations to be applied:</Text>
|
|
134
|
+
<Table data={unappliedMigrations} columns={["Name"]}></Table>
|
|
135
|
+
</Box>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
export function ApplyOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
|
|
141
|
+
return Database(yargs);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export const ApplyHandler = withConfig<BaseSqlExecuteArgs>(
|
|
145
|
+
async ({ config, database, local, persistTo }): Promise<void> => {
|
|
146
|
+
const accountId = await requireAuth({});
|
|
147
|
+
logger.log(d1BetaWarning);
|
|
148
|
+
|
|
149
|
+
const databaseInfo = await getDatabaseInfoFromConfig(config, database);
|
|
150
|
+
if (!databaseInfo) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Can't find a DB with name/binding '${database}' in local config. Check info in wrangler.toml...`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!config.configPath) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const migrationsPath = await getMigrationsPath(
|
|
161
|
+
path.dirname(config.configPath),
|
|
162
|
+
databaseInfo.migrationsFolderPath,
|
|
163
|
+
false
|
|
164
|
+
);
|
|
165
|
+
await initMigrationsTable(
|
|
166
|
+
databaseInfo.migrationsTableName,
|
|
167
|
+
local,
|
|
168
|
+
config,
|
|
169
|
+
database,
|
|
170
|
+
persistTo
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const unappliedMigrations = (
|
|
174
|
+
await getUnappliedMigrations(
|
|
175
|
+
databaseInfo.migrationsTableName,
|
|
176
|
+
migrationsPath,
|
|
177
|
+
local,
|
|
178
|
+
config,
|
|
179
|
+
database,
|
|
180
|
+
persistTo
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
.map((migration) => {
|
|
184
|
+
return {
|
|
185
|
+
Name: migration,
|
|
186
|
+
Status: "🕒️",
|
|
187
|
+
};
|
|
188
|
+
})
|
|
189
|
+
.sort((a, b) => {
|
|
190
|
+
const migrationNumberA = parseInt(a.Name.split("_")[0]);
|
|
191
|
+
const migrationNumberB = parseInt(b.Name.split("_")[0]);
|
|
192
|
+
if (migrationNumberA < migrationNumberB) {
|
|
193
|
+
return -1;
|
|
194
|
+
}
|
|
195
|
+
if (migrationNumberA > migrationNumberB) {
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// numbers must be equal
|
|
200
|
+
return 0;
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (unappliedMigrations.length === 0) {
|
|
204
|
+
render(<Text>✅ No migrations to apply!</Text>);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const isInteractive = process.stdout.isTTY;
|
|
209
|
+
if (isInteractive) {
|
|
210
|
+
const ok = await confirm(
|
|
211
|
+
`About to apply ${unappliedMigrations.length} migration(s)\n` +
|
|
212
|
+
"Your database may not be available to serve requests during the migration, continue?",
|
|
213
|
+
<Box flexDirection="column">
|
|
214
|
+
<Text>Migrations to be applied:</Text>
|
|
215
|
+
<Table data={unappliedMigrations} columns={["Name"]}></Table>
|
|
216
|
+
</Box>
|
|
217
|
+
);
|
|
218
|
+
if (!ok) return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
render(<Text>🕒 Creating backup...</Text>);
|
|
222
|
+
await createBackup(accountId, databaseInfo.uuid);
|
|
223
|
+
|
|
224
|
+
for (const migration of unappliedMigrations) {
|
|
225
|
+
let query = fs.readFileSync(
|
|
226
|
+
`${migrationsPath}/${migration.Name}`,
|
|
227
|
+
"utf8"
|
|
228
|
+
);
|
|
229
|
+
query += `
|
|
230
|
+
INSERT INTO ${databaseInfo.migrationsTableName} (name)
|
|
231
|
+
values ('${migration.Name}');
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
let success = true;
|
|
235
|
+
let errorNotes: Array<string> = [];
|
|
236
|
+
try {
|
|
237
|
+
const response = await executeSql(
|
|
238
|
+
local,
|
|
239
|
+
config,
|
|
240
|
+
database,
|
|
241
|
+
undefined,
|
|
242
|
+
persistTo,
|
|
243
|
+
undefined,
|
|
244
|
+
query
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
if (response === null) {
|
|
248
|
+
// TODO: return error
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const result of response) {
|
|
253
|
+
// When executing more than 1 statement, response turns into an array of QueryResult
|
|
254
|
+
if (Array.isArray(result)) {
|
|
255
|
+
for (const subResult of result) {
|
|
256
|
+
if (!subResult.success) {
|
|
257
|
+
success = false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
if (!result.success) {
|
|
262
|
+
success = false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} catch (e) {
|
|
267
|
+
const err = e as ParseError;
|
|
268
|
+
|
|
269
|
+
success = false;
|
|
270
|
+
errorNotes = err.notes.map((msg) => msg.text);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
migration.Status = success ? "✅" : "❌";
|
|
274
|
+
|
|
275
|
+
render(
|
|
276
|
+
<Box flexDirection="column">
|
|
277
|
+
<Table
|
|
278
|
+
data={unappliedMigrations}
|
|
279
|
+
columns={["Name", "Status"]}
|
|
280
|
+
></Table>
|
|
281
|
+
{errorNotes.length > 0 && (
|
|
282
|
+
<Box flexDirection="column">
|
|
283
|
+
<Text> </Text>
|
|
284
|
+
<Text>
|
|
285
|
+
❌ Migration {migration.Name} failed with following Errors
|
|
286
|
+
</Text>
|
|
287
|
+
<Table
|
|
288
|
+
data={errorNotes.map((err) => {
|
|
289
|
+
return { Error: err };
|
|
290
|
+
})}
|
|
291
|
+
></Table>
|
|
292
|
+
</Box>
|
|
293
|
+
)}
|
|
294
|
+
</Box>
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
if (errorNotes.length > 0) return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
export const listAppliedMigrations = async (
|
|
303
|
+
migrationsTableName: string,
|
|
304
|
+
local: undefined | boolean,
|
|
305
|
+
config: ConfigFields<DevConfig> & Environment,
|
|
306
|
+
name: string,
|
|
307
|
+
persistTo: undefined | string
|
|
308
|
+
): Promise<Migration[]> => {
|
|
309
|
+
const Query = `SELECT *
|
|
310
|
+
FROM ${migrationsTableName}
|
|
311
|
+
ORDER BY id`;
|
|
312
|
+
|
|
313
|
+
const response: QueryResult[] | null = await executeSql(
|
|
314
|
+
local,
|
|
315
|
+
config,
|
|
316
|
+
name,
|
|
317
|
+
undefined,
|
|
318
|
+
persistTo,
|
|
319
|
+
undefined,
|
|
320
|
+
Query
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
if (!response || response[0].results.length === 0) return [];
|
|
324
|
+
|
|
325
|
+
return response[0].results as Migration[];
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const initMigrationsTable = async (
|
|
329
|
+
migrationsTableName: string,
|
|
330
|
+
local: undefined | boolean,
|
|
331
|
+
config: ConfigFields<DevConfig> & Environment,
|
|
332
|
+
name: string,
|
|
333
|
+
persistTo: undefined | string
|
|
334
|
+
) => {
|
|
335
|
+
return executeSql(
|
|
336
|
+
local,
|
|
337
|
+
config,
|
|
338
|
+
name,
|
|
339
|
+
undefined,
|
|
340
|
+
persistTo,
|
|
341
|
+
undefined,
|
|
342
|
+
`
|
|
343
|
+
CREATE TABLE IF NOT EXISTS ${migrationsTableName}
|
|
344
|
+
(
|
|
345
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
346
|
+
name TEXT UNIQUE,
|
|
347
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
|
348
|
+
);
|
|
349
|
+
`
|
|
350
|
+
);
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
function getMigrationNames(migrationsPath: string): Array<string> {
|
|
354
|
+
const migrations = [];
|
|
355
|
+
|
|
356
|
+
const dir = fs.opendirSync(migrationsPath);
|
|
357
|
+
|
|
358
|
+
let dirent;
|
|
359
|
+
while ((dirent = dir.readSync()) !== null) {
|
|
360
|
+
if (dirent.name.endsWith(".sql")) migrations.push(dirent.name);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
dir.closeSync();
|
|
364
|
+
|
|
365
|
+
return migrations;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function getNextMigrationNumber(migrationsPath: string): number {
|
|
369
|
+
let highestMigrationNumber = -1;
|
|
370
|
+
|
|
371
|
+
for (const migration in getMigrationNames(migrationsPath)) {
|
|
372
|
+
const migrationNumber = parseInt(migration.split("_")[0]);
|
|
373
|
+
|
|
374
|
+
if (migrationNumber > highestMigrationNumber) {
|
|
375
|
+
highestMigrationNumber = migrationNumber;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return highestMigrationNumber + 1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function pad(num: number, size: number): string {
|
|
383
|
+
let newNum = num.toString();
|
|
384
|
+
while (newNum.length < size) newNum = "0" + newNum;
|
|
385
|
+
return newNum;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
type MigrationsCreateArgs = {
|
|
389
|
+
config?: string;
|
|
390
|
+
database: string;
|
|
391
|
+
message: string;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export function CreateOptions(yargs: Argv): Argv<MigrationsCreateArgs> {
|
|
395
|
+
return Database(yargs).positional("message", {
|
|
396
|
+
describe: "The Migration message",
|
|
397
|
+
type: "string",
|
|
398
|
+
demandOption: true,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export const CreateHandler = withConfig<MigrationsCreateArgs>(
|
|
403
|
+
async ({ config, database, message }): Promise<void> => {
|
|
404
|
+
await requireAuth({});
|
|
405
|
+
logger.log(d1BetaWarning);
|
|
406
|
+
|
|
407
|
+
const databaseInfo = await getDatabaseInfoFromConfig(config, database);
|
|
408
|
+
if (!databaseInfo) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Can't find a DB with name/binding '${database}' in local config. Check info in wrangler.toml...`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!config.configPath) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const migrationsPath = await getMigrationsPath(
|
|
419
|
+
path.dirname(config.configPath),
|
|
420
|
+
databaseInfo.migrationsFolderPath,
|
|
421
|
+
true
|
|
422
|
+
);
|
|
423
|
+
const nextMigrationNumber = pad(getNextMigrationNumber(migrationsPath), 4);
|
|
424
|
+
const migrationName = message.replaceAll(" ", "_");
|
|
425
|
+
|
|
426
|
+
const newMigrationName = `${nextMigrationNumber}_${migrationName}.sql`;
|
|
427
|
+
|
|
428
|
+
fs.writeFileSync(
|
|
429
|
+
`${migrationsPath}/${newMigrationName}`,
|
|
430
|
+
`-- Migration number: ${nextMigrationNumber} \t ${new Date().toISOString()}\n`
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
render(
|
|
434
|
+
<Box flexDirection="column">
|
|
435
|
+
<Text>
|
|
436
|
+
✅ Successfully created Migration '{newMigrationName}'!
|
|
437
|
+
</Text>
|
|
438
|
+
<Text> </Text>
|
|
439
|
+
<Text>The migration is available for editing here</Text>
|
|
440
|
+
<Text>
|
|
441
|
+
{migrationsPath}/{newMigrationName}
|
|
442
|
+
</Text>
|
|
443
|
+
</Box>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
);
|
package/src/d1/options.ts
CHANGED
|
@@ -10,3 +10,13 @@ export function Name(yargs: Argv) {
|
|
|
10
10
|
})
|
|
11
11
|
.epilogue(d1BetaWarning);
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
export function Database(yargs: Argv) {
|
|
15
|
+
return yargs
|
|
16
|
+
.positional("database", {
|
|
17
|
+
describe: "The name or binding of the DB",
|
|
18
|
+
type: "string",
|
|
19
|
+
demandOption: true,
|
|
20
|
+
})
|
|
21
|
+
.epilogue(d1BetaWarning);
|
|
22
|
+
}
|
package/src/d1/types.tsx
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export type Database = {
|
|
2
2
|
uuid: string;
|
|
3
3
|
name: string;
|
|
4
|
+
binding: string;
|
|
5
|
+
internal_env?: string;
|
|
6
|
+
migrationsTableName: string;
|
|
7
|
+
migrationsFolderPath: string;
|
|
4
8
|
};
|
|
5
9
|
|
|
6
10
|
export type Backup = {
|
|
@@ -12,3 +16,9 @@ export type Backup = {
|
|
|
12
16
|
file_size: number;
|
|
13
17
|
size?: string;
|
|
14
18
|
};
|
|
19
|
+
|
|
20
|
+
export type Migration = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
applied_at: string;
|
|
24
|
+
};
|
package/src/d1/utils.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { DEFAULT_MIGRATION_PATH, DEFAULT_MIGRATION_TABLE } from "./constants";
|
|
1
2
|
import { listDatabases } from "./list";
|
|
2
3
|
import type { Config } from "../config";
|
|
3
4
|
import type { Database } from "./types";
|
|
4
5
|
|
|
5
|
-
export function getDatabaseInfoFromConfig(
|
|
6
|
+
export function getDatabaseInfoFromConfig(
|
|
7
|
+
config: Config,
|
|
8
|
+
name: string
|
|
9
|
+
): Database | null {
|
|
6
10
|
for (const d1Database of config.d1_databases) {
|
|
7
11
|
if (
|
|
8
12
|
d1Database.database_id &&
|
|
@@ -12,6 +16,11 @@ export function getDatabaseInfoFromConfig(config: Config, name: string) {
|
|
|
12
16
|
uuid: d1Database.database_id,
|
|
13
17
|
binding: d1Database.binding,
|
|
14
18
|
name: d1Database.database_name,
|
|
19
|
+
migrationsTableName:
|
|
20
|
+
d1Database.migrations_table || DEFAULT_MIGRATION_TABLE,
|
|
21
|
+
migrationsFolderPath:
|
|
22
|
+
d1Database.migrations_dir || DEFAULT_MIGRATION_PATH,
|
|
23
|
+
internal_env: d1Database.database_internal_env,
|
|
15
24
|
};
|
|
16
25
|
}
|
|
17
26
|
}
|
package/src/dev/local.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import { realpathSync } from "node:fs";
|
|
|
4
4
|
import { readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import chalk from "chalk";
|
|
7
|
+
import getPort from "get-port";
|
|
7
8
|
import { npxImport } from "npx-import";
|
|
8
9
|
import { useState, useEffect, useRef } from "react";
|
|
9
10
|
import onExit from "signal-exit";
|
|
@@ -109,6 +110,7 @@ function useLocalWorker({
|
|
|
109
110
|
const local = useRef<ChildProcess>();
|
|
110
111
|
const experimentalLocalRef = useRef<Miniflare3Type>();
|
|
111
112
|
const removeSignalExitListener = useRef<() => void>();
|
|
113
|
+
const removeExperimentalLocalSignalExitListener = useRef<() => void>();
|
|
112
114
|
const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
|
|
113
115
|
|
|
114
116
|
useEffect(() => {
|
|
@@ -174,7 +176,7 @@ function useLocalWorker({
|
|
|
174
176
|
bundle,
|
|
175
177
|
});
|
|
176
178
|
|
|
177
|
-
const { forkOptions, miniflareCLIPath } = setupMiniflareOptions({
|
|
179
|
+
const { forkOptions, miniflareCLIPath, options } = setupMiniflareOptions({
|
|
178
180
|
workerName,
|
|
179
181
|
port,
|
|
180
182
|
scriptPath,
|
|
@@ -207,16 +209,27 @@ function useLocalWorker({
|
|
|
207
209
|
});
|
|
208
210
|
|
|
209
211
|
if (experimentalLocal) {
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
mf
|
|
218
|
-
|
|
219
|
-
|
|
212
|
+
const mf3Options = await transformLocalOptions(options, format, bundle);
|
|
213
|
+
const current = experimentalLocalRef.current;
|
|
214
|
+
if (current === undefined) {
|
|
215
|
+
// If we don't have an active Miniflare instance, create a new one
|
|
216
|
+
const Miniflare = await getMiniflare3Constructor();
|
|
217
|
+
if (abortController.signal.aborted) return;
|
|
218
|
+
const mf = new Miniflare(mf3Options);
|
|
219
|
+
experimentalLocalRef.current = mf;
|
|
220
|
+
removeExperimentalLocalSignalExitListener.current = onExit(() => {
|
|
221
|
+
logger.log("⎔ Shutting down experimental local server.");
|
|
222
|
+
mf.dispose();
|
|
223
|
+
experimentalLocalRef.current = undefined;
|
|
224
|
+
});
|
|
225
|
+
await mf.ready;
|
|
226
|
+
} else {
|
|
227
|
+
// Otherwise, reuse the existing instance with its loopback server
|
|
228
|
+
// and just update the options
|
|
229
|
+
if (abortController.signal.aborted) return;
|
|
230
|
+
logger.log("⎔ Reloading experimental local server.");
|
|
231
|
+
await current.setOptions(mf3Options);
|
|
232
|
+
}
|
|
220
233
|
return;
|
|
221
234
|
}
|
|
222
235
|
|
|
@@ -317,13 +330,6 @@ function useLocalWorker({
|
|
|
317
330
|
local.current?.kill();
|
|
318
331
|
local.current = undefined;
|
|
319
332
|
}
|
|
320
|
-
if (experimentalLocalRef.current) {
|
|
321
|
-
logger.log("⎔ Shutting down experimental local server.");
|
|
322
|
-
// Initialisation errors are also thrown asynchronously by dispose().
|
|
323
|
-
// The catch() above should've caught them though.
|
|
324
|
-
experimentalLocalRef.current?.dispose().catch(() => {});
|
|
325
|
-
experimentalLocalRef.current = undefined;
|
|
326
|
-
}
|
|
327
333
|
removeSignalExitListener.current?.();
|
|
328
334
|
removeSignalExitListener.current = undefined;
|
|
329
335
|
};
|
|
@@ -362,6 +368,26 @@ function useLocalWorker({
|
|
|
362
368
|
enablePagesAssetsServiceBinding,
|
|
363
369
|
experimentalLocal,
|
|
364
370
|
]);
|
|
371
|
+
|
|
372
|
+
// Rather than disposing the Miniflare instance on every reload, only dispose
|
|
373
|
+
// it if local mode is disabled and the `Local` component is unmounted. This
|
|
374
|
+
// allows us to use the more efficient `Miniflare#setOptions` on reload which
|
|
375
|
+
// retains internal state (e.g. the Miniflare loopback server).
|
|
376
|
+
useEffect(
|
|
377
|
+
() => () => {
|
|
378
|
+
if (experimentalLocalRef.current) {
|
|
379
|
+
logger.log("⎔ Shutting down experimental local server.");
|
|
380
|
+
// Initialisation errors are also thrown asynchronously by dispose().
|
|
381
|
+
// The catch() above should've caught them though.
|
|
382
|
+
experimentalLocalRef.current?.dispose().catch(() => {});
|
|
383
|
+
experimentalLocalRef.current = undefined;
|
|
384
|
+
}
|
|
385
|
+
removeExperimentalLocalSignalExitListener.current?.();
|
|
386
|
+
removeExperimentalLocalSignalExitListener.current = undefined;
|
|
387
|
+
},
|
|
388
|
+
[]
|
|
389
|
+
);
|
|
390
|
+
|
|
365
391
|
return { inspectorUrl };
|
|
366
392
|
}
|
|
367
393
|
|
|
@@ -512,9 +538,10 @@ export function setupMiniflareOptions({
|
|
|
512
538
|
}: SetupMiniflareOptionsProps): {
|
|
513
539
|
miniflareCLIPath: string;
|
|
514
540
|
forkOptions: string[];
|
|
541
|
+
options: MiniflareOptions;
|
|
515
542
|
} {
|
|
516
543
|
// It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
|
|
517
|
-
const options = {
|
|
544
|
+
const options: MiniflareOptions = {
|
|
518
545
|
name: workerName,
|
|
519
546
|
port,
|
|
520
547
|
scriptPath,
|
|
@@ -633,7 +660,7 @@ export function setupMiniflareOptions({
|
|
|
633
660
|
if (enablePagesAssetsServiceBinding) {
|
|
634
661
|
forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
|
|
635
662
|
}
|
|
636
|
-
return { miniflareCLIPath, forkOptions };
|
|
663
|
+
return { miniflareCLIPath, forkOptions, options };
|
|
637
664
|
}
|
|
638
665
|
|
|
639
666
|
export function setupNodeOptions({
|
|
@@ -656,19 +683,15 @@ export function setupNodeOptions({
|
|
|
656
683
|
return nodeOptions;
|
|
657
684
|
}
|
|
658
685
|
|
|
659
|
-
// Caching of the `npx-import`ed `@miniflare/tre` package
|
|
660
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
661
|
-
let Miniflare: typeof import("@miniflare/tre").Miniflare;
|
|
662
|
-
|
|
663
686
|
function arrayToObject(values: string[] = []): Record<string, string> {
|
|
664
687
|
return Object.fromEntries(values.map((value) => [value, value]));
|
|
665
688
|
}
|
|
666
689
|
|
|
667
|
-
export async function
|
|
690
|
+
export async function transformLocalOptions(
|
|
668
691
|
mf2Options: MiniflareOptions,
|
|
669
692
|
format: CfScriptFormat,
|
|
670
693
|
bundle: EsbuildBundle
|
|
671
|
-
): Promise<
|
|
694
|
+
): Promise<Miniflare3Options> {
|
|
672
695
|
const options: Miniflare3Options = {
|
|
673
696
|
...mf2Options,
|
|
674
697
|
// Miniflare 3 distinguishes between binding name and namespace/bucket IDs.
|
|
@@ -676,6 +699,8 @@ export async function setupExperimentalLocal(
|
|
|
676
699
|
// TODO: use defined KV preview ID if any
|
|
677
700
|
kvNamespaces: arrayToObject(mf2Options.kvNamespaces),
|
|
678
701
|
r2Buckets: arrayToObject(mf2Options.r2Buckets),
|
|
702
|
+
inspectorPort: await getPort({ port: 9229 }),
|
|
703
|
+
verbose: true,
|
|
679
704
|
};
|
|
680
705
|
|
|
681
706
|
if (format === "modules") {
|
|
@@ -704,14 +729,18 @@ export async function setupExperimentalLocal(
|
|
|
704
729
|
];
|
|
705
730
|
}
|
|
706
731
|
|
|
707
|
-
|
|
732
|
+
return options;
|
|
733
|
+
}
|
|
708
734
|
|
|
735
|
+
// Caching of the `npx-import`ed `@miniflare/tre` package
|
|
736
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
737
|
+
let Miniflare: typeof import("@miniflare/tre").Miniflare;
|
|
738
|
+
export async function getMiniflare3Constructor(): Promise<typeof Miniflare> {
|
|
709
739
|
if (Miniflare === undefined) {
|
|
710
740
|
({ Miniflare } = await npxImport<
|
|
711
741
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
712
742
|
typeof import("@miniflare/tre")
|
|
713
|
-
>("@miniflare/tre@next"));
|
|
743
|
+
>("@miniflare/tre@3.0.0-next.5"));
|
|
714
744
|
}
|
|
715
|
-
|
|
716
|
-
return new Miniflare(options);
|
|
745
|
+
return Miniflare;
|
|
717
746
|
}
|