wrangler 2.1.6 → 2.1.8
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/miniflare-dist/index.mjs +5 -20
- package/package.json +14 -3
- package/src/__tests__/api-dev.test.ts +20 -0
- package/src/__tests__/configuration.test.ts +125 -22
- package/src/__tests__/dev.test.tsx +0 -2
- package/src/__tests__/helpers/mock-oauth-flow.ts +4 -2
- package/src/__tests__/index.test.ts +2 -0
- package/src/__tests__/paths.test.ts +23 -1
- package/src/__tests__/publish.test.ts +8 -10
- package/src/__tests__/user.test.ts +4 -4
- package/src/__tests__/whoami.test.tsx +0 -1
- package/src/__tests__/worker-namespace.test.ts +102 -112
- package/src/api/dev.ts +12 -12
- package/src/bundle.ts +59 -1
- package/src/cfetch/internal.ts +37 -21
- package/src/config/environment.ts +20 -0
- package/src/config/index.ts +32 -0
- package/src/config/validation.ts +59 -0
- package/src/config-cache.ts +1 -1
- package/src/create-worker-upload-form.ts +9 -0
- package/src/d1/backups.tsx +212 -0
- package/src/d1/create.tsx +54 -0
- package/src/d1/delete.tsx +56 -0
- package/src/d1/execute.tsx +294 -0
- package/src/d1/formatTimeAgo.ts +14 -0
- package/src/d1/index.ts +75 -0
- package/src/d1/list.tsx +48 -0
- package/src/d1/options.ts +12 -0
- package/src/d1/types.tsx +14 -0
- package/src/d1/utils.ts +39 -0
- package/src/dev/dev.tsx +30 -3
- package/src/dev/get-local-persistence-path.tsx +31 -0
- package/src/dev/local.tsx +73 -11
- package/src/dev/start-server.ts +6 -3
- package/src/dev/use-esbuild.ts +12 -1
- package/src/dev.tsx +48 -29
- package/src/dialogs.tsx +4 -0
- package/src/environment-variables.ts +17 -2
- package/src/index.tsx +18 -16
- package/src/logger.ts +11 -4
- package/src/miniflare-cli/index.ts +11 -16
- package/src/pages/dev.tsx +13 -9
- package/src/paths.ts +30 -4
- package/src/proxy.ts +21 -1
- package/src/publish.ts +7 -0
- package/src/user/user.tsx +1 -0
- package/src/worker.ts +30 -0
- package/templates/d1-beta-facade.js +174 -0
- package/templates/experimental-local-cache-stubs.js +27 -0
- package/wrangler-dist/cli.d.ts +438 -7
- package/wrangler-dist/cli.js +11679 -3911
- package/src/miniflare-cli/enum-keys.ts +0 -17
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdir } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { render, Static, Text } from "ink";
|
|
6
|
+
import Table from "ink-table";
|
|
7
|
+
import { npxImport } from "npx-import";
|
|
8
|
+
import React from "react";
|
|
9
|
+
import { fetchResult } from "../cfetch";
|
|
10
|
+
import { withConfig } from "../config";
|
|
11
|
+
import { getLocalPersistencePath } from "../dev/get-local-persistence-path";
|
|
12
|
+
import { confirm, logDim } from "../dialogs";
|
|
13
|
+
import { logger } from "../logger";
|
|
14
|
+
import { readableRelative } from "../paths";
|
|
15
|
+
import { requireAuth } from "../user";
|
|
16
|
+
import { Name } from "./options";
|
|
17
|
+
import {
|
|
18
|
+
d1BetaWarning,
|
|
19
|
+
getDatabaseByNameOrBinding,
|
|
20
|
+
getDatabaseInfoFromConfig,
|
|
21
|
+
} from "./utils";
|
|
22
|
+
import type { Config } from "../config";
|
|
23
|
+
import type { Database } from "./types";
|
|
24
|
+
import type splitSqlQuery from "@databases/split-sql-query";
|
|
25
|
+
import type { SQL, SQLQuery } from "@databases/sql";
|
|
26
|
+
import type { Statement as StatementType } from "@miniflare/d1";
|
|
27
|
+
import type { createSQLiteDB as createSQLiteDBType } from "@miniflare/shared";
|
|
28
|
+
import type { Argv } from "yargs";
|
|
29
|
+
|
|
30
|
+
type MiniflareNpxImportTypes = [
|
|
31
|
+
{
|
|
32
|
+
Statement: typeof StatementType;
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
createSQLiteDB: typeof createSQLiteDBType;
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
type ExecuteArgs = {
|
|
40
|
+
config?: string;
|
|
41
|
+
name: string;
|
|
42
|
+
file?: string;
|
|
43
|
+
command?: string;
|
|
44
|
+
local?: boolean;
|
|
45
|
+
"persist-to"?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type QueryResult = {
|
|
49
|
+
results: Record<string, string | number | boolean>[];
|
|
50
|
+
success: boolean;
|
|
51
|
+
duration: number;
|
|
52
|
+
query?: string;
|
|
53
|
+
};
|
|
54
|
+
// Max number of bytes to send in a single /execute call
|
|
55
|
+
const QUERY_LIMIT = 1_000_000; // 1MB
|
|
56
|
+
|
|
57
|
+
export function Options(yargs: Argv): Argv<ExecuteArgs> {
|
|
58
|
+
return Name(yargs)
|
|
59
|
+
.option("local", {
|
|
60
|
+
describe:
|
|
61
|
+
"Execute commands/files against a local DB for use with wrangler dev",
|
|
62
|
+
type: "boolean",
|
|
63
|
+
})
|
|
64
|
+
.option("file", {
|
|
65
|
+
describe: "A .sql file to injest",
|
|
66
|
+
type: "string",
|
|
67
|
+
})
|
|
68
|
+
.option("command", {
|
|
69
|
+
describe: "A single SQL statement to execute",
|
|
70
|
+
type: "string",
|
|
71
|
+
})
|
|
72
|
+
.option("persist-to", {
|
|
73
|
+
describe: "Specify directory to use for local persistence (for --local)",
|
|
74
|
+
type: "string",
|
|
75
|
+
requiresArg: true,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function shorten(query: string | undefined, length: number) {
|
|
80
|
+
return query && query.length > length
|
|
81
|
+
? query.slice(0, length) + "..."
|
|
82
|
+
: query;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const Handler = withConfig<ExecuteArgs>(
|
|
86
|
+
async ({ config, name, file, command, local, persistTo }): Promise<void> => {
|
|
87
|
+
logger.log(d1BetaWarning);
|
|
88
|
+
if (file && command)
|
|
89
|
+
return console.error(`Error: can't provide both --command and --file.`);
|
|
90
|
+
const { parser, splitter } = await loadSqlUtils();
|
|
91
|
+
|
|
92
|
+
const sql = file
|
|
93
|
+
? parser.file(file)
|
|
94
|
+
: command
|
|
95
|
+
? parser.__dangerous__rawValue(command)
|
|
96
|
+
: null;
|
|
97
|
+
|
|
98
|
+
if (!sql) throw new Error(`Error: must provide --command or --file.`);
|
|
99
|
+
if (persistTo && !local)
|
|
100
|
+
throw new Error(`Error: can't use --persist-to without --local`);
|
|
101
|
+
|
|
102
|
+
const isInteractive = process.stdout.isTTY;
|
|
103
|
+
const response: QueryResult[] | null = local
|
|
104
|
+
? await executeLocally(
|
|
105
|
+
config,
|
|
106
|
+
name,
|
|
107
|
+
isInteractive,
|
|
108
|
+
splitSql(splitter, sql),
|
|
109
|
+
persistTo
|
|
110
|
+
)
|
|
111
|
+
: await executeRemotely(
|
|
112
|
+
config,
|
|
113
|
+
name,
|
|
114
|
+
isInteractive,
|
|
115
|
+
batchSplit(splitter, sql)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Early exit if prompt rejected
|
|
119
|
+
if (!response) return;
|
|
120
|
+
|
|
121
|
+
if (isInteractive) {
|
|
122
|
+
render(
|
|
123
|
+
<Static items={response}>
|
|
124
|
+
{(result) => {
|
|
125
|
+
const { results, duration, query } = result;
|
|
126
|
+
|
|
127
|
+
if (Array.isArray(results) && results.length > 0) {
|
|
128
|
+
const shortQuery = shorten(query, 48);
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
{shortQuery ? <Text dimColor>{shortQuery}</Text> : null}
|
|
132
|
+
<Table data={results}></Table>
|
|
133
|
+
</>
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
const shortQuery = shorten(query, 24);
|
|
137
|
+
return (
|
|
138
|
+
<Text>
|
|
139
|
+
Executed{" "}
|
|
140
|
+
{shortQuery ? <Text dimColor>{shortQuery}</Text> : "command"}{" "}
|
|
141
|
+
in {duration}ms.
|
|
142
|
+
</Text>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
}}
|
|
146
|
+
</Static>
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
console.log(JSON.stringify(response, null, 2));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
async function executeLocally(
|
|
155
|
+
config: Config,
|
|
156
|
+
name: string,
|
|
157
|
+
isInteractive: boolean,
|
|
158
|
+
queries: string[],
|
|
159
|
+
persistTo: string | undefined
|
|
160
|
+
) {
|
|
161
|
+
const localDB = getDatabaseInfoFromConfig(config, name);
|
|
162
|
+
if (!localDB) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
`Can't find a DB with name/binding '${name}' in local config. Check info in wrangler.toml...`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const persistencePath = getLocalPersistencePath(
|
|
169
|
+
persistTo,
|
|
170
|
+
true,
|
|
171
|
+
config.configPath
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const dbDir = path.join(persistencePath, "d1");
|
|
175
|
+
const dbPath = path.join(dbDir, `${localDB.binding}.sqlite3`);
|
|
176
|
+
const [{ Statement }, { createSQLiteDB }] =
|
|
177
|
+
await npxImport<MiniflareNpxImportTypes>(
|
|
178
|
+
["@miniflare/d1", "@miniflare/shared"],
|
|
179
|
+
logDim
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (!existsSync(dbDir) && isInteractive) {
|
|
183
|
+
const ok = await confirm(
|
|
184
|
+
`About to create ${readableRelative(dbPath)}, ok?`
|
|
185
|
+
);
|
|
186
|
+
if (!ok) return null;
|
|
187
|
+
await mkdir(dbDir, { recursive: true });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log(`Loading DB at ${readableRelative(dbPath)}`);
|
|
191
|
+
const db = await createSQLiteDB(dbPath);
|
|
192
|
+
|
|
193
|
+
const results: QueryResult[] = [];
|
|
194
|
+
for (const sql of queries) {
|
|
195
|
+
const statement = new Statement(db, sql);
|
|
196
|
+
results.push((await statement.all()) as QueryResult);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return results;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function executeRemotely(
|
|
203
|
+
config: Config,
|
|
204
|
+
name: string,
|
|
205
|
+
isInteractive: boolean,
|
|
206
|
+
batches: string[]
|
|
207
|
+
) {
|
|
208
|
+
if (batches.length > 1) {
|
|
209
|
+
const warning =
|
|
210
|
+
chalk.red(`WARNING! `) +
|
|
211
|
+
`Too much SQL to send at once, this execution will be sent as ${batches.length} batches.`;
|
|
212
|
+
|
|
213
|
+
if (isInteractive) {
|
|
214
|
+
const ok = await confirm(
|
|
215
|
+
`${warning}\nNOTE: each batch is sent individually and may leave your DB in an unexpected state if a later batch fails.\n${chalk.green(
|
|
216
|
+
`Make sure you have a recent backup.`
|
|
217
|
+
)}\nOk to proceed?`
|
|
218
|
+
);
|
|
219
|
+
if (!ok) return null;
|
|
220
|
+
} else {
|
|
221
|
+
console.error(warning);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const accountId = await requireAuth({});
|
|
226
|
+
const db: Database = await getDatabaseByNameOrBinding(
|
|
227
|
+
config,
|
|
228
|
+
accountId,
|
|
229
|
+
name
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (isInteractive) {
|
|
233
|
+
console.log(`Executing on ${name} (${db.uuid}):`);
|
|
234
|
+
} else {
|
|
235
|
+
// Pipe to error so we don't break jq
|
|
236
|
+
console.error(`Executing on ${name} (${db.uuid}):`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const results: QueryResult[] = [];
|
|
240
|
+
for (const sql of batches) {
|
|
241
|
+
results.push(
|
|
242
|
+
...(await fetchResult<QueryResult[]>(
|
|
243
|
+
`/accounts/${accountId}/d1/database/${db.uuid}/query`,
|
|
244
|
+
{
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({ sql }),
|
|
250
|
+
}
|
|
251
|
+
))
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return results;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function splitSql(splitter: (query: SQLQuery) => SQLQuery[], sql: SQLQuery) {
|
|
258
|
+
// We have no interpolations, so convert everything to text
|
|
259
|
+
return splitter(sql).map(
|
|
260
|
+
(q) =>
|
|
261
|
+
q.format({
|
|
262
|
+
escapeIdentifier: (_) => "",
|
|
263
|
+
formatValue: (_, __) => ({ placeholder: "", value: "" }),
|
|
264
|
+
}).text
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function batchSplit(splitter: typeof splitSqlQuery, sql: SQLQuery) {
|
|
269
|
+
const queries = splitSql(splitter, sql);
|
|
270
|
+
|
|
271
|
+
const batches: string[] = [];
|
|
272
|
+
for (const query of queries) {
|
|
273
|
+
const last = batches.at(-1);
|
|
274
|
+
if (!last || last.length + query.length > QUERY_LIMIT) {
|
|
275
|
+
batches.push(query);
|
|
276
|
+
} else {
|
|
277
|
+
batches.splice(-1, 1, [last, query].join("; "));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return batches;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function loadSqlUtils() {
|
|
284
|
+
const [
|
|
285
|
+
{ default: parser },
|
|
286
|
+
{
|
|
287
|
+
// No idea why this is doubly-nested, see https://github.com/ForbesLindesay/atdatabases/issues/255
|
|
288
|
+
default: { default: splitter },
|
|
289
|
+
},
|
|
290
|
+
] = await npxImport<
|
|
291
|
+
[{ default: SQL }, { default: { default: typeof splitSqlQuery } }]
|
|
292
|
+
>(["@databases/sql@3.2.0", "@databases/split-sql-query@1.0.3"], logDim);
|
|
293
|
+
return { parser, splitter };
|
|
294
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import TimeAgo from "javascript-time-ago";
|
|
2
|
+
import en from "javascript-time-ago/locale/en";
|
|
3
|
+
import prettyBytes from "pretty-bytes";
|
|
4
|
+
TimeAgo.addDefaultLocale(en);
|
|
5
|
+
const timeAgo = new TimeAgo("en-US");
|
|
6
|
+
|
|
7
|
+
export const formatTimeAgo = (date: Date): string => {
|
|
8
|
+
const result = timeAgo.format(date);
|
|
9
|
+
return Array.isArray(result) ? result[0] : result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const formatBytes = (bytes: number): string => {
|
|
13
|
+
return prettyBytes(bytes);
|
|
14
|
+
};
|
package/src/d1/index.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as Backups from "./backups";
|
|
2
|
+
import * as Create from "./create";
|
|
3
|
+
import * as Delete from "./delete";
|
|
4
|
+
import * as Execute from "./execute";
|
|
5
|
+
import * as List from "./list";
|
|
6
|
+
import { d1BetaWarning } from "./utils";
|
|
7
|
+
import type { Argv } from "yargs";
|
|
8
|
+
|
|
9
|
+
export const d1api = (yargs: Argv) => {
|
|
10
|
+
return (
|
|
11
|
+
yargs
|
|
12
|
+
.command("list", "List D1 databases", List.Options, List.Handler)
|
|
13
|
+
.command(
|
|
14
|
+
"create <name>",
|
|
15
|
+
"Create D1 database",
|
|
16
|
+
Create.Options,
|
|
17
|
+
Create.Handler
|
|
18
|
+
)
|
|
19
|
+
.command(
|
|
20
|
+
"delete <name>",
|
|
21
|
+
"Delete D1 database",
|
|
22
|
+
Delete.Options,
|
|
23
|
+
Delete.Handler
|
|
24
|
+
)
|
|
25
|
+
.command("backup", "Interact with D1 Backups", (yargs2) =>
|
|
26
|
+
yargs2
|
|
27
|
+
.command(
|
|
28
|
+
"list <name>",
|
|
29
|
+
"List your D1 backups",
|
|
30
|
+
Backups.ListOptions,
|
|
31
|
+
Backups.ListHandler
|
|
32
|
+
)
|
|
33
|
+
.command(
|
|
34
|
+
"create <name>",
|
|
35
|
+
"Create a new D1 backup",
|
|
36
|
+
Backups.CreateOptions,
|
|
37
|
+
Backups.CreateHandler
|
|
38
|
+
)
|
|
39
|
+
.command(
|
|
40
|
+
"restore <name> <backup-id>",
|
|
41
|
+
"Restore a DB backup",
|
|
42
|
+
Backups.RestoreOptions,
|
|
43
|
+
Backups.RestoreHandler
|
|
44
|
+
)
|
|
45
|
+
.command(
|
|
46
|
+
"download <name> <backup-id>",
|
|
47
|
+
"Download a DB backup",
|
|
48
|
+
Backups.DownloadOptions,
|
|
49
|
+
Backups.DownloadHandler
|
|
50
|
+
)
|
|
51
|
+
.epilogue(d1BetaWarning)
|
|
52
|
+
)
|
|
53
|
+
// .command(
|
|
54
|
+
// "console <name>",
|
|
55
|
+
// "Open a Console on a D1 database",
|
|
56
|
+
// (d1CreateYargs) => {
|
|
57
|
+
// return d1CreateYargs.positional("name", {
|
|
58
|
+
// describe: "The name of the DB",
|
|
59
|
+
// type: "string",
|
|
60
|
+
// demandOption: true,
|
|
61
|
+
// });
|
|
62
|
+
// },
|
|
63
|
+
// async (_) => {
|
|
64
|
+
// // TODO
|
|
65
|
+
// }
|
|
66
|
+
// )
|
|
67
|
+
.command(
|
|
68
|
+
"execute <name>",
|
|
69
|
+
"Executed command or SQL file",
|
|
70
|
+
Execute.Options,
|
|
71
|
+
Execute.Handler
|
|
72
|
+
)
|
|
73
|
+
.epilogue(d1BetaWarning)
|
|
74
|
+
);
|
|
75
|
+
};
|
package/src/d1/list.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { render } from "ink";
|
|
2
|
+
import Table from "ink-table";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { fetchResult } from "../cfetch";
|
|
5
|
+
import { logger } from "../logger";
|
|
6
|
+
import { requireAuth } from "../user";
|
|
7
|
+
import { d1BetaWarning } from "./utils";
|
|
8
|
+
import type { Database } from "./types";
|
|
9
|
+
import type { ArgumentsCamelCase, Argv } from "yargs";
|
|
10
|
+
|
|
11
|
+
type ListArgs = Record<string, never>;
|
|
12
|
+
|
|
13
|
+
export function Options(d1ListYargs: Argv): Argv<ListArgs> {
|
|
14
|
+
return d1ListYargs.epilogue(d1BetaWarning);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function Handler(_: ArgumentsCamelCase<ListArgs>): Promise<void> {
|
|
18
|
+
const accountId = await requireAuth({});
|
|
19
|
+
logger.log(d1BetaWarning);
|
|
20
|
+
|
|
21
|
+
const dbs: Array<Database> = await listDatabases(accountId);
|
|
22
|
+
|
|
23
|
+
render(<Table data={dbs}></Table>);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const listDatabases = async (
|
|
27
|
+
accountId: string
|
|
28
|
+
): Promise<Array<Database>> => {
|
|
29
|
+
const pageSize = 10;
|
|
30
|
+
let page = 1;
|
|
31
|
+
const results = [];
|
|
32
|
+
while (results.length % pageSize === 0) {
|
|
33
|
+
const json: Array<Database> = await fetchResult(
|
|
34
|
+
`/accounts/${accountId}/d1/database`,
|
|
35
|
+
{},
|
|
36
|
+
new URLSearchParams({
|
|
37
|
+
per_page: pageSize.toString(),
|
|
38
|
+
page: page.toString(),
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
page++;
|
|
42
|
+
results.push(...json);
|
|
43
|
+
if (json.length < pageSize) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { d1BetaWarning } from "./utils";
|
|
2
|
+
import type { Argv } from "yargs";
|
|
3
|
+
|
|
4
|
+
export function Name(yargs: Argv) {
|
|
5
|
+
return yargs
|
|
6
|
+
.positional("name", {
|
|
7
|
+
describe: "The name or binding of the DB",
|
|
8
|
+
type: "string",
|
|
9
|
+
demandOption: true,
|
|
10
|
+
})
|
|
11
|
+
.epilogue(d1BetaWarning);
|
|
12
|
+
}
|
package/src/d1/types.tsx
ADDED
package/src/d1/utils.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { listDatabases } from "./list";
|
|
2
|
+
import type { Config } from "../config";
|
|
3
|
+
import type { Database } from "./types";
|
|
4
|
+
|
|
5
|
+
export function getDatabaseInfoFromConfig(config: Config, name: string) {
|
|
6
|
+
for (const d1Database of config.d1_databases) {
|
|
7
|
+
if (
|
|
8
|
+
d1Database.database_id &&
|
|
9
|
+
(name === d1Database.database_name || name === d1Database.binding)
|
|
10
|
+
) {
|
|
11
|
+
return {
|
|
12
|
+
uuid: d1Database.database_id,
|
|
13
|
+
binding: d1Database.binding,
|
|
14
|
+
name: d1Database.database_name,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getDatabaseByNameOrBinding = async (
|
|
22
|
+
config: Config,
|
|
23
|
+
accountId: string,
|
|
24
|
+
name: string
|
|
25
|
+
): Promise<Database> => {
|
|
26
|
+
const dbFromConfig = getDatabaseInfoFromConfig(config, name);
|
|
27
|
+
if (dbFromConfig) return dbFromConfig;
|
|
28
|
+
|
|
29
|
+
const allDBs = await listDatabases(accountId);
|
|
30
|
+
const matchingDB = allDBs.find((db) => db.name === name);
|
|
31
|
+
if (!matchingDB) {
|
|
32
|
+
throw new Error(`Couldn't find DB with name '${name}'`);
|
|
33
|
+
}
|
|
34
|
+
return matchingDB;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const d1BetaWarning = process.env.NO_D1_WARNING
|
|
38
|
+
? ""
|
|
39
|
+
: "🚧 'wrangler d1 <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose";
|
package/src/dev/dev.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import { watch } from "chokidar";
|
|
|
5
5
|
import clipboardy from "clipboardy";
|
|
6
6
|
import commandExists from "command-exists";
|
|
7
7
|
import { Box, Text, useApp, useInput, useStdin } from "ink";
|
|
8
|
-
import React, { useState, useEffect, useRef } from "react";
|
|
8
|
+
import React, { useState, useEffect, useRef, useMemo } from "react";
|
|
9
9
|
import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
|
|
10
10
|
import onExit from "signal-exit";
|
|
11
11
|
import tmp from "tmp-promise";
|
|
@@ -145,7 +145,6 @@ export type DevProps = {
|
|
|
145
145
|
host: string | undefined;
|
|
146
146
|
routes: Route[] | undefined;
|
|
147
147
|
inspect: boolean;
|
|
148
|
-
logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
|
|
149
148
|
logPrefix?: string;
|
|
150
149
|
onReady: ((ip: string, port: number) => void) | undefined;
|
|
151
150
|
showInteractiveDevSession: boolean | undefined;
|
|
@@ -154,6 +153,7 @@ export type DevProps = {
|
|
|
154
153
|
firstPartyWorker: boolean | undefined;
|
|
155
154
|
sendMetrics: boolean | undefined;
|
|
156
155
|
testScheduled: boolean | undefined;
|
|
156
|
+
experimentalLocal: boolean | undefined;
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
export function DevImplementation(props: DevProps): JSX.Element {
|
|
@@ -214,12 +214,36 @@ function InteractiveDevSession(props: DevProps) {
|
|
|
214
214
|
|
|
215
215
|
type DevSessionProps = DevProps & {
|
|
216
216
|
local: boolean;
|
|
217
|
+
experimentalLocal?: boolean;
|
|
217
218
|
};
|
|
218
219
|
|
|
219
220
|
function DevSession(props: DevSessionProps) {
|
|
220
221
|
useCustomBuild(props.entry, props.build);
|
|
221
222
|
|
|
222
223
|
const directory = useTmpDir();
|
|
224
|
+
const handleError = useErrorHandler();
|
|
225
|
+
|
|
226
|
+
// Note: when D1 is out of beta, this (and all instances of `betaD1Shims`) can be removed.
|
|
227
|
+
// Additionally, useMemo is used so that new arrays aren't created on every render
|
|
228
|
+
// cause re-rendering further down.
|
|
229
|
+
const betaD1Shims = useMemo(
|
|
230
|
+
() => props.bindings.d1_databases?.map((db) => db.binding),
|
|
231
|
+
[props.bindings.d1_databases]
|
|
232
|
+
);
|
|
233
|
+
const everyD1BindingHasPreview = props.bindings.d1_databases?.every(
|
|
234
|
+
(binding) => binding.preview_database_id
|
|
235
|
+
);
|
|
236
|
+
if (
|
|
237
|
+
betaD1Shims &&
|
|
238
|
+
betaD1Shims.length > 0 &&
|
|
239
|
+
!(props.local || everyD1BindingHasPreview)
|
|
240
|
+
) {
|
|
241
|
+
handleError(
|
|
242
|
+
new Error(
|
|
243
|
+
"D1 bindings require dev --local or preview_database_id for now"
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
223
247
|
|
|
224
248
|
const workerDefinitions = useDevRegistry(
|
|
225
249
|
props.name,
|
|
@@ -240,6 +264,7 @@ function DevSession(props: DevSessionProps) {
|
|
|
240
264
|
tsconfig: props.tsconfig,
|
|
241
265
|
minify: props.minify,
|
|
242
266
|
nodeCompat: props.nodeCompat,
|
|
267
|
+
betaD1Shims,
|
|
243
268
|
define: props.define,
|
|
244
269
|
noBundle: props.noBundle,
|
|
245
270
|
assets: props.assetsConfig,
|
|
@@ -247,9 +272,11 @@ function DevSession(props: DevSessionProps) {
|
|
|
247
272
|
services: props.bindings.services,
|
|
248
273
|
durableObjects: props.bindings.durable_objects || { bindings: [] },
|
|
249
274
|
firstPartyWorkerDevFacade: props.firstPartyWorker,
|
|
275
|
+
local: props.local,
|
|
250
276
|
// Enable the bundling to know whether we are using dev or publish
|
|
251
277
|
targetConsumer: "dev",
|
|
252
278
|
testScheduled: props.testScheduled ?? false,
|
|
279
|
+
experimentalLocalStubCache: props.local && props.experimentalLocal,
|
|
253
280
|
});
|
|
254
281
|
|
|
255
282
|
return props.local ? (
|
|
@@ -272,11 +299,11 @@ function DevSession(props: DevSessionProps) {
|
|
|
272
299
|
crons={props.crons}
|
|
273
300
|
localProtocol={props.localProtocol}
|
|
274
301
|
localUpstream={props.localUpstream}
|
|
275
|
-
logLevel={props.logLevel}
|
|
276
302
|
logPrefix={props.logPrefix}
|
|
277
303
|
inspect={props.inspect}
|
|
278
304
|
onReady={props.onReady}
|
|
279
305
|
enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
|
|
306
|
+
experimentalLocal={props.experimentalLocal}
|
|
280
307
|
/>
|
|
281
308
|
) : (
|
|
282
309
|
<Remote
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
export function getLocalPersistencePath(
|
|
4
|
+
persistTo: string | undefined,
|
|
5
|
+
doPersist: true,
|
|
6
|
+
configPath: string | undefined
|
|
7
|
+
): string;
|
|
8
|
+
|
|
9
|
+
export function getLocalPersistencePath(
|
|
10
|
+
persistTo: string | undefined,
|
|
11
|
+
doPersist: boolean,
|
|
12
|
+
configPath: string | undefined
|
|
13
|
+
): string | null;
|
|
14
|
+
|
|
15
|
+
export function getLocalPersistencePath(
|
|
16
|
+
persistTo: string | undefined,
|
|
17
|
+
doPersist: boolean,
|
|
18
|
+
configPath: string | undefined
|
|
19
|
+
) {
|
|
20
|
+
return persistTo
|
|
21
|
+
? // If path specified, always treat it as relative to cwd()
|
|
22
|
+
path.resolve(process.cwd(), persistTo)
|
|
23
|
+
: doPersist
|
|
24
|
+
? // If just flagged on, treat it as relative to wrangler.toml,
|
|
25
|
+
// if one can be found, otherwise cwd()
|
|
26
|
+
path.resolve(
|
|
27
|
+
configPath ? path.dirname(configPath) : process.cwd(),
|
|
28
|
+
".wrangler/state"
|
|
29
|
+
)
|
|
30
|
+
: null;
|
|
31
|
+
}
|