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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.2.2",
3
+ "version": "2.2.3",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -119,7 +119,7 @@
119
119
  "@databases/sql": "^3.2.0",
120
120
  "@iarna/toml": "^3.0.0",
121
121
  "@microsoft/api-extractor": "^7.28.3",
122
- "@miniflare/tre": "3.0.0-next.2",
122
+ "@miniflare/tre": "^3.0.0-next.5",
123
123
  "@types/better-sqlite3": "^7.6.0",
124
124
  "@types/busboy": "^1.5.0",
125
125
  "@types/command-exists": "^1.2.0",
@@ -21,11 +21,12 @@ describe("d1", () => {
21
21
  šŸ—„ Interact with a D1 database
22
22
 
23
23
  Commands:
24
- wrangler d1 list List D1 databases
25
- wrangler d1 create <name> Create D1 database
26
- wrangler d1 delete <name> Delete D1 database
27
- wrangler d1 backup Interact with D1 Backups
28
- wrangler d1 execute <name> Executed command or SQL file
24
+ wrangler d1 list List D1 databases
25
+ wrangler d1 create <name> Create D1 database
26
+ wrangler d1 delete <name> Delete D1 database
27
+ wrangler d1 backup Interact with D1 Backups
28
+ wrangler d1 execute <database> Executed command or SQL file
29
+ wrangler d1 migrations Interact with D1 Migrations
29
30
 
30
31
  Flags:
31
32
  -c, --config Path to .toml configuration file [string]
@@ -54,11 +55,12 @@ describe("d1", () => {
54
55
  šŸ—„ Interact with a D1 database
55
56
 
56
57
  Commands:
57
- wrangler d1 list List D1 databases
58
- wrangler d1 create <name> Create D1 database
59
- wrangler d1 delete <name> Delete D1 database
60
- wrangler d1 backup Interact with D1 Backups
61
- wrangler d1 execute <name> Executed command or SQL file
58
+ wrangler d1 list List D1 databases
59
+ wrangler d1 create <name> Create D1 database
60
+ wrangler d1 delete <name> Delete D1 database
61
+ wrangler d1 backup Interact with D1 Backups
62
+ wrangler d1 execute <database> Executed command or SQL file
63
+ wrangler d1 migrations Interact with D1 Migrations
62
64
 
63
65
  Flags:
64
66
  -c, --config Path to .toml configuration file [string]
@@ -55,7 +55,7 @@ describe("generate", () => {
55
55
  `);
56
56
  });
57
57
 
58
- it("auto-increments the worker directory name", async () => {
58
+ it.skip("auto-increments the worker directory name", async () => {
59
59
  fs.mkdirSync("my-worker");
60
60
 
61
61
  expect(fs.existsSync("my-worker-1")).toBe(false);
@@ -104,7 +104,7 @@ describe("generate", () => {
104
104
  });
105
105
 
106
106
  describe("cloning", () => {
107
- it("clones a cloudflare template with sparse checkouts", async () => {
107
+ it.skip("clones a cloudflare template with sparse checkouts", async () => {
108
108
  await expect(
109
109
  runWrangler("generate my-worker worker-typescript")
110
110
  ).resolves.toBeUndefined();
@@ -143,7 +143,7 @@ describe("generate", () => {
143
143
  });
144
144
  });
145
145
 
146
- it("clones a user/repo/path/to/subdirectory template", async () => {
146
+ it.skip("clones a user/repo/path/to/subdirectory template", async () => {
147
147
  await expect(
148
148
  runWrangler("generate my-worker cloudflare/templates/worker-typescript")
149
149
  ).resolves.toBeUndefined();
@@ -42,7 +42,7 @@ describe("wrangler", () => {
42
42
  wrangler kv:key šŸ”‘ Individually manage Workers KV key-value pairs
43
43
  wrangler kv:bulk šŸ’Ŗ Interact with multiple Workers KV key-value pairs at once
44
44
  wrangler pages āš”ļø Configure Cloudflare Pages
45
- wrangler queues šŸ†€ Configure Workers Queues
45
+ wrangler queues šŸ‡¶ Configure Workers Queues
46
46
  wrangler r2 šŸ“¦ Interact with an R2 store
47
47
  wrangler dispatch-namespace šŸ“¦ Interact with a dispatch namespace
48
48
  wrangler d1 šŸ—„ Interact with a D1 database
@@ -87,7 +87,7 @@ describe("wrangler", () => {
87
87
  wrangler kv:key šŸ”‘ Individually manage Workers KV key-value pairs
88
88
  wrangler kv:bulk šŸ’Ŗ Interact with multiple Workers KV key-value pairs at once
89
89
  wrangler pages āš”ļø Configure Cloudflare Pages
90
- wrangler queues šŸ†€ Configure Workers Queues
90
+ wrangler queues šŸ‡¶ Configure Workers Queues
91
91
  wrangler r2 šŸ“¦ Interact with an R2 store
92
92
  wrangler dispatch-namespace šŸ“¦ Interact with a dispatch namespace
93
93
  wrangler d1 šŸ—„ Interact with a D1 database
@@ -22,7 +22,7 @@ describe("wrangler", () => {
22
22
  expect(std.out).toMatchInlineSnapshot(`
23
23
  "wrangler queues
24
24
 
25
- šŸ†€ Configure Workers Queues
25
+ šŸ‡¶ Configure Workers Queues
26
26
 
27
27
  Commands:
28
28
  wrangler queues list List Queues
@@ -411,6 +411,12 @@ interface EnvironmentNonInheritable {
411
411
  database_id: string;
412
412
  /** The UUID of this D1 database for Wrangler Dev (if specified). */
413
413
  preview_database_id?: string;
414
+ /** The name of the migrations table for this D1 database (defaults to 'd1_migrations'). */
415
+ migrations_table?: string;
416
+ /** The path to the directory of migrations for this D1 database (defaults to './migrations'). */
417
+ migrations_dir?: string;
418
+ /** Internal use only. */
419
+ database_internal_env?: string;
414
420
  }[];
415
421
 
416
422
  /**
@@ -41,7 +41,7 @@ type WorkerMetadataBinding =
41
41
  }
42
42
  | { type: "queue"; name: string; queue_name: string }
43
43
  | { type: "r2_bucket"; name: string; bucket_name: string }
44
- | { type: "d1"; name: string; id: string }
44
+ | { type: "d1"; name: string; id: string; internalEnv?: string }
45
45
  | { type: "service"; name: string; service: string; environment?: string }
46
46
  | { type: "namespace"; name: string; namespace: string }
47
47
  | {
@@ -129,13 +129,16 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
129
129
  });
130
130
  });
131
131
 
132
- bindings.d1_databases?.forEach(({ binding, database_id }) => {
133
- metadataBindings.push({
134
- name: binding,
135
- type: "d1",
136
- id: database_id,
137
- });
138
- });
132
+ bindings.d1_databases?.forEach(
133
+ ({ binding, database_id, database_internal_env }) => {
134
+ metadataBindings.push({
135
+ name: binding,
136
+ type: "d1",
137
+ id: database_id,
138
+ internalEnv: database_internal_env,
139
+ });
140
+ }
141
+ );
139
142
 
140
143
  bindings.services?.forEach(({ binding, service, environment }) => {
141
144
  metadataBindings.push({
@@ -0,0 +1,2 @@
1
+ export const DEFAULT_MIGRATION_PATH = "./migrations";
2
+ export const DEFAULT_MIGRATION_TABLE = "d1_migrations";
package/src/d1/create.tsx CHANGED
@@ -23,17 +23,26 @@ export async function Handler({
23
23
  name,
24
24
  }: ArgumentsCamelCase<CreateArgs>): Promise<void> {
25
25
  const accountId = await requireAuth({});
26
+
26
27
  logger.log(d1BetaWarning);
27
28
 
28
- const db: Database = await fetchResult(`/accounts/${accountId}/d1/database`, {
29
- method: "POST",
30
- headers: {
31
- "Content-Type": "application/json",
32
- },
33
- body: JSON.stringify({
34
- name,
35
- }),
36
- });
29
+ let db: Database;
30
+ try {
31
+ db = await fetchResult(`/accounts/${accountId}/d1/database`, {
32
+ method: "POST",
33
+ headers: {
34
+ "Content-Type": "application/json",
35
+ },
36
+ body: JSON.stringify({
37
+ name,
38
+ }),
39
+ });
40
+ } catch (e) {
41
+ if ((e as { code: number }).code === 7502) {
42
+ throw new Error("A database with that name already exists");
43
+ }
44
+ throw e;
45
+ }
37
46
 
38
47
  render(
39
48
  <Box flexDirection="column">
@@ -12,13 +12,13 @@ import { confirm, logDim } from "../dialogs";
12
12
  import { logger } from "../logger";
13
13
  import { readableRelative } from "../paths";
14
14
  import { requireAuth } from "../user";
15
- import { Name } from "./options";
15
+ import * as options from "./options";
16
16
  import {
17
17
  d1BetaWarning,
18
18
  getDatabaseByNameOrBinding,
19
19
  getDatabaseInfoFromConfig,
20
20
  } from "./utils";
21
- import type { Config } from "../config";
21
+ import type { Config, ConfigFields, DevConfig, Environment } from "../config";
22
22
  import type { Database } from "./types";
23
23
  import type splitSqlQuery from "@databases/split-sql-query";
24
24
  import type { SQL, SQLQuery } from "@databases/sql";
@@ -35,26 +35,38 @@ type MiniflareNpxImportTypes = [
35
35
  }
36
36
  ];
37
37
 
38
- type ExecuteArgs = {
38
+ export type BaseSqlExecuteArgs = {
39
39
  config?: string;
40
- name: string;
41
- file?: string;
42
- command?: string;
40
+ database: string;
43
41
  local?: boolean;
44
42
  "persist-to"?: string;
43
+ yes?: boolean;
45
44
  };
46
45
 
47
- type QueryResult = {
46
+ type ExecuteArgs = BaseSqlExecuteArgs & {
47
+ file?: string;
48
+ command?: string;
49
+ };
50
+
51
+ export type QueryResult = {
48
52
  results: Record<string, string | number | boolean>[];
49
53
  success: boolean;
50
- duration: number;
54
+ meta?: {
55
+ duration?: number;
56
+ };
51
57
  query?: string;
52
58
  };
53
59
  // Max number of bytes to send in a single /execute call
54
60
  const QUERY_LIMIT = 10_000;
55
61
 
56
62
  export function Options(yargs: Argv): Argv<ExecuteArgs> {
57
- return Name(yargs)
63
+ return options
64
+ .Database(yargs)
65
+ .option("yes", {
66
+ describe: 'Answer "yes" to any prompts',
67
+ type: "boolean",
68
+ alias: "y",
69
+ })
58
70
  .option("local", {
59
71
  describe:
60
72
  "Execute commands/files against a local DB for use with wrangler dev",
@@ -81,38 +93,66 @@ function shorten(query: string | undefined, length: number) {
81
93
  : query;
82
94
  }
83
95
 
96
+ export async function executeSql(
97
+ local: undefined | boolean,
98
+ config: ConfigFields<DevConfig> & Environment,
99
+ name: string,
100
+ shouldPrompt: boolean | undefined,
101
+ persistTo: undefined | string,
102
+ file?: string,
103
+ command?: string
104
+ ) {
105
+ const { parser, splitter } = await loadSqlUtils();
106
+
107
+ const sql = file
108
+ ? parser.file(file)
109
+ : command
110
+ ? parser.__dangerous__rawValue(command)
111
+ : null;
112
+
113
+ if (!sql) throw new Error(`Error: must provide --command or --file.`);
114
+ if (persistTo && !local)
115
+ throw new Error(`Error: can't use --persist-to without --local`);
116
+
117
+ const queries = splitSql(splitter, sql);
118
+ if (file && sql) {
119
+ if (queries[0].startsWith("SQLite format 3")) {
120
+ //TODO: update this error to recommend using `wrangler d1 restore` when it exists
121
+ throw new Error(
122
+ "Provided file is a binary SQLite database file instead of an SQL text file.\nThe execute command can only process SQL text files.\nPlease export an SQL file from your SQLite database and try again."
123
+ );
124
+ }
125
+ }
126
+
127
+ return local
128
+ ? await executeLocally(config, name, shouldPrompt, queries, persistTo)
129
+ : await executeRemotely(config, name, shouldPrompt, batchSplit(queries));
130
+ }
131
+
84
132
  export const Handler = withConfig<ExecuteArgs>(
85
- async ({ config, name, file, command, local, persistTo }): Promise<void> => {
133
+ async ({
134
+ config,
135
+ database,
136
+ file,
137
+ command,
138
+ local,
139
+ persistTo,
140
+ yes,
141
+ }): Promise<void> => {
86
142
  logger.log(d1BetaWarning);
87
143
  if (file && command)
88
144
  return console.error(`Error: can't provide both --command and --file.`);
89
- const { parser, splitter } = await loadSqlUtils();
90
-
91
- const sql = file
92
- ? parser.file(file)
93
- : command
94
- ? parser.__dangerous__rawValue(command)
95
- : null;
96
-
97
- if (!sql) throw new Error(`Error: must provide --command or --file.`);
98
- if (persistTo && !local)
99
- throw new Error(`Error: can't use --persist-to without --local`);
100
145
 
101
146
  const isInteractive = process.stdout.isTTY;
102
- const response: QueryResult[] | null = local
103
- ? await executeLocally(
104
- config,
105
- name,
106
- isInteractive,
107
- splitSql(splitter, sql),
108
- persistTo
109
- )
110
- : await executeRemotely(
111
- config,
112
- name,
113
- isInteractive,
114
- batchSplit(splitter, sql)
115
- );
147
+ const response: QueryResult[] | null = await executeSql(
148
+ local,
149
+ config,
150
+ database,
151
+ isInteractive && !yes,
152
+ persistTo,
153
+ file,
154
+ command
155
+ );
116
156
 
117
157
  // Early exit if prompt rejected
118
158
  if (!response) return;
@@ -148,7 +188,7 @@ export const Handler = withConfig<ExecuteArgs>(
148
188
  async function executeLocally(
149
189
  config: Config,
150
190
  name: string,
151
- isInteractive: boolean,
191
+ shouldPrompt: boolean | undefined,
152
192
  queries: string[],
153
193
  persistTo: string | undefined
154
194
  ) {
@@ -173,7 +213,7 @@ async function executeLocally(
173
213
  logDim
174
214
  );
175
215
 
176
- if (!existsSync(dbDir) && isInteractive) {
216
+ if (!existsSync(dbDir) && shouldPrompt) {
177
217
  const ok = await confirm(
178
218
  `About to create ${readableRelative(dbPath)}, ok?`
179
219
  );
@@ -196,13 +236,14 @@ async function executeLocally(
196
236
  async function executeRemotely(
197
237
  config: Config,
198
238
  name: string,
199
- isInteractive: boolean,
239
+ shouldPrompt: boolean | undefined,
200
240
  batches: string[]
201
241
  ) {
202
- if (batches.length > 1) {
242
+ const multiple_batches = batches.length > 1;
243
+ if (multiple_batches) {
203
244
  const warning = `āš ļø Too much SQL to send at once, this execution will be sent as ${batches.length} batches.`;
204
245
 
205
- if (isInteractive) {
246
+ if (shouldPrompt) {
206
247
  const ok = await confirm(
207
248
  `${warning}\nā„¹ļø Each batch is sent individually and may leave your DB in an unexpected state if a later batch fails.\nāš ļø Make sure you have a recent backup. Ok to proceed?`
208
249
  );
@@ -220,9 +261,11 @@ async function executeRemotely(
220
261
  name
221
262
  );
222
263
 
223
- if (isInteractive) {
264
+ if (shouldPrompt) {
224
265
  logger.log(`šŸŒ€ Executing on ${name} (${db.uuid}):`);
225
- } else {
266
+
267
+ // Don't output if shouldPrompt is undefined
268
+ } else if (shouldPrompt !== undefined) {
226
269
  // Pipe to error so we don't break jq
227
270
  console.error(`Executing on ${name} (${db.uuid}):`);
228
271
  }
@@ -235,6 +278,7 @@ async function executeRemotely(
235
278
  method: "POST",
236
279
  headers: {
237
280
  "Content-Type": "application/json",
281
+ ...(db.internal_env ? { "x-d1-internal-env": db.internal_env } : {}),
238
282
  },
239
283
  body: JSON.stringify({ sql }),
240
284
  }
@@ -247,12 +291,14 @@ async function executeRemotely(
247
291
 
248
292
  function logResult(r: QueryResult | QueryResult[]) {
249
293
  logger.log(
250
- `🚣 Executed ${Array.isArray(r) ? r.length : "1"} command(s) in ${
294
+ `🚣 Executed ${
295
+ Array.isArray(r) ? `${r.length} commands` : "1 command"
296
+ } in ${
251
297
  Array.isArray(r)
252
298
  ? r
253
- .map((d: QueryResult) => d.duration)
299
+ .map((d: QueryResult) => d.meta?.duration || 0)
254
300
  .reduce((a: number, b: number) => a + b, 0)
255
- : r.duration
301
+ : r.meta?.duration
256
302
  }ms`
257
303
  );
258
304
  }
@@ -269,12 +315,11 @@ function splitSql(splitter: (query: SQLQuery) => SQLQuery[], sql: SQLQuery) {
269
315
  );
270
316
  }
271
317
 
272
- function batchSplit(splitter: typeof splitSqlQuery, sql: SQLQuery) {
273
- const queries = splitSql(splitter, sql);
318
+ function batchSplit(queries: string[]) {
274
319
  logger.log(`šŸŒ€ Parsing ${queries.length} statements`);
275
320
  const batches: string[] = [];
276
- const nbatches = Math.floor(queries.length / QUERY_LIMIT);
277
- for (let i = 0; i <= nbatches; i++) {
321
+ const num_batches = Math.ceil(queries.length / QUERY_LIMIT);
322
+ for (let i = 0; i < num_batches; i++) {
278
323
  batches.push(
279
324
  queries.slice(i * QUERY_LIMIT, (i + 1) * QUERY_LIMIT).join("; ")
280
325
  );
package/src/d1/index.ts CHANGED
@@ -3,6 +3,7 @@ import * as Create from "./create";
3
3
  import * as Delete from "./delete";
4
4
  import * as Execute from "./execute";
5
5
  import * as List from "./list";
6
+ import * as Migrations from "./migrations";
6
7
  import { d1BetaWarning } from "./utils";
7
8
  import type { CommonYargsOptions } from "../yargs-types";
8
9
  import type { Argv } from "yargs";
@@ -66,11 +67,33 @@ export const d1 = (yargs: Argv<CommonYargsOptions>) => {
66
67
  // }
67
68
  // )
68
69
  .command(
69
- "execute <name>",
70
+ "execute <database>",
70
71
  "Executed command or SQL file",
71
72
  Execute.Options,
72
73
  Execute.Handler
73
74
  )
75
+ .command("migrations", "Interact with D1 Migrations", (yargs2) =>
76
+ yargs2
77
+ .command(
78
+ "list <database>",
79
+ "List your D1 migrations",
80
+ Migrations.ListOptions,
81
+ Migrations.ListHandler
82
+ )
83
+ .command(
84
+ "create <database> <message>",
85
+ "Create a new Migration",
86
+ Migrations.CreateOptions,
87
+ Migrations.CreateHandler
88
+ )
89
+ .command(
90
+ "apply <database>",
91
+ "Apply D1 Migrations",
92
+ Migrations.ApplyOptions,
93
+ Migrations.ApplyHandler
94
+ )
95
+ .epilogue(d1BetaWarning)
96
+ )
74
97
  .epilogue(d1BetaWarning)
75
98
  );
76
99
  };