wrangler 2.2.2 → 2.2.4

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.4",
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]
@@ -57,15 +57,15 @@ describe("deployments", () => {
57
57
  "🚧\`wrangler deployments\` is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
58
58
 
59
59
 
60
- Deployment ID: Intrepid-Class
61
- Created on: 2021-02-02T00:00:00.000000Z
62
- Author: Kathryn-Janeway@federation.org
63
- Source: Wrangler
64
-
65
60
  Deployment ID: Galaxy-Class
66
61
  Created on: 2021-01-01T00:00:00.000000Z
67
62
  Author: Jean-Luc-Picard@federation.org
68
63
  Source: Wrangler
64
+
65
+ Deployment ID: Intrepid-Class
66
+ Created on: 2021-02-02T00:00:00.000000Z
67
+ Author: Kathryn-Janeway@federation.org
68
+ Source: Wrangler
69
69
  🟩 Active"
70
70
  `);
71
71
  });
@@ -76,15 +76,15 @@ describe("deployments", () => {
76
76
  "🚧\`wrangler deployments\` is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
77
77
 
78
78
 
79
- Deployment ID: Intrepid-Class
80
- Created on: 2021-02-02T00:00:00.000000Z
81
- Author: Kathryn-Janeway@federation.org
82
- Source: Wrangler
83
-
84
79
  Deployment ID: Galaxy-Class
85
80
  Created on: 2021-01-01T00:00:00.000000Z
86
81
  Author: Jean-Luc-Picard@federation.org
87
82
  Source: Wrangler
83
+
84
+ Deployment ID: Intrepid-Class
85
+ Created on: 2021-02-02T00:00:00.000000Z
86
+ Author: Kathryn-Janeway@federation.org
87
+ Source: Wrangler
88
88
  🟩 Active"
89
89
  `);
90
90
  });
@@ -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();
@@ -4,7 +4,7 @@ import type { DeploymentListRes } from "../../../../deployments";
4
4
 
5
5
  export const mswSuccessDeployments = [
6
6
  rest.get(
7
- "*/accounts/:accountId/workers/versions/by-script/:scriptTag",
7
+ "*/accounts/:accountId/workers/deployments/by-script/:scriptTag",
8
8
  (_, response, context) =>
9
9
  response.once(
10
10
  context.status(200),
@@ -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
@@ -65,7 +65,7 @@ describe("publish", () => {
65
65
  })
66
66
  );
67
67
  setMockResponse(
68
- "/accounts/:accountId/workers/versions/by-script/:scriptTag",
68
+ "/accounts/:accountId/workers/deployments/by-script/:scriptTag",
69
69
  () => ({
70
70
  latest: { number: "2" },
71
71
  })
@@ -4363,7 +4363,7 @@ addEventListener('fetch', event => {});`
4363
4363
  },
4364
4364
  });
4365
4365
  setMockResponse(
4366
- "/accounts/:accountId/workers/versions/by-script/:scriptTag",
4366
+ "/accounts/:accountId/workers/deployments/by-script/:scriptTag",
4367
4367
  () => ({
4368
4368
  latest: { number: "2" },
4369
4369
  })
@@ -6861,7 +6861,7 @@ addEventListener('fetch', event => {});`
6861
6861
  mockSubDomainRequest();
6862
6862
  mockUploadWorkerRequest();
6863
6863
  setMockResponse(
6864
- "/accounts/:accountId/workers/versions/by-script/:scriptTag",
6864
+ "/accounts/:accountId/workers/deployments/by-script/:scriptTag",
6865
6865
  () => ({
6866
6866
  latest: { number: "2" },
6867
6867
  })
@@ -6907,7 +6907,7 @@ addEventListener('fetch', event => {});`
6907
6907
  }
6908
6908
  );
6909
6909
  setMockResponse(
6910
- "/accounts/:accountId/workers/versions/by-script/:scriptTag",
6910
+ "/accounts/:accountId/workers/deployments/by-script/:scriptTag",
6911
6911
  () => ({
6912
6912
  latest: { number: "2" },
6913
6913
  })
@@ -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
@@ -4,11 +4,8 @@ import { mockConsoleMethods } from "./helpers/mock-console";
4
4
  import { runInTempDir } from "./helpers/run-in-tmp";
5
5
  import { runWrangler } from "./helpers/run-wrangler";
6
6
  import type { Config } from "../config";
7
- import type { CfWorkerInit } from "../worker";
8
7
 
9
- const bindingsConfigMock: CfWorkerInit["bindings"] & {
10
- rules: Config["rules"];
11
- } = {
8
+ const bindingsConfigMock: Partial<Config> = {
12
9
  kv_namespaces: [{ binding: "TEST_KV_NAMESPACE", id: "1234" }],
13
10
  vars: {
14
11
  SOMETHING: "asdasdfasdf",
@@ -19,6 +16,22 @@ const bindingsConfigMock: CfWorkerInit["bindings"] & {
19
16
  captian: "Picard",
20
17
  }, // We can assume the objects will be stringified
21
18
  },
19
+ queues: {
20
+ producers: [
21
+ {
22
+ binding: "TEST_QUEUE_BINDING",
23
+ queue: "TEST_QUEUE",
24
+ },
25
+ ],
26
+ consumers: [
27
+ {
28
+ queue: "my-queue",
29
+ max_batch_size: 10,
30
+ max_batch_timeout: 1,
31
+ max_retries: 3,
32
+ },
33
+ ],
34
+ },
22
35
  durable_objects: {
23
36
  bindings: [
24
37
  { name: "DURABLE_TEST1", class_name: "Durability1" },
@@ -33,7 +46,6 @@ const bindingsConfigMock: CfWorkerInit["bindings"] & {
33
46
  ],
34
47
  d1_databases: [
35
48
  {
36
- // @ts-expect-error This type is resolved in the function that handles creating BETA string
37
49
  binding: "D1_TESTING_SOMETHING",
38
50
  database_name: "D1_BINDING",
39
51
  database_id: "1234",
@@ -56,12 +68,9 @@ const bindingsConfigMock: CfWorkerInit["bindings"] & {
56
68
  SOME_TEXT_BLOB2: "SOME_TEXT_BLOB2.txt",
57
69
  },
58
70
  wasm_modules: { MODULE1: "module1.wasm", MODULE2: "module2.wasm" },
59
- unsafe: [
60
- {
61
- // @ts-expect-error Unsafe bindings type is somewhat different in different places
62
- bindings: [{ name: "testing_unsafe", type: "plain_text" }],
63
- },
64
- ],
71
+ unsafe: {
72
+ bindings: [{ name: "testing_unsafe", type: "plain_text" }],
73
+ },
65
74
  rules: [
66
75
  {
67
76
  type: "Text",
@@ -89,10 +98,9 @@ describe("generateTypes()", () => {
89
98
  compatibility_date: "2022-01-12",
90
99
  name: "test-name",
91
100
  main: "./index.ts",
92
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
- ...(bindingsConfigMock as any),
94
- unsafe: bindingsConfigMock.unsafe?.at(0) ?? {},
95
- }),
101
+ ...bindingsConfigMock,
102
+ unsafe: bindingsConfigMock.unsafe ?? {},
103
+ } as unknown as TOML.JsonMap),
96
104
  "utf-8"
97
105
  );
98
106
 
@@ -115,6 +123,7 @@ describe("generateTypes()", () => {
115
123
  SOME_TEXT_BLOB1: string;
116
124
  SOME_TEXT_BLOB2: string;
117
125
  testing_unsafe: any;
126
+ TEST_QUEUE_BINDING: Queue;
118
127
  }
119
128
  declare module \\"*.txt\\" {
120
129
  const value: string;
@@ -139,9 +148,9 @@ describe("generateTypes()", () => {
139
148
  compatibility_date: "2022-01-12",
140
149
  name: "test-name",
141
150
  main: "./index.ts",
142
- // @ts-expect-error This type is out of sync with the actual bindingsConfig type
143
151
  vars: bindingsConfigMock.vars,
144
- }),
152
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
153
+ } as any),
145
154
  "utf-8"
146
155
  );
147
156
  await runWrangler("types");
@@ -159,6 +168,7 @@ describe("generateTypes()", () => {
159
168
  }),
160
169
  "utf-8"
161
170
  );
171
+
162
172
  await runWrangler("types");
163
173
  expect(fs.existsSync("./worker-configuration.d.ts")).toBe(false);
164
174
  expect(std.out).toMatchInlineSnapshot(`""`);
@@ -176,9 +186,8 @@ describe("generateTypes()", () => {
176
186
  compatibility_date: "2022-01-12",
177
187
  name: "test-name",
178
188
  main: "./index.ts",
179
- // @ts-expect-error This type is out of sync with the actual bindingsConfig type
180
189
  vars: bindingsConfigMock.vars,
181
- }),
190
+ } as TOML.JsonMap),
182
191
  "utf-8"
183
192
  );
184
193
 
@@ -200,9 +209,8 @@ describe("generateTypes()", () => {
200
209
  compatibility_date: "2022-01-12",
201
210
  name: "test-name",
202
211
  main: "./index.ts",
203
- // @ts-expect-error This type is out of sync with the actual bindingsConfig type
204
- unsafe: bindingsConfigMock.unsafe?.at(0) ?? {},
205
- }),
212
+ unsafe: bindingsConfigMock.unsafe ?? {},
213
+ } as TOML.JsonMap),
206
214
  "utf-8"
207
215
  );
208
216
 
@@ -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
  );