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 +2 -2
- package/src/__tests__/d1.test.ts +12 -10
- package/src/__tests__/deployments.test.ts +10 -10
- package/src/__tests__/generate.test.ts +3 -3
- package/src/__tests__/helpers/msw/handlers/deployments.ts +1 -1
- package/src/__tests__/index.test.ts +2 -2
- package/src/__tests__/publish.test.ts +4 -4
- package/src/__tests__/queues.test.ts +1 -1
- package/src/__tests__/type-generation.test.ts +30 -22
- 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/deployments.ts +7 -3
- 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 +4 -5
- package/src/publish/publish.ts +2 -2
- package/src/type-generation.ts +58 -44
- 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 +1443 -995
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wrangler",
|
|
3
|
-
"version": "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.
|
|
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",
|
package/src/__tests__/d1.test.ts
CHANGED
|
@@ -21,11 +21,12 @@ describe("d1", () => {
|
|
|
21
21
|
š Interact with a D1 database
|
|
22
22
|
|
|
23
23
|
Commands:
|
|
24
|
-
wrangler d1 list
|
|
25
|
-
wrangler d1 create <name>
|
|
26
|
-
wrangler d1 delete <name>
|
|
27
|
-
wrangler d1 backup
|
|
28
|
-
wrangler d1 execute <
|
|
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
|
|
58
|
-
wrangler d1 create <name>
|
|
59
|
-
wrangler d1 delete <name>
|
|
60
|
-
wrangler d1 backup
|
|
61
|
-
wrangler d1 execute <
|
|
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/
|
|
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
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
6910
|
+
"/accounts/:accountId/workers/deployments/by-script/:scriptTag",
|
|
6911
6911
|
() => ({
|
|
6912
6912
|
latest: { number: "2" },
|
|
6913
6913
|
})
|
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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({
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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">
|
package/src/d1/execute.tsx
CHANGED
|
@@ -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
|
|
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
|
|
38
|
+
export type BaseSqlExecuteArgs = {
|
|
39
39
|
config?: string;
|
|
40
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 ({
|
|
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 =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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) &&
|
|
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
|
-
|
|
239
|
+
shouldPrompt: boolean | undefined,
|
|
200
240
|
batches: string[]
|
|
201
241
|
) {
|
|
202
|
-
|
|
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 (
|
|
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 (
|
|
264
|
+
if (shouldPrompt) {
|
|
224
265
|
logger.log(`š Executing on ${name} (${db.uuid}):`);
|
|
225
|
-
|
|
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 ${
|
|
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(
|
|
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
|
|
277
|
-
for (let i = 0; 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
|
);
|