wrangler 0.0.0-e178d6fb → 0.0.0-e1c2f5b9

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.
Files changed (239) hide show
  1. package/README.md +16 -2
  2. package/bin/wrangler.js +31 -11
  3. package/miniflare-dist/index.mjs +1340 -330
  4. package/package.json +40 -13
  5. package/src/__tests__/access.test.ts +25 -0
  6. package/src/__tests__/api-dev.test.ts +238 -0
  7. package/src/__tests__/api-devregistry.test.js +64 -0
  8. package/src/__tests__/api.test.ts +80 -0
  9. package/src/__tests__/configuration.test.ts +683 -470
  10. package/src/__tests__/d1.test.ts +123 -0
  11. package/src/__tests__/delete.test.ts +263 -0
  12. package/src/__tests__/deployments.test.ts +105 -0
  13. package/src/__tests__/dev.test.tsx +453 -70
  14. package/src/__tests__/dialogs.test.tsx +40 -0
  15. package/src/__tests__/generate.test.ts +235 -0
  16. package/src/__tests__/get-host-from-url.test.ts +16 -0
  17. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  18. package/src/__tests__/helpers/mock-cfetch.ts +92 -26
  19. package/src/__tests__/helpers/mock-console.ts +11 -2
  20. package/src/__tests__/helpers/mock-dialogs.ts +1 -1
  21. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +1 -1
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
  25. package/src/__tests__/helpers/mock-process.ts +8 -13
  26. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  27. package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
  28. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  29. package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
  30. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  31. package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
  32. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  33. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  34. package/src/__tests__/helpers/msw/index.ts +21 -0
  35. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  36. package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
  37. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  38. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  39. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  40. package/src/__tests__/index.test.ts +103 -97
  41. package/src/__tests__/init.test.ts +1484 -382
  42. package/src/__tests__/jest.setup.ts +88 -5
  43. package/src/__tests__/kv.test.ts +655 -540
  44. package/src/__tests__/logout.test.ts +47 -0
  45. package/src/__tests__/metrics.test.ts +95 -45
  46. package/src/__tests__/middleware.scheduled.test.ts +145 -0
  47. package/src/__tests__/middleware.test.ts +750 -0
  48. package/src/__tests__/pages-deployment-tail.test.ts +884 -0
  49. package/src/__tests__/pages.test.ts +1301 -119
  50. package/src/__tests__/paths.test.ts +39 -0
  51. package/src/__tests__/publish.test.ts +2266 -1180
  52. package/src/__tests__/pubsub.test.ts +211 -88
  53. package/src/__tests__/queues.test.ts +522 -0
  54. package/src/__tests__/r2.test.ts +363 -203
  55. package/src/__tests__/secret.test.ts +261 -59
  56. package/src/__tests__/tail.test.ts +352 -151
  57. package/src/__tests__/test-old-node-version.js +3 -3
  58. package/src/__tests__/tsconfig-sanity.ts +12 -0
  59. package/src/__tests__/tsconfig.json +8 -0
  60. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  61. package/src/__tests__/type-generation.test.ts +233 -0
  62. package/src/__tests__/user.test.ts +59 -73
  63. package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
  64. package/src/__tests__/validate-dev-props.test.ts +56 -0
  65. package/src/__tests__/whoami.test.tsx +114 -66
  66. package/src/__tests__/worker-namespace.test.ts +154 -155
  67. package/src/api/dev.ts +170 -38
  68. package/src/api/index.ts +1 -0
  69. package/src/bundle-reporter.tsx +41 -2
  70. package/src/bundle.ts +668 -81
  71. package/src/cfetch/index.ts +40 -3
  72. package/src/cfetch/internal.ts +144 -32
  73. package/src/cli.ts +4 -3
  74. package/src/config/config.ts +27 -3
  75. package/src/config/environment.ts +131 -7
  76. package/src/config/index.ts +128 -8
  77. package/src/config/validation-helpers.ts +19 -6
  78. package/src/config/validation.ts +411 -14
  79. package/src/config-cache.ts +3 -2
  80. package/src/create-worker-preview.ts +31 -6
  81. package/src/create-worker-upload-form.ts +84 -22
  82. package/src/d1/backups.tsx +223 -0
  83. package/src/d1/constants.ts +2 -0
  84. package/src/d1/create.tsx +63 -0
  85. package/src/d1/delete.tsx +56 -0
  86. package/src/d1/execute.tsx +352 -0
  87. package/src/d1/formatTimeAgo.ts +14 -0
  88. package/src/d1/index.ts +101 -0
  89. package/src/d1/list.tsx +48 -0
  90. package/src/d1/migrations/apply.tsx +196 -0
  91. package/src/d1/migrations/create.tsx +77 -0
  92. package/src/d1/migrations/helpers.ts +147 -0
  93. package/src/d1/migrations/index.tsx +3 -0
  94. package/src/d1/migrations/list.tsx +79 -0
  95. package/src/d1/migrations/options.ts +18 -0
  96. package/src/d1/options.ts +22 -0
  97. package/src/d1/types.tsx +24 -0
  98. package/src/d1/utils.ts +48 -0
  99. package/src/delete.ts +108 -0
  100. package/src/deployments.ts +83 -0
  101. package/src/deprecated/index.ts +145 -0
  102. package/src/dev/dev-vars.ts +11 -8
  103. package/src/dev/dev.tsx +220 -58
  104. package/src/dev/get-local-persistence-path.tsx +31 -0
  105. package/src/dev/local.tsx +740 -181
  106. package/src/dev/remote.tsx +340 -123
  107. package/src/dev/start-server.ts +543 -0
  108. package/src/dev/use-esbuild.ts +61 -7
  109. package/src/dev/validate-dev-props.ts +31 -0
  110. package/src/dev-registry.tsx +201 -0
  111. package/src/dev.tsx +614 -258
  112. package/src/dialogs.tsx +41 -8
  113. package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
  114. package/src/docs/index.ts +97 -0
  115. package/src/entry.ts +3 -2
  116. package/src/environment-variables/factory.ts +88 -0
  117. package/src/environment-variables/misc-variables.ts +30 -0
  118. package/src/generate/index.ts +300 -0
  119. package/src/git-client.ts +100 -7
  120. package/src/index.tsx +249 -1560
  121. package/src/init.ts +626 -150
  122. package/src/inspect.ts +137 -32
  123. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  124. package/src/is-interactive.ts +4 -0
  125. package/src/jest.d.ts +4 -0
  126. package/src/{kv.ts → kv/helpers.ts} +3 -3
  127. package/src/kv/index.ts +596 -0
  128. package/src/logger.ts +27 -8
  129. package/src/metrics/index.ts +1 -0
  130. package/src/metrics/metrics-config.ts +4 -4
  131. package/src/metrics/metrics-dispatcher.ts +1 -0
  132. package/src/metrics/metrics-usage-headers.ts +24 -0
  133. package/src/metrics/send-event.ts +13 -8
  134. package/src/miniflare-cli/assets.ts +172 -465
  135. package/src/miniflare-cli/index.ts +149 -24
  136. package/src/miniflare-cli/tsconfig.json +9 -0
  137. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  138. package/src/miniflare-cli/types.ts +11 -0
  139. package/src/module-collection.ts +12 -3
  140. package/src/pages/build.tsx +108 -42
  141. package/src/pages/constants.ts +11 -0
  142. package/src/pages/deployment-tails.tsx +284 -0
  143. package/src/pages/deployments.tsx +9 -31
  144. package/src/pages/dev.tsx +417 -108
  145. package/src/pages/errors.ts +66 -0
  146. package/src/pages/functions/buildPlugin.ts +86 -84
  147. package/src/pages/functions/buildWorker.ts +121 -106
  148. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  149. package/src/pages/functions/routes-consolidation.ts +73 -0
  150. package/src/pages/functions/routes-transformation.test.ts +282 -0
  151. package/src/pages/functions/routes-transformation.ts +115 -0
  152. package/src/pages/functions/routes-validation.test.ts +403 -0
  153. package/src/pages/functions/routes-validation.ts +202 -0
  154. package/src/pages/functions/tsconfig.json +8 -0
  155. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  156. package/src/pages/functions.tsx +82 -0
  157. package/src/pages/hash.tsx +13 -0
  158. package/src/pages/index.tsx +73 -55
  159. package/src/pages/projects.tsx +9 -3
  160. package/src/pages/prompt-select-project.tsx +31 -0
  161. package/src/pages/publish.tsx +183 -60
  162. package/src/pages/types.ts +1 -0
  163. package/src/pages/upload.tsx +107 -56
  164. package/src/pages/utils.ts +13 -2
  165. package/src/paths.ts +49 -4
  166. package/src/proxy.ts +375 -157
  167. package/src/publish/index.ts +270 -0
  168. package/src/{publish.ts → publish/publish.ts} +431 -188
  169. package/src/pubsub/pubsub-commands.tsx +4 -3
  170. package/src/queues/cli/commands/consumer/add.ts +71 -0
  171. package/src/queues/cli/commands/consumer/index.ts +22 -0
  172. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  173. package/src/queues/cli/commands/create.ts +25 -0
  174. package/src/queues/cli/commands/delete.ts +26 -0
  175. package/src/queues/cli/commands/index.ts +36 -0
  176. package/src/queues/cli/commands/list.ts +25 -0
  177. package/src/queues/client.ts +135 -0
  178. package/src/queues/utils.ts +18 -0
  179. package/src/r2/constants.ts +4 -0
  180. package/src/r2/helpers.ts +131 -0
  181. package/src/r2/index.ts +289 -0
  182. package/src/routes.ts +140 -0
  183. package/src/secret/index.ts +379 -0
  184. package/src/sites.tsx +3 -3
  185. package/src/tail/createTail.ts +379 -0
  186. package/src/tail/filters.ts +4 -6
  187. package/src/tail/index.ts +199 -284
  188. package/src/tail/printing.ts +43 -3
  189. package/src/tsconfig-sanity.ts +16 -0
  190. package/src/type-generation.ts +181 -0
  191. package/src/user/access.ts +68 -0
  192. package/src/user/auth-variables.ts +113 -0
  193. package/src/user/choose-account.tsx +1 -1
  194. package/src/user/index.ts +0 -1
  195. package/src/user/user.tsx +116 -49
  196. package/src/utils/collectKeyValues.ts +14 -0
  197. package/src/whoami.tsx +80 -2
  198. package/src/worker.ts +69 -9
  199. package/src/yargs-types.ts +24 -0
  200. package/src/zones.ts +124 -9
  201. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  202. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  203. package/templates/__tests__/tsconfig.json +8 -0
  204. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  205. package/templates/d1-beta-facade.js +205 -0
  206. package/templates/facade.d.ts +14 -0
  207. package/templates/first-party-worker-module-facade.ts +19 -0
  208. package/templates/format-dev-errors.ts +33 -0
  209. package/templates/init-tests/test-jest-new-worker.js +23 -0
  210. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  211. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  212. package/templates/middleware/common.ts +62 -0
  213. package/templates/middleware/loader-modules.ts +82 -0
  214. package/templates/middleware/loader-sw.ts +233 -0
  215. package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
  216. package/templates/middleware/middleware-pretty-error.ts +40 -0
  217. package/templates/middleware/middleware-scheduled.ts +15 -0
  218. package/templates/new-worker.js +1 -1
  219. package/templates/pages-dev-pipeline.ts +31 -0
  220. package/templates/pages-dev-util.ts +52 -0
  221. package/templates/pages-shim.ts +4 -1
  222. package/templates/pages-template-plugin.ts +21 -5
  223. package/templates/pages-template-worker.ts +37 -9
  224. package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
  225. package/templates/service-bindings-module-facade.js +54 -0
  226. package/templates/service-bindings-sw-facade.js +42 -0
  227. package/templates/tsconfig-sanity.ts +11 -0
  228. package/templates/tsconfig.init.json +106 -0
  229. package/templates/tsconfig.json +5 -102
  230. package/templates/tsconfig.tsbuildinfo +1 -0
  231. package/wrangler-dist/cli.d.ts +621 -42
  232. package/wrangler-dist/cli.js +144441 -102606
  233. package/src/environment-variables.ts +0 -35
  234. package/src/faye-websocket.d.ts +0 -6
  235. package/src/generate.ts +0 -33
  236. package/src/miniflare-cli/enum-keys.ts +0 -17
  237. package/src/preview.ts +0 -31
  238. package/src/r2.ts +0 -50
  239. package/src/user/env-vars.ts +0 -46
@@ -0,0 +1,352 @@
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 * as options from "./options";
17
+ import {
18
+ d1BetaWarning,
19
+ getDatabaseByNameOrBinding,
20
+ getDatabaseInfoFromConfig,
21
+ } from "./utils";
22
+ import type { Config, ConfigFields, DevConfig, Environment } 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
+ export type BaseSqlExecuteArgs = {
40
+ config?: string;
41
+ database: string;
42
+ local?: boolean;
43
+ "persist-to"?: string;
44
+ yes?: boolean;
45
+ };
46
+
47
+ type ExecuteArgs = BaseSqlExecuteArgs & {
48
+ file?: string;
49
+ command?: string;
50
+ };
51
+
52
+ export type QueryResult = {
53
+ results: Record<string, string | number | boolean>[];
54
+ success: boolean;
55
+ meta?: {
56
+ duration?: number;
57
+ };
58
+ query?: string;
59
+ };
60
+ // Max number of bytes to send in a single /execute call
61
+ const QUERY_LIMIT = 10_000;
62
+
63
+ export function Options(yargs: Argv): Argv<ExecuteArgs> {
64
+ return options
65
+ .Database(yargs)
66
+ .option("yes", {
67
+ describe: 'Answer "yes" to any prompts',
68
+ type: "boolean",
69
+ alias: "y",
70
+ })
71
+ .option("local", {
72
+ describe:
73
+ "Execute commands/files against a local DB for use with wrangler dev",
74
+ type: "boolean",
75
+ })
76
+ .option("file", {
77
+ describe: "A .sql file to injest",
78
+ type: "string",
79
+ })
80
+ .option("command", {
81
+ describe: "A single SQL statement to execute",
82
+ type: "string",
83
+ })
84
+ .option("persist-to", {
85
+ describe: "Specify directory to use for local persistence (for --local)",
86
+ type: "string",
87
+ requiresArg: true,
88
+ });
89
+ }
90
+
91
+ function shorten(query: string | undefined, length: number) {
92
+ return query && query.length > length
93
+ ? query.slice(0, length) + "..."
94
+ : query;
95
+ }
96
+
97
+ export async function executeSql(
98
+ local: undefined | boolean,
99
+ config: ConfigFields<DevConfig> & Environment,
100
+ name: string,
101
+ shouldPrompt: boolean | undefined,
102
+ persistTo: undefined | string,
103
+ file?: string,
104
+ command?: string
105
+ ) {
106
+ const { parser, splitter } = await loadSqlUtils();
107
+
108
+ const sql = file
109
+ ? parser.file(file)
110
+ : command
111
+ ? parser.__dangerous__rawValue(command)
112
+ : null;
113
+
114
+ if (!sql) throw new Error(`Error: must provide --command or --file.`);
115
+ if (persistTo && !local)
116
+ throw new Error(`Error: can't use --persist-to without --local`);
117
+
118
+ const queries = splitSql(splitter, sql);
119
+ if (file && sql) {
120
+ if (queries[0].startsWith("SQLite format 3")) {
121
+ //TODO: update this error to recommend using `wrangler d1 restore` when it exists
122
+ throw new Error(
123
+ "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."
124
+ );
125
+ }
126
+ }
127
+
128
+ return local
129
+ ? await executeLocally(config, name, shouldPrompt, queries, persistTo)
130
+ : await executeRemotely(config, name, shouldPrompt, batchSplit(queries));
131
+ }
132
+
133
+ export const Handler = withConfig<ExecuteArgs>(
134
+ async ({
135
+ config,
136
+ database,
137
+ file,
138
+ command,
139
+ local,
140
+ persistTo,
141
+ yes,
142
+ }): Promise<void> => {
143
+ logger.log(d1BetaWarning);
144
+ if (file && command)
145
+ return logger.error(`Error: can't provide both --command and --file.`);
146
+
147
+ const isInteractive = process.stdout.isTTY;
148
+ const response: QueryResult[] | null = await executeSql(
149
+ local,
150
+ config,
151
+ database,
152
+ isInteractive && !yes,
153
+ persistTo,
154
+ file,
155
+ command
156
+ );
157
+
158
+ // Early exit if prompt rejected
159
+ if (!response) return;
160
+
161
+ if (isInteractive) {
162
+ // Render table if single result
163
+ render(
164
+ <Static items={response}>
165
+ {(result) => {
166
+ // batch results
167
+ if (!Array.isArray(result)) {
168
+ const { results, query } = result;
169
+
170
+ if (Array.isArray(results) && results.length > 0) {
171
+ const shortQuery = shorten(query, 48);
172
+ return (
173
+ <>
174
+ {shortQuery ? <Text dimColor>{shortQuery}</Text> : null}
175
+ <Table data={results}></Table>
176
+ </>
177
+ );
178
+ }
179
+ }
180
+ }}
181
+ </Static>
182
+ );
183
+ } else {
184
+ logger.log(JSON.stringify(response, null, 2));
185
+ }
186
+ }
187
+ );
188
+
189
+ async function executeLocally(
190
+ config: Config,
191
+ name: string,
192
+ shouldPrompt: boolean | undefined,
193
+ queries: string[],
194
+ persistTo: string | undefined
195
+ ) {
196
+ const localDB = getDatabaseInfoFromConfig(config, name);
197
+ if (!localDB) {
198
+ throw new Error(
199
+ `Can't find a DB with name/binding '${name}' in local config. Check info in wrangler.toml...`
200
+ );
201
+ }
202
+
203
+ const persistencePath = getLocalPersistencePath(
204
+ persistTo,
205
+ true,
206
+ config.configPath
207
+ );
208
+
209
+ const dbDir = path.join(persistencePath, "d1");
210
+ const dbPath = path.join(dbDir, `${localDB.binding}.sqlite3`);
211
+ const [{ Statement }, { createSQLiteDB }] =
212
+ await npxImport<MiniflareNpxImportTypes>(
213
+ ["@miniflare/d1", "@miniflare/shared"],
214
+ logDim
215
+ );
216
+
217
+ if (!existsSync(dbDir) && shouldPrompt) {
218
+ const ok = await confirm(
219
+ `About to create ${readableRelative(dbPath)}, ok?`
220
+ );
221
+ if (!ok) return null;
222
+ await mkdir(dbDir, { recursive: true });
223
+ }
224
+
225
+ logger.log(`🌀 Loading DB at ${readableRelative(dbPath)}`);
226
+ const db = await createSQLiteDB(dbPath);
227
+
228
+ const results: QueryResult[] = [];
229
+ for (const sql of queries) {
230
+ const statement = new Statement(db, sql);
231
+ results.push((await statement.all()) as QueryResult);
232
+ }
233
+
234
+ return results;
235
+ }
236
+
237
+ async function executeRemotely(
238
+ config: Config,
239
+ name: string,
240
+ shouldPrompt: boolean | undefined,
241
+ batches: string[]
242
+ ) {
243
+ const multiple_batches = batches.length > 1;
244
+ if (multiple_batches) {
245
+ const warning = `⚠️ Too much SQL to send at once, this execution will be sent as ${batches.length} batches.`;
246
+
247
+ if (shouldPrompt) {
248
+ const ok = await confirm(
249
+ `${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?`
250
+ );
251
+ if (!ok) return null;
252
+ logger.log(`🌀 Let's go`);
253
+ } else {
254
+ logger.error(warning);
255
+ }
256
+ }
257
+
258
+ const accountId = await requireAuth({});
259
+ const db: Database = await getDatabaseByNameOrBinding(
260
+ config,
261
+ accountId,
262
+ name
263
+ );
264
+
265
+ if (shouldPrompt) {
266
+ logger.log(`🌀 Executing on ${name} (${db.uuid}):`);
267
+
268
+ // Don't output if shouldPrompt is undefined
269
+ } else if (shouldPrompt !== undefined) {
270
+ // Pipe to error so we don't break jq
271
+ logger.error(`Executing on ${name} (${db.uuid}):`);
272
+ }
273
+
274
+ const results: QueryResult[] = [];
275
+ for (const sql of batches) {
276
+ if (multiple_batches)
277
+ logger.log(
278
+ chalk.gray(` ${sql.slice(0, 70)}${sql.length > 70 ? "..." : ""}`)
279
+ );
280
+
281
+ const result = await fetchResult<QueryResult[]>(
282
+ `/accounts/${accountId}/d1/database/${db.uuid}/query`,
283
+ {
284
+ method: "POST",
285
+ headers: {
286
+ "Content-Type": "application/json",
287
+ ...(db.internal_env ? { "x-d1-internal-env": db.internal_env } : {}),
288
+ },
289
+ body: JSON.stringify({ sql }),
290
+ }
291
+ );
292
+ result.map(logResult);
293
+ results.push(...result);
294
+ }
295
+ return results;
296
+ }
297
+
298
+ function logResult(r: QueryResult | QueryResult[]) {
299
+ logger.log(
300
+ `🚣 Executed ${
301
+ Array.isArray(r) ? `${r.length} commands` : "1 command"
302
+ } in ${
303
+ Array.isArray(r)
304
+ ? r
305
+ .map((d: QueryResult) => d.meta?.duration || 0)
306
+ .reduce((a: number, b: number) => a + b, 0)
307
+ : r.meta?.duration
308
+ }ms`
309
+ );
310
+ }
311
+
312
+ function splitSql(splitter: (query: SQLQuery) => SQLQuery[], sql: SQLQuery) {
313
+ // We have no interpolations, so convert everything to text
314
+ logger.log(`🌀 Mapping SQL input into an array of statements`);
315
+ return splitter(sql).map(
316
+ (q) =>
317
+ q.format({
318
+ escapeIdentifier: (_) => "",
319
+ formatValue: (_, __) => ({ placeholder: "", value: "" }),
320
+ }).text
321
+ );
322
+ }
323
+
324
+ function batchSplit(queries: string[]) {
325
+ logger.log(`🌀 Parsing ${queries.length} statements`);
326
+ const num_batches = Math.ceil(queries.length / QUERY_LIMIT);
327
+ const batches: string[] = [];
328
+ for (let i = 0; i < num_batches; i++) {
329
+ batches.push(
330
+ queries.slice(i * QUERY_LIMIT, (i + 1) * QUERY_LIMIT).join("; ")
331
+ );
332
+ }
333
+ if (num_batches > 1) {
334
+ logger.log(
335
+ `🌀 We are sending ${num_batches} batch(es) to D1 (limited to ${QUERY_LIMIT} statements per batch)`
336
+ );
337
+ }
338
+ return batches;
339
+ }
340
+
341
+ async function loadSqlUtils() {
342
+ const [
343
+ { default: parser },
344
+ {
345
+ // No idea why this is doubly-nested, see https://github.com/ForbesLindesay/atdatabases/issues/255
346
+ default: { default: splitter },
347
+ },
348
+ ] = await npxImport<
349
+ [{ default: SQL }, { default: { default: typeof splitSqlQuery } }]
350
+ >(["@databases/sql@3.2.0", "@databases/split-sql-query@1.0.3"], logDim);
351
+ return { parser, splitter };
352
+ }
@@ -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
+ };
@@ -0,0 +1,101 @@
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 * as Migrations from "./migrations";
7
+ import { d1BetaWarning } from "./utils";
8
+ import type { CommonYargsOptions } from "../yargs-types";
9
+ import type { Argv } from "yargs";
10
+
11
+ export const d1 = (yargs: Argv<CommonYargsOptions>) => {
12
+ return (
13
+ yargs
14
+ .command("list", "List D1 databases", List.Options, List.Handler)
15
+ .command(
16
+ "create <name>",
17
+ "Create D1 database",
18
+ Create.Options,
19
+ Create.Handler
20
+ )
21
+ .command(
22
+ "delete <name>",
23
+ "Delete D1 database",
24
+ Delete.Options,
25
+ Delete.Handler
26
+ )
27
+ .command("backup", "Interact with D1 Backups", (yargs2) =>
28
+ yargs2
29
+ .demandCommand()
30
+ .command(
31
+ "list <name>",
32
+ "List your D1 backups",
33
+ Backups.ListOptions,
34
+ Backups.ListHandler
35
+ )
36
+ .command(
37
+ "create <name>",
38
+ "Create a new D1 backup",
39
+ Backups.CreateOptions,
40
+ Backups.CreateHandler
41
+ )
42
+ .command(
43
+ "restore <name> <backup-id>",
44
+ "Restore a DB backup",
45
+ Backups.RestoreOptions,
46
+ Backups.RestoreHandler
47
+ )
48
+ .command(
49
+ "download <name> <backup-id>",
50
+ "Download a DB backup",
51
+ Backups.DownloadOptions,
52
+ Backups.DownloadHandler
53
+ )
54
+ .epilogue(d1BetaWarning)
55
+ )
56
+ // .command(
57
+ // "console <name>",
58
+ // "Open a Console on a D1 database",
59
+ // (d1CreateYargs) => {
60
+ // return d1CreateYargs.positional("name", {
61
+ // describe: "The name of the DB",
62
+ // type: "string",
63
+ // demandOption: true,
64
+ // });
65
+ // },
66
+ // async (_) => {
67
+ // // TODO
68
+ // }
69
+ // )
70
+ .command(
71
+ "execute <database>",
72
+ "Executed command or SQL file",
73
+ Execute.Options,
74
+ Execute.Handler
75
+ )
76
+ .command("migrations", "Interact with D1 Migrations", (yargs2) =>
77
+ yargs2
78
+ .demandCommand()
79
+ .command(
80
+ "list <database>",
81
+ "List your D1 migrations",
82
+ Migrations.ListOptions,
83
+ Migrations.ListHandler
84
+ )
85
+ .command(
86
+ "create <database> <message>",
87
+ "Create a new Migration",
88
+ Migrations.CreateOptions,
89
+ Migrations.CreateHandler
90
+ )
91
+ .command(
92
+ "apply <database>",
93
+ "Apply D1 Migrations",
94
+ Migrations.ApplyOptions,
95
+ Migrations.ApplyHandler
96
+ )
97
+ .epilogue(d1BetaWarning)
98
+ )
99
+ .epilogue(d1BetaWarning)
100
+ );
101
+ };
@@ -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,196 @@
1
+ import assert from "node:assert";
2
+ import fs from "node:fs";
3
+ import path from "path";
4
+ import { Box, render, Text } from "ink";
5
+ import Table from "ink-table";
6
+ import React from "react";
7
+ import { withConfig } from "../../config";
8
+ import { confirm } from "../../dialogs";
9
+ import { CI } from "../../is-ci";
10
+ import isInteractive from "../../is-interactive";
11
+ import { logger } from "../../logger";
12
+ import { requireAuth } from "../../user";
13
+ import { createBackup } from "../backups";
14
+ import { DEFAULT_MIGRATION_PATH, DEFAULT_MIGRATION_TABLE } from "../constants";
15
+ import { executeSql } from "../execute";
16
+ import { d1BetaWarning, getDatabaseInfoFromConfig } from "../utils";
17
+ import {
18
+ getMigrationsPath,
19
+ getUnappliedMigrations,
20
+ initMigrationsTable,
21
+ } from "./helpers";
22
+ import { DatabaseWithLocal } from "./options";
23
+ import type { ParseError } from "../../parse";
24
+ import type { BaseSqlExecuteArgs } from "../execute";
25
+ import type { Argv } from "yargs";
26
+
27
+ export function ApplyOptions(yargs: Argv): Argv<BaseSqlExecuteArgs> {
28
+ return DatabaseWithLocal(yargs);
29
+ }
30
+
31
+ export const ApplyHandler = withConfig<BaseSqlExecuteArgs>(
32
+ async ({ config, database, local, persistTo }): Promise<void> => {
33
+ logger.log(d1BetaWarning);
34
+
35
+ const databaseInfo = await getDatabaseInfoFromConfig(config, database);
36
+ if (!databaseInfo && !local) {
37
+ throw new Error(
38
+ `Can't find a DB with name/binding '${database}' in local config. Check info in wrangler.toml...`
39
+ );
40
+ }
41
+
42
+ if (!config.configPath) {
43
+ return;
44
+ }
45
+
46
+ const migrationsPath = await getMigrationsPath(
47
+ path.dirname(config.configPath),
48
+ databaseInfo?.migrationsFolderPath ?? DEFAULT_MIGRATION_PATH,
49
+ false
50
+ );
51
+
52
+ const migrationTableName =
53
+ databaseInfo?.migrationsTableName ?? DEFAULT_MIGRATION_TABLE;
54
+ await initMigrationsTable(
55
+ migrationTableName,
56
+ local,
57
+ config,
58
+ database,
59
+ persistTo
60
+ );
61
+
62
+ const unappliedMigrations = (
63
+ await getUnappliedMigrations(
64
+ migrationTableName,
65
+ migrationsPath,
66
+ local,
67
+ config,
68
+ database,
69
+ persistTo
70
+ )
71
+ )
72
+ .map((migration) => {
73
+ return {
74
+ Name: migration,
75
+ Status: "🕒️",
76
+ };
77
+ })
78
+ .sort((a, b) => {
79
+ const migrationNumberA = parseInt(a.Name.split("_")[0]);
80
+ const migrationNumberB = parseInt(b.Name.split("_")[0]);
81
+ if (migrationNumberA < migrationNumberB) {
82
+ return -1;
83
+ }
84
+ if (migrationNumberA > migrationNumberB) {
85
+ return 1;
86
+ }
87
+
88
+ // numbers must be equal
89
+ return 0;
90
+ });
91
+
92
+ if (unappliedMigrations.length === 0) {
93
+ render(<Text>✅ No migrations to apply!</Text>);
94
+ return;
95
+ }
96
+
97
+ if (isInteractive() && !CI.isCI()) {
98
+ const ok = await confirm(
99
+ `About to apply ${unappliedMigrations.length} migration(s)\n` +
100
+ "Your database may not be available to serve requests during the migration, continue?",
101
+ <Box flexDirection="column">
102
+ <Text>Migrations to be applied:</Text>
103
+ <Table data={unappliedMigrations} columns={["Name"]}></Table>
104
+ </Box>
105
+ );
106
+ if (!ok) return;
107
+ }
108
+
109
+ // don't backup prod db when applying migrations locally
110
+ if (!local) {
111
+ assert(
112
+ databaseInfo,
113
+ "In non-local mode `databaseInfo` should be defined."
114
+ );
115
+ render(<Text>🕒 Creating backup...</Text>);
116
+ const accountId = await requireAuth({});
117
+ await createBackup(accountId, databaseInfo.uuid);
118
+ }
119
+
120
+ for (const migration of unappliedMigrations) {
121
+ let query = fs.readFileSync(
122
+ `${migrationsPath}/${migration.Name}`,
123
+ "utf8"
124
+ );
125
+ query += `
126
+ INSERT INTO ${migrationTableName} (name)
127
+ values ('${migration.Name}');
128
+ `;
129
+
130
+ let success = true;
131
+ let errorNotes: Array<string> = [];
132
+ try {
133
+ const response = await executeSql(
134
+ local,
135
+ config,
136
+ database,
137
+ isInteractive() && !CI.isCI(),
138
+ persistTo,
139
+ undefined,
140
+ query
141
+ );
142
+
143
+ if (response === null) {
144
+ // TODO: return error
145
+ return;
146
+ }
147
+
148
+ for (const result of response) {
149
+ // When executing more than 1 statement, response turns into an array of QueryResult
150
+ if (Array.isArray(result)) {
151
+ for (const subResult of result) {
152
+ if (!subResult.success) {
153
+ success = false;
154
+ }
155
+ }
156
+ } else {
157
+ if (!result.success) {
158
+ success = false;
159
+ }
160
+ }
161
+ }
162
+ } catch (e) {
163
+ const err = e as ParseError;
164
+
165
+ success = false;
166
+ errorNotes = err.notes.map((msg) => msg.text);
167
+ }
168
+
169
+ migration.Status = success ? "✅" : "❌";
170
+
171
+ render(
172
+ <Box flexDirection="column">
173
+ <Table
174
+ data={unappliedMigrations}
175
+ columns={["Name", "Status"]}
176
+ ></Table>
177
+ {errorNotes.length > 0 && (
178
+ <Box flexDirection="column">
179
+ <Text>&nbsp;</Text>
180
+ <Text>
181
+ ❌ Migration {migration.Name} failed with following Errors
182
+ </Text>
183
+ <Table
184
+ data={errorNotes.map((err) => {
185
+ return { Error: err };
186
+ })}
187
+ ></Table>
188
+ </Box>
189
+ )}
190
+ </Box>
191
+ );
192
+
193
+ if (errorNotes.length > 0) return;
194
+ }
195
+ }
196
+ );