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.
- package/README.md +16 -2
- package/bin/wrangler.js +31 -11
- package/miniflare-dist/index.mjs +1340 -330
- package/package.json +40 -13
- package/src/__tests__/access.test.ts +25 -0
- package/src/__tests__/api-dev.test.ts +238 -0
- package/src/__tests__/api-devregistry.test.js +64 -0
- package/src/__tests__/api.test.ts +80 -0
- package/src/__tests__/configuration.test.ts +683 -470
- package/src/__tests__/d1.test.ts +123 -0
- package/src/__tests__/delete.test.ts +263 -0
- package/src/__tests__/deployments.test.ts +105 -0
- package/src/__tests__/dev.test.tsx +453 -70
- package/src/__tests__/dialogs.test.tsx +40 -0
- package/src/__tests__/generate.test.ts +235 -0
- package/src/__tests__/get-host-from-url.test.ts +16 -0
- package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
- package/src/__tests__/helpers/mock-cfetch.ts +92 -26
- package/src/__tests__/helpers/mock-console.ts +11 -2
- package/src/__tests__/helpers/mock-dialogs.ts +1 -1
- package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
- package/src/__tests__/helpers/mock-known-routes.ts +7 -0
- package/src/__tests__/helpers/mock-kv.ts +1 -1
- package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
- package/src/__tests__/helpers/mock-process.ts +8 -13
- package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
- package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
- package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
- package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
- package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
- package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
- package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
- package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
- package/src/__tests__/helpers/msw/index.ts +21 -0
- package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
- package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
- package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
- package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
- package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
- package/src/__tests__/index.test.ts +103 -97
- package/src/__tests__/init.test.ts +1484 -382
- package/src/__tests__/jest.setup.ts +88 -5
- package/src/__tests__/kv.test.ts +655 -540
- package/src/__tests__/logout.test.ts +47 -0
- package/src/__tests__/metrics.test.ts +95 -45
- package/src/__tests__/middleware.scheduled.test.ts +145 -0
- package/src/__tests__/middleware.test.ts +750 -0
- package/src/__tests__/pages-deployment-tail.test.ts +884 -0
- package/src/__tests__/pages.test.ts +1301 -119
- package/src/__tests__/paths.test.ts +39 -0
- package/src/__tests__/publish.test.ts +2266 -1180
- package/src/__tests__/pubsub.test.ts +211 -88
- package/src/__tests__/queues.test.ts +522 -0
- package/src/__tests__/r2.test.ts +363 -203
- package/src/__tests__/secret.test.ts +261 -59
- package/src/__tests__/tail.test.ts +352 -151
- package/src/__tests__/test-old-node-version.js +3 -3
- package/src/__tests__/tsconfig-sanity.ts +12 -0
- package/src/__tests__/tsconfig.json +8 -0
- package/src/__tests__/tsconfig.tsbuildinfo +1 -0
- package/src/__tests__/type-generation.test.ts +233 -0
- package/src/__tests__/user.test.ts +59 -73
- package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
- package/src/__tests__/validate-dev-props.test.ts +56 -0
- package/src/__tests__/whoami.test.tsx +114 -66
- package/src/__tests__/worker-namespace.test.ts +154 -155
- package/src/api/dev.ts +170 -38
- package/src/api/index.ts +1 -0
- package/src/bundle-reporter.tsx +41 -2
- package/src/bundle.ts +668 -81
- package/src/cfetch/index.ts +40 -3
- package/src/cfetch/internal.ts +144 -32
- package/src/cli.ts +4 -3
- package/src/config/config.ts +27 -3
- package/src/config/environment.ts +131 -7
- package/src/config/index.ts +128 -8
- package/src/config/validation-helpers.ts +19 -6
- package/src/config/validation.ts +411 -14
- package/src/config-cache.ts +3 -2
- package/src/create-worker-preview.ts +31 -6
- package/src/create-worker-upload-form.ts +84 -22
- package/src/d1/backups.tsx +223 -0
- package/src/d1/constants.ts +2 -0
- package/src/d1/create.tsx +63 -0
- package/src/d1/delete.tsx +56 -0
- package/src/d1/execute.tsx +352 -0
- package/src/d1/formatTimeAgo.ts +14 -0
- package/src/d1/index.ts +101 -0
- package/src/d1/list.tsx +48 -0
- package/src/d1/migrations/apply.tsx +196 -0
- package/src/d1/migrations/create.tsx +77 -0
- package/src/d1/migrations/helpers.ts +147 -0
- package/src/d1/migrations/index.tsx +3 -0
- package/src/d1/migrations/list.tsx +79 -0
- package/src/d1/migrations/options.ts +18 -0
- package/src/d1/options.ts +22 -0
- package/src/d1/types.tsx +24 -0
- package/src/d1/utils.ts +48 -0
- package/src/delete.ts +108 -0
- package/src/deployments.ts +83 -0
- package/src/deprecated/index.ts +145 -0
- package/src/dev/dev-vars.ts +11 -8
- package/src/dev/dev.tsx +220 -58
- package/src/dev/get-local-persistence-path.tsx +31 -0
- package/src/dev/local.tsx +740 -181
- package/src/dev/remote.tsx +340 -123
- package/src/dev/start-server.ts +543 -0
- package/src/dev/use-esbuild.ts +61 -7
- package/src/dev/validate-dev-props.ts +31 -0
- package/src/dev-registry.tsx +201 -0
- package/src/dev.tsx +614 -258
- package/src/dialogs.tsx +41 -8
- package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
- package/src/docs/index.ts +97 -0
- package/src/entry.ts +3 -2
- package/src/environment-variables/factory.ts +88 -0
- package/src/environment-variables/misc-variables.ts +30 -0
- package/src/generate/index.ts +300 -0
- package/src/git-client.ts +100 -7
- package/src/index.tsx +249 -1560
- package/src/init.ts +626 -150
- package/src/inspect.ts +137 -32
- package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
- package/src/is-interactive.ts +4 -0
- package/src/jest.d.ts +4 -0
- package/src/{kv.ts → kv/helpers.ts} +3 -3
- package/src/kv/index.ts +596 -0
- package/src/logger.ts +27 -8
- package/src/metrics/index.ts +1 -0
- package/src/metrics/metrics-config.ts +4 -4
- package/src/metrics/metrics-dispatcher.ts +1 -0
- package/src/metrics/metrics-usage-headers.ts +24 -0
- package/src/metrics/send-event.ts +13 -8
- package/src/miniflare-cli/assets.ts +172 -465
- package/src/miniflare-cli/index.ts +149 -24
- package/src/miniflare-cli/tsconfig.json +9 -0
- package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
- package/src/miniflare-cli/types.ts +11 -0
- package/src/module-collection.ts +12 -3
- package/src/pages/build.tsx +108 -42
- package/src/pages/constants.ts +11 -0
- package/src/pages/deployment-tails.tsx +284 -0
- package/src/pages/deployments.tsx +9 -31
- package/src/pages/dev.tsx +417 -108
- package/src/pages/errors.ts +66 -0
- package/src/pages/functions/buildPlugin.ts +86 -84
- package/src/pages/functions/buildWorker.ts +121 -106
- package/src/pages/functions/routes-consolidation.test.ts +250 -0
- package/src/pages/functions/routes-consolidation.ts +73 -0
- package/src/pages/functions/routes-transformation.test.ts +282 -0
- package/src/pages/functions/routes-transformation.ts +115 -0
- package/src/pages/functions/routes-validation.test.ts +403 -0
- package/src/pages/functions/routes-validation.ts +202 -0
- package/src/pages/functions/tsconfig.json +8 -0
- package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
- package/src/pages/functions.tsx +82 -0
- package/src/pages/hash.tsx +13 -0
- package/src/pages/index.tsx +73 -55
- package/src/pages/projects.tsx +9 -3
- package/src/pages/prompt-select-project.tsx +31 -0
- package/src/pages/publish.tsx +183 -60
- package/src/pages/types.ts +1 -0
- package/src/pages/upload.tsx +107 -56
- package/src/pages/utils.ts +13 -2
- package/src/paths.ts +49 -4
- package/src/proxy.ts +375 -157
- package/src/publish/index.ts +270 -0
- package/src/{publish.ts → publish/publish.ts} +431 -188
- package/src/pubsub/pubsub-commands.tsx +4 -3
- package/src/queues/cli/commands/consumer/add.ts +71 -0
- package/src/queues/cli/commands/consumer/index.ts +22 -0
- package/src/queues/cli/commands/consumer/remove.ts +38 -0
- package/src/queues/cli/commands/create.ts +25 -0
- package/src/queues/cli/commands/delete.ts +26 -0
- package/src/queues/cli/commands/index.ts +36 -0
- package/src/queues/cli/commands/list.ts +25 -0
- package/src/queues/client.ts +135 -0
- package/src/queues/utils.ts +18 -0
- package/src/r2/constants.ts +4 -0
- package/src/r2/helpers.ts +131 -0
- package/src/r2/index.ts +289 -0
- package/src/routes.ts +140 -0
- package/src/secret/index.ts +379 -0
- package/src/sites.tsx +3 -3
- package/src/tail/createTail.ts +379 -0
- package/src/tail/filters.ts +4 -6
- package/src/tail/index.ts +199 -284
- package/src/tail/printing.ts +43 -3
- package/src/tsconfig-sanity.ts +16 -0
- package/src/type-generation.ts +181 -0
- package/src/user/access.ts +68 -0
- package/src/user/auth-variables.ts +113 -0
- package/src/user/choose-account.tsx +1 -1
- package/src/user/index.ts +0 -1
- package/src/user/user.tsx +116 -49
- package/src/utils/collectKeyValues.ts +14 -0
- package/src/whoami.tsx +80 -2
- package/src/worker.ts +69 -9
- package/src/yargs-types.ts +24 -0
- package/src/zones.ts +124 -9
- package/templates/__tests__/pages-dev-util.test.ts +128 -0
- package/templates/__tests__/tsconfig-sanity.ts +12 -0
- package/templates/__tests__/tsconfig.json +8 -0
- package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
- package/templates/d1-beta-facade.js +205 -0
- package/templates/facade.d.ts +14 -0
- package/templates/first-party-worker-module-facade.ts +19 -0
- package/templates/format-dev-errors.ts +33 -0
- package/templates/init-tests/test-jest-new-worker.js +23 -0
- package/templates/init-tests/test-vitest-new-worker.js +24 -0
- package/templates/init-tests/test-vitest-new-worker.ts +25 -0
- package/templates/middleware/common.ts +62 -0
- package/templates/middleware/loader-modules.ts +82 -0
- package/templates/middleware/loader-sw.ts +233 -0
- package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
- package/templates/middleware/middleware-pretty-error.ts +40 -0
- package/templates/middleware/middleware-scheduled.ts +15 -0
- package/templates/new-worker.js +1 -1
- package/templates/pages-dev-pipeline.ts +31 -0
- package/templates/pages-dev-util.ts +52 -0
- package/templates/pages-shim.ts +4 -1
- package/templates/pages-template-plugin.ts +21 -5
- package/templates/pages-template-worker.ts +37 -9
- package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
- package/templates/service-bindings-module-facade.js +54 -0
- package/templates/service-bindings-sw-facade.js +42 -0
- package/templates/tsconfig-sanity.ts +11 -0
- package/templates/tsconfig.init.json +106 -0
- package/templates/tsconfig.json +5 -102
- package/templates/tsconfig.tsbuildinfo +1 -0
- package/wrangler-dist/cli.d.ts +621 -42
- package/wrangler-dist/cli.js +144441 -102606
- package/src/environment-variables.ts +0 -35
- package/src/faye-websocket.d.ts +0 -6
- package/src/generate.ts +0 -33
- package/src/miniflare-cli/enum-keys.ts +0 -17
- package/src/preview.ts +0 -31
- package/src/r2.ts +0 -50
- 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
|
+
};
|
package/src/d1/index.ts
ADDED
|
@@ -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
|
+
};
|
package/src/d1/list.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { render } from "ink";
|
|
2
|
+
import Table from "ink-table";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { fetchResult } from "../cfetch";
|
|
5
|
+
import { logger } from "../logger";
|
|
6
|
+
import { requireAuth } from "../user";
|
|
7
|
+
import { d1BetaWarning } from "./utils";
|
|
8
|
+
import type { Database } from "./types";
|
|
9
|
+
import type { ArgumentsCamelCase, Argv } from "yargs";
|
|
10
|
+
|
|
11
|
+
type ListArgs = Record<string, never>;
|
|
12
|
+
|
|
13
|
+
export function Options(d1ListYargs: Argv): Argv<ListArgs> {
|
|
14
|
+
return d1ListYargs.epilogue(d1BetaWarning);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function Handler(_: ArgumentsCamelCase<ListArgs>): Promise<void> {
|
|
18
|
+
const accountId = await requireAuth({});
|
|
19
|
+
logger.log(d1BetaWarning);
|
|
20
|
+
|
|
21
|
+
const dbs: Array<Database> = await listDatabases(accountId);
|
|
22
|
+
|
|
23
|
+
render(<Table data={dbs}></Table>);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const listDatabases = async (
|
|
27
|
+
accountId: string
|
|
28
|
+
): Promise<Array<Database>> => {
|
|
29
|
+
const pageSize = 10;
|
|
30
|
+
let page = 1;
|
|
31
|
+
const results = [];
|
|
32
|
+
while (results.length % pageSize === 0) {
|
|
33
|
+
const json: Array<Database> = await fetchResult(
|
|
34
|
+
`/accounts/${accountId}/d1/database`,
|
|
35
|
+
{},
|
|
36
|
+
new URLSearchParams({
|
|
37
|
+
per_page: pageSize.toString(),
|
|
38
|
+
page: page.toString(),
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
page++;
|
|
42
|
+
results.push(...json);
|
|
43
|
+
if (json.length < pageSize) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
};
|
|
@@ -0,0 +1,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> </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
|
+
);
|