wrangler 0.0.0-e0d2f35 → 0.0.0-e0efb6f17

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 (129) hide show
  1. package/README.md +49 -26
  2. package/bin/wrangler.js +88 -31
  3. package/config-schema.json +2277 -0
  4. package/kv-asset-handler.js +1 -0
  5. package/package.json +146 -94
  6. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  7. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  8. package/templates/__tests__/tsconfig.json +8 -0
  9. package/templates/checked-fetch.js +30 -0
  10. package/templates/facade.d.ts +19 -0
  11. package/templates/gitignore +170 -0
  12. package/templates/init-tests/test-jest-new-worker.js +23 -0
  13. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  14. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  15. package/templates/middleware/common.ts +67 -0
  16. package/templates/middleware/loader-modules.ts +134 -0
  17. package/templates/middleware/loader-sw.ts +229 -0
  18. package/templates/middleware/middleware-ensure-req-body-drained.ts +18 -0
  19. package/templates/middleware/middleware-miniflare3-json-error.ts +32 -0
  20. package/templates/middleware/middleware-mock-analytics-engine.d.ts +3 -0
  21. package/templates/middleware/middleware-mock-analytics-engine.ts +30 -0
  22. package/templates/middleware/middleware-patch-console-prefix.d.ts +3 -0
  23. package/templates/middleware/middleware-patch-console-prefix.ts +21 -0
  24. package/templates/middleware/middleware-pretty-error.ts +40 -0
  25. package/templates/middleware/middleware-scheduled.ts +29 -0
  26. package/templates/modules-watch-stub.js +4 -0
  27. package/templates/new-worker-scheduled.js +17 -0
  28. package/templates/new-worker-scheduled.ts +32 -0
  29. package/templates/new-worker.js +15 -0
  30. package/templates/new-worker.ts +33 -0
  31. package/templates/no-op-worker.js +10 -0
  32. package/templates/pages-dev-pipeline.ts +33 -0
  33. package/templates/pages-dev-util.ts +55 -0
  34. package/templates/pages-shim.ts +9 -0
  35. package/templates/pages-template-plugin.ts +190 -0
  36. package/templates/pages-template-worker.ts +198 -0
  37. package/templates/startDevWorker/InspectorProxyWorker.ts +679 -0
  38. package/templates/startDevWorker/ProxyWorker.ts +336 -0
  39. package/templates/tsconfig-sanity.ts +11 -0
  40. package/templates/tsconfig.init.json +22 -0
  41. package/templates/tsconfig.json +15 -0
  42. package/templates/tsconfig.tsbuildinfo +1 -0
  43. package/wrangler-dist/InspectorProxyWorker.js +471 -0
  44. package/wrangler-dist/InspectorProxyWorker.js.map +6 -0
  45. package/wrangler-dist/ProxyWorker.js +238 -0
  46. package/wrangler-dist/ProxyWorker.js.map +6 -0
  47. package/wrangler-dist/cli.d.ts +26693 -0
  48. package/wrangler-dist/cli.js +149075 -118759
  49. package/wrangler-dist/cli.js.map +4 -4
  50. package/import_meta_url.js +0 -3
  51. package/miniflare-config-stubs/.env.empty +0 -0
  52. package/miniflare-config-stubs/package.empty.json +0 -1
  53. package/miniflare-config-stubs/wrangler.empty.toml +0 -0
  54. package/pages/functions/buildWorker.ts +0 -62
  55. package/pages/functions/filepath-routing.test.ts +0 -39
  56. package/pages/functions/filepath-routing.ts +0 -228
  57. package/pages/functions/identifiers.ts +0 -78
  58. package/pages/functions/routes.ts +0 -158
  59. package/pages/functions/template-worker.ts +0 -136
  60. package/src/__tests__/clipboardy-mock.js +0 -4
  61. package/src/__tests__/dev.test.tsx +0 -71
  62. package/src/__tests__/index.test.ts +0 -424
  63. package/src/__tests__/jest.setup.ts +0 -29
  64. package/src/__tests__/kv.test.ts +0 -1224
  65. package/src/__tests__/logout.test.ts +0 -64
  66. package/src/__tests__/mock-cfetch.ts +0 -173
  67. package/src/__tests__/mock-console.ts +0 -34
  68. package/src/__tests__/mock-dialogs.ts +0 -65
  69. package/src/__tests__/mock-kv.ts +0 -40
  70. package/src/__tests__/mock-user.ts +0 -27
  71. package/src/__tests__/publish.test.ts +0 -1206
  72. package/src/__tests__/run-in-tmp.ts +0 -29
  73. package/src/__tests__/run-wrangler.ts +0 -8
  74. package/src/__tests__/secret.test.ts +0 -206
  75. package/src/__tests__/whoami.test.tsx +0 -101
  76. package/src/api/form_data.ts +0 -135
  77. package/src/api/preview.ts +0 -128
  78. package/src/api/worker.ts +0 -155
  79. package/src/cfetch/index.ts +0 -111
  80. package/src/cfetch/internal.ts +0 -71
  81. package/src/cli.ts +0 -9
  82. package/src/config.ts +0 -641
  83. package/src/dev.tsx +0 -830
  84. package/src/dialogs.tsx +0 -77
  85. package/src/index.tsx +0 -2078
  86. package/src/inspect.ts +0 -595
  87. package/src/kv.tsx +0 -276
  88. package/src/module-collection.ts +0 -63
  89. package/src/pages.tsx +0 -1043
  90. package/src/proxy.ts +0 -370
  91. package/src/publish.ts +0 -410
  92. package/src/sites.tsx +0 -240
  93. package/src/tail.tsx +0 -73
  94. package/src/user.tsx +0 -1019
  95. package/src/whoami.tsx +0 -69
  96. package/static-asset-facade.js +0 -47
  97. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  98. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  99. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  100. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  101. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  102. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  103. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  104. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  105. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  106. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  107. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  108. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  109. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  110. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  111. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  112. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  113. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  114. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  115. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  116. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  117. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  118. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  119. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  120. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  121. package/vendor/wrangler-mime/LICENSE +0 -21
  122. package/vendor/wrangler-mime/Mime.js +0 -97
  123. package/vendor/wrangler-mime/README.md +0 -187
  124. package/vendor/wrangler-mime/cli.js +0 -46
  125. package/vendor/wrangler-mime/index.js +0 -4
  126. package/vendor/wrangler-mime/lite.js +0 -4
  127. package/vendor/wrangler-mime/package.json +0 -52
  128. package/vendor/wrangler-mime/types/other.js +0 -1
  129. package/vendor/wrangler-mime/types/standard.js +0 -1
package/src/index.tsx DELETED
@@ -1,2078 +0,0 @@
1
- import React from "react";
2
- import { render } from "ink";
3
- import Dev from "./dev";
4
- import { readFile } from "node:fs/promises";
5
- import makeCLI from "yargs";
6
- import type Yargs from "yargs";
7
- import { findUp } from "find-up";
8
- import TOML from "@iarna/toml";
9
- import { normaliseAndValidateEnvironmentsConfig } from "./config";
10
- import type { Config } from "./config";
11
- import { getAssetPaths } from "./sites";
12
- import { confirm, prompt } from "./dialogs";
13
- import { version as wranglerVersion } from "../package.json";
14
- import {
15
- login,
16
- logout,
17
- listScopes,
18
- initialise as initialiseUserConfig,
19
- loginOrRefreshIfRequired,
20
- getAccountId,
21
- validateScopeKeys,
22
- } from "./user";
23
- import {
24
- getNamespaceId,
25
- listNamespaces,
26
- listNamespaceKeys,
27
- putKeyValue,
28
- putBulkKeyValue,
29
- deleteBulkKeyValue,
30
- createNamespace,
31
- isValidNamespaceBinding,
32
- } from "./kv";
33
-
34
- import { pages } from "./pages";
35
-
36
- import { fetchResult, fetchRaw } from "./cfetch";
37
-
38
- import publish from "./publish";
39
- import path from "path/posix";
40
- import { writeFile } from "node:fs/promises";
41
- import { toFormData } from "./api/form_data";
42
-
43
- import { createTail } from "./tail";
44
- import onExit from "signal-exit";
45
- import { setTimeout } from "node:timers/promises";
46
- import * as fs from "node:fs";
47
- import { execa } from "execa";
48
- import { whoami } from "./whoami";
49
-
50
- const resetColor = "\x1b[0m";
51
- const fgGreenColor = "\x1b[32m";
52
-
53
- async function readConfig(configPath?: string): Promise<Config> {
54
- const config: Config = {};
55
- if (!configPath) {
56
- configPath = await findUp("wrangler.toml");
57
- // TODO - terminate this early instead of going all the way to the root
58
- }
59
-
60
- if (configPath) {
61
- const tml: string = await readFile(configPath, "utf-8");
62
- const parsed = TOML.parse(tml) as Config;
63
- Object.assign(config, parsed);
64
- }
65
-
66
- normaliseAndValidateEnvironmentsConfig(config);
67
-
68
- if ("experimental_services" in config) {
69
- console.warn(
70
- "The experimental_services field is only for cloudflare internal usage right now, and is subject to change. Please do not use this on production projects"
71
- );
72
- }
73
-
74
- // todo: validate, add defaults
75
- // let's just do some basics for now
76
-
77
- // @ts-expect-error we're being sneaky here for now
78
- config.__path__ = configPath;
79
-
80
- return config;
81
- }
82
-
83
- // a helper to demand one of a set of options
84
- // via https://github.com/yargs/yargs/issues/1093#issuecomment-491299261
85
- function demandOneOfOption(...options: string[]) {
86
- return function (argv: Yargs.Arguments) {
87
- const count = options.filter((option) => argv[option]).length;
88
- const lastOption = options.pop();
89
-
90
- if (count === 0) {
91
- throw new CommandLineArgsError(
92
- `Exactly one of the arguments ${options.join(
93
- ", "
94
- )} and ${lastOption} is required`
95
- );
96
- } else if (count > 1) {
97
- throw new CommandLineArgsError(
98
- `Arguments ${options.join(
99
- ", "
100
- )} and ${lastOption} are mutually exclusive`
101
- );
102
- }
103
-
104
- return true;
105
- };
106
- }
107
-
108
- class CommandLineArgsError extends Error {}
109
- class DeprecationError extends Error {}
110
- class NotImplementedError extends Error {}
111
-
112
- export async function main(argv: string[]): Promise<void> {
113
- const wrangler = makeCLI(argv)
114
- // We handle errors ourselves in a try-catch around `yargs.parse`.
115
- // If you want the "help info" to be displayed then throw an instance of `CommandLineArgsError`.
116
- // Otherwise we just log the error that was thrown without any "help info".
117
- .showHelpOnFail(false)
118
- .fail((msg, error) => {
119
- if (!error) {
120
- // If there is only a `msg` then this came from yargs own validation, so wrap in a `CommandLineArgsError`.
121
- error = new CommandLineArgsError(msg);
122
- }
123
- throw error;
124
- })
125
- .scriptName("wrangler")
126
- .wrap(null);
127
-
128
- // the default is to simply print the help menu
129
- wrangler.command(
130
- ["*"],
131
- false,
132
- () => {},
133
- (args) => {
134
- if (args._.length > 0) {
135
- throw new CommandLineArgsError(`Unknown command: ${args._}.`);
136
- } else {
137
- wrangler.showHelp("log");
138
- }
139
- }
140
- );
141
-
142
- // You will note that we use the form for all commands where we use the builder function
143
- // to define options and subcommands.
144
- // Further we return the result of this builder even though it's not completely necessary.
145
- // The reason is that it's required for type inference of the args in the handle function.
146
- // I wish we could enforce this pattern, but this comment will have to do for now.
147
- // (It's also annoying that choices[] doesn't get inferred as an enum. 🤷‍♂.)
148
-
149
- // [DEPRECATED] generate
150
- wrangler.command(
151
- // we definitely want to move away from us cloning github templates
152
- // we can do something better here, let's see
153
- "generate [name] [template]",
154
- false,
155
- (yargs) => {
156
- return yargs
157
- .positional("name", {
158
- describe: "Name of the Workers project",
159
- default: "worker",
160
- })
161
- .positional("template", {
162
- describe: "a link to a GitHub template",
163
- default: "https://github.com/cloudflare/worker-template",
164
- });
165
- },
166
- () => {
167
- // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
168
- throw new DeprecationError(
169
- "`wrangler generate` has been deprecated, please refer to TODO://some/path for alternatives"
170
- );
171
- }
172
- );
173
-
174
- // init
175
- wrangler.command(
176
- "init [name]",
177
- "📥 Create a wrangler.toml configuration file",
178
- (yargs) => {
179
- return yargs.positional("name", {
180
- describe: "The name of your worker.",
181
- type: "string",
182
- });
183
- },
184
- async (args) => {
185
- if ("type" in args) {
186
- let message = "The --type option is no longer supported.";
187
- if (args.type === "webpack") {
188
- message +=
189
- "\nIf you wish to use webpack then you will need to create a custom build.";
190
- // TODO: Add a link to docs
191
- }
192
- throw new CommandLineArgsError(message);
193
- }
194
-
195
- const destination = path.join(process.cwd(), "wrangler.toml");
196
- if (fs.existsSync(destination)) {
197
- console.warn(`${destination} file already exists!`);
198
- const shouldContinue = await confirm(
199
- "Do you want to continue initializing this project?"
200
- );
201
- if (!shouldContinue) {
202
- return;
203
- }
204
- } else {
205
- const compatibilityDate = new Date().toISOString().substring(0, 10);
206
- try {
207
- await writeFile(
208
- destination,
209
- `compatibility_date = "${compatibilityDate}"` + "\n"
210
- );
211
- console.log(`✨ Successfully created wrangler.toml`);
212
- // TODO: suggest next steps?
213
- } catch (err) {
214
- throw new Error(
215
- `Failed to create wrangler.toml.\n${err.message ?? err}`
216
- );
217
- }
218
- }
219
-
220
- let pathToPackageJson = await findUp("package.json");
221
- if (!pathToPackageJson) {
222
- // If no package.json exists, ask to create one
223
- const shouldCreatePackageJson = await confirm(
224
- "No package.json found. Would you like to create one?"
225
- );
226
- if (shouldCreatePackageJson) {
227
- await writeFile(
228
- path.join(process.cwd(), "package.json"),
229
- JSON.stringify(
230
- {
231
- name: "worker",
232
- version: "0.0.1",
233
- devDependencies: {
234
- wrangler: wranglerVersion,
235
- },
236
- },
237
- null,
238
- " "
239
- ) + "\n"
240
- );
241
- await execa("npm", ["install"], {
242
- stdio: "inherit",
243
- });
244
- console.log(`✨ Created package.json`);
245
- pathToPackageJson = path.join(process.cwd(), "package.json");
246
- } else {
247
- return;
248
- }
249
- } else {
250
- // If package.json exists and wrangler isn't installed,
251
- // then ask to add wrangler to devDependencies
252
- const packageJson = JSON.parse(
253
- await readFile(pathToPackageJson, "utf-8")
254
- );
255
- if (
256
- !(
257
- packageJson.devDependencies?.wrangler ||
258
- packageJson.dependencies?.wrangler
259
- )
260
- ) {
261
- const shouldInstall = await confirm(
262
- "Would you like to install wrangler into your package.json?"
263
- );
264
- if (shouldInstall) {
265
- await execa(
266
- "npm",
267
- ["install", `wrangler@${wranglerVersion}`, "--save-dev"],
268
- {
269
- stdio: "inherit",
270
- }
271
- );
272
- console.log(`✨ Installed wrangler`);
273
- }
274
- }
275
- }
276
-
277
- let pathToTSConfig = await findUp("tsconfig.json");
278
- if (!pathToTSConfig) {
279
- // If there's no tsconfig, offer to create one
280
- // and install @cloudflare/workers-types
281
- if (await confirm("Would you like to use typescript?")) {
282
- await writeFile(
283
- path.join(process.cwd(), "tsconfig.json"),
284
- JSON.stringify(
285
- {
286
- compilerOptions: {
287
- target: "esnext",
288
- module: "esnext",
289
- moduleResolution: "node",
290
- esModuleInterop: true,
291
- allowJs: true,
292
- allowSyntheticDefaultImports: true,
293
- isolatedModules: true,
294
- noEmit: true,
295
- lib: ["esnext"],
296
- jsx: "react",
297
- resolveJsonModule: true,
298
- types: ["@cloudflare/workers-types"],
299
- },
300
- },
301
- null,
302
- " "
303
- ) + "\n"
304
- );
305
- await execa(
306
- "npm",
307
- ["install", "@cloudflare/workers-types", "--save-dev"],
308
- { stdio: "inherit" }
309
- );
310
- console.log(
311
- `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
312
- );
313
- pathToTSConfig = path.join(process.cwd(), "tsconfig.json");
314
- }
315
- } else {
316
- // If there's a tsconfig, check if @cloudflare/workers-types
317
- // is already installed, and offer to install it if not
318
- const packageJson = JSON.parse(
319
- await readFile(pathToPackageJson, "utf-8")
320
- );
321
- if (
322
- !(
323
- packageJson.devDependencies?.["@cloudflare/workers-types"] ||
324
- packageJson.dependencies?.["@cloudflare/workers-types"]
325
- )
326
- ) {
327
- const shouldInstall = await confirm(
328
- "Would you like to install the type definitions for Workers into your package.json?"
329
- );
330
- if (shouldInstall) {
331
- await execa(
332
- "npm",
333
- ["install", "@cloudflare/workers-types", "--save-dev"],
334
- {
335
- stdio: "inherit",
336
- }
337
- );
338
- // We don't update the tsconfig.json because
339
- // it could be complicated in existing projects
340
- // and we don't want to break them. Instead, we simply
341
- // tell the user that they need to update their tsconfig.json
342
- console.log(
343
- `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
344
- );
345
- } else {
346
- return;
347
- }
348
- }
349
- }
350
- }
351
- );
352
-
353
- // build
354
- wrangler.command(
355
- "build",
356
- false,
357
- (yargs) => {
358
- return yargs.option("env", {
359
- describe: "Perform on a specific environment",
360
- });
361
- },
362
- () => {
363
- // "[DEPRECATED] 🦀 Build your project (if applicable)",
364
- throw new DeprecationError(
365
- "`wrangler build` has been deprecated, please refer to TODO://some/path for alternatives"
366
- );
367
- }
368
- );
369
-
370
- // login
371
- wrangler.command(
372
- // this needs scopes as an option?
373
- "login",
374
- false, // we don't need to show this in the menu
375
- // "🔓 Login to Cloudflare",
376
- (yargs) => {
377
- // TODO: This needs some copy editing
378
- // I mean, this entire app does, but this too.
379
- return yargs
380
- .option("scopes-list", {
381
- describe: "list all the available OAuth scopes with descriptions.",
382
- })
383
- .option("scopes", {
384
- describe: "allows to choose your set of OAuth scopes.",
385
- array: true,
386
- type: "string",
387
- });
388
-
389
- // TODO: scopes
390
- },
391
- async (args) => {
392
- if (args["scopes-list"]) {
393
- listScopes();
394
- return;
395
- }
396
- if (args.scopes) {
397
- if (args.scopes.length === 0) {
398
- // don't allow no scopes to be passed, that would be weird
399
- listScopes();
400
- return;
401
- }
402
- if (!validateScopeKeys(args.scopes)) {
403
- throw new CommandLineArgsError(
404
- `One of ${args.scopes} is not a valid authentication scope. Run "wrangler login --list-scopes" to see the valid scopes.`
405
- );
406
- }
407
- await login({ scopes: args.scopes });
408
- return;
409
- }
410
- await login();
411
-
412
- // TODO: would be nice if it optionally saved login
413
- // credentials inside node_modules/.cache or something
414
- // this way you could have multiple users on a single machine
415
- }
416
- );
417
-
418
- // logout
419
- wrangler.command(
420
- // this needs scopes as an option?
421
- "logout",
422
- false, // we don't need to show this in the menu
423
- // "🚪 Logout from Cloudflare",
424
- () => {},
425
- async () => {
426
- await logout();
427
- }
428
- );
429
-
430
- // whoami
431
- wrangler.command(
432
- "whoami",
433
- "🕵️ Retrieve your user info and test your auth config",
434
- () => {},
435
- async () => {
436
- await whoami();
437
- }
438
- );
439
-
440
- // config
441
- wrangler.command(
442
- "config",
443
- false,
444
- () => {},
445
- () => {
446
- // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
447
- throw new DeprecationError(
448
- "`wrangler config` has been deprecated, please refer to TODO://some/path for alternatives"
449
- );
450
- }
451
- );
452
-
453
- // dev
454
- wrangler.command(
455
- "dev <filename>",
456
- "👂 Start a local server for developing your worker",
457
- (yargs) => {
458
- return yargs
459
- .positional("filename", {
460
- describe: "entry point",
461
- type: "string",
462
- demandOption: true,
463
- })
464
- .option("name", {
465
- describe: "name of the script",
466
- type: "string",
467
- })
468
- .option("format", {
469
- choices: ["modules", "service-worker"] as const,
470
- describe: "Choose an entry type",
471
- })
472
- .option("env", {
473
- describe: "Perform on a specific environment",
474
- type: "string",
475
- // TODO: get choices for the toml file?
476
- })
477
- .option("compatibility-date", {
478
- describe: "Date to use for compatibility checks",
479
- type: "string",
480
- })
481
- .option("compatibility-flags", {
482
- describe: "Flags to use for compatibility checks",
483
- type: "array",
484
- alias: "compatibility-flag",
485
- })
486
- .option("latest", {
487
- describe: "Use the latest version of the worker runtime",
488
- type: "boolean",
489
- default: true,
490
- })
491
- .option("ip", {
492
- describe: "IP address to listen on",
493
- type: "string",
494
- default: "127.0.0.1",
495
- })
496
- .option("port", {
497
- describe: "Port to listen on, defaults to 8787",
498
- type: "number",
499
- default: 8787,
500
- })
501
- .option("host", {
502
- type: "string",
503
- describe:
504
- "Host to forward requests to, defaults to the zone of project",
505
- })
506
- .option("local-protocol", {
507
- default: "http",
508
- describe: "Protocol to listen to requests on, defaults to http.",
509
- choices: ["http", "https"],
510
- })
511
- .option("experimental-public", {
512
- describe: "Static assets to be served",
513
- type: "string",
514
- })
515
- .option("site", {
516
- describe: "Root folder of static assets for Workers Sites",
517
- type: "string",
518
- })
519
- .option("site-include", {
520
- describe:
521
- "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
522
- type: "string",
523
- array: true,
524
- })
525
- .option("site-exclude", {
526
- describe:
527
- "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
528
- type: "string",
529
- array: true,
530
- })
531
- .option("upstream-protocol", {
532
- default: "https",
533
- describe:
534
- "Protocol to forward requests to host on, defaults to https.",
535
- choices: ["http", "https"],
536
- })
537
- .option("jsx-factory", {
538
- describe: "The function that is called for each JSX element",
539
- type: "string",
540
- })
541
- .option("jsx-fragment", {
542
- describe: "The function that is called for each JSX fragment",
543
- type: "string",
544
- })
545
- .option("experimental-enable-local-persistence", {
546
- describe: "Enable persistence for this session (only for local mode)",
547
- type: "boolean",
548
- });
549
- },
550
- async (args) => {
551
- const { filename, format } = args;
552
- const config = args.config as Config;
553
-
554
- if (args["experimental-public"]) {
555
- console.warn(
556
- "🚨 The --experimental-public field is experimental and will change in the future."
557
- );
558
- }
559
-
560
- if (args.public) {
561
- throw new Error(
562
- "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
563
- );
564
- }
565
-
566
- if (config.site?.["entry-point"]) {
567
- console.warn(
568
- "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
569
- "The entry-point is specified via the command line (e.g. `wrangler dev path/to/script`).\n" +
570
- "Please remove the `site.entry-point` field from the `wrangler.toml` file."
571
- );
572
- }
573
-
574
- if (!args.local) {
575
- // -- snip, extract --
576
- const loggedIn = await loginOrRefreshIfRequired();
577
- if (!loggedIn) {
578
- // didn't login, let's just quit
579
- console.log("Did not login, quitting...");
580
- return;
581
- }
582
-
583
- if (!config.account_id) {
584
- config.account_id = await getAccountId();
585
- if (!config.account_id) {
586
- throw new Error("No account id found, quitting...");
587
- }
588
- }
589
- // -- snip, end --
590
- }
591
-
592
- const environments = config.env ?? {};
593
- const envRootObj = args.env ? environments[args.env] || {} : config;
594
-
595
- // TODO: this error shouldn't actually happen,
596
- // but we haven't fixed it internally yet
597
- if ("durable_objects" in envRootObj) {
598
- if (!(args.name || config.name)) {
599
- console.warn(
600
- 'A worker with durable objects needs to be named, or it may not work as expected. Add a "name" into wrangler.toml, or pass it in the command line with --name.'
601
- );
602
- }
603
- // TODO: if not already published, publish a draft worker
604
- }
605
-
606
- const { waitUntilExit } = render(
607
- <Dev
608
- name={args.name || config.name}
609
- entry={path.relative(process.cwd(), filename)}
610
- env={args.env}
611
- buildCommand={config.build || {}}
612
- format={format}
613
- initialMode={args.local ? "local" : "remote"}
614
- jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
615
- jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
616
- enableLocalPersistence={
617
- args["experimental-enable-local-persistence"] || false
618
- }
619
- accountId={config.account_id}
620
- assetPaths={getAssetPaths(
621
- config,
622
- args.site,
623
- args.siteInclude,
624
- args.siteExclude
625
- )}
626
- port={args.port || config.dev?.port}
627
- public={args["experimental-public"]}
628
- compatibilityDate={
629
- args["compatibility-date"] ||
630
- config.compatibility_date ||
631
- new Date().toISOString().substring(0, 10)
632
- }
633
- compatibilityFlags={
634
- (args["compatibility-flags"] as string[]) ||
635
- config.compatibility_flags
636
- }
637
- usageModel={config.usage_model}
638
- bindings={{
639
- kv_namespaces: envRootObj.kv_namespaces?.map(
640
- ({ binding, preview_id, id: _id }) => {
641
- // In `dev`, we make folks use a separate kv namespace called
642
- // `preview_id` instead of `id` so that they don't
643
- // break production data. So here we check that a `preview_id`
644
- // has actually been configured.
645
- // This whole block of code will be obsoleted in the future
646
- // when we have copy-on-write for previews on edge workers.
647
- if (!preview_id) {
648
- // TODO: This error has to be a _lot_ better, ideally just asking
649
- // to create a preview namespace for the user automatically
650
- throw new Error(
651
- `In development, you should use a separate kv namespace than the one you'd use in production. Please create a new kv namespace with "wrangler kv:namespace create <name> --preview" and add its id as preview_id to the kv_namespace "${binding}" in your wrangler.toml`
652
- ); // Ugh, I really don't like this message very much
653
- }
654
- return {
655
- binding,
656
- id: preview_id,
657
- };
658
- }
659
- ),
660
- vars: envRootObj.vars,
661
- durable_objects: envRootObj.durable_objects,
662
- services: envRootObj.experimental_services,
663
- }}
664
- />
665
- );
666
- await waitUntilExit();
667
- }
668
- );
669
-
670
- // publish
671
- wrangler.command(
672
- "publish [script]",
673
- "🆙 Publish your Worker to Cloudflare.",
674
- (yargs) => {
675
- return yargs
676
- .option("env", {
677
- type: "string",
678
- describe: "Perform on a specific environment",
679
- })
680
- .positional("script", {
681
- describe: "script to upload",
682
- type: "string",
683
- })
684
- .option("name", {
685
- describe: "name to use when uploading",
686
- type: "string",
687
- })
688
- .option("compatibility-date", {
689
- describe: "Date to use for compatibility checks",
690
- type: "string",
691
- })
692
- .option("compatibility-flags", {
693
- describe: "Flags to use for compatibility checks",
694
- type: "array",
695
- alias: "compatibility-flag",
696
- })
697
- .option("latest", {
698
- describe: "Use the latest version of the worker runtime",
699
- type: "boolean",
700
- default: false,
701
- })
702
- .option("experimental-public", {
703
- describe: "Static assets to be served",
704
- type: "string",
705
- })
706
- .option("site", {
707
- describe: "Root folder of static assets for Workers Sites",
708
- type: "string",
709
- })
710
- .option("site-include", {
711
- describe:
712
- "Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.",
713
- type: "string",
714
- array: true,
715
- })
716
- .option("site-exclude", {
717
- describe:
718
- "Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.",
719
- type: "string",
720
- array: true,
721
- })
722
- .option("triggers", {
723
- describe: "cron schedules to attach",
724
- alias: ["schedule", "schedules"],
725
- type: "array",
726
- })
727
- .option("routes", {
728
- describe: "routes to upload",
729
- alias: "route",
730
- type: "array",
731
- })
732
- .option("services", {
733
- describe: "experimental support for services",
734
- type: "boolean",
735
- default: "false",
736
- hidden: true,
737
- })
738
- .option("jsx-factory", {
739
- describe: "The function that is called for each JSX element",
740
- type: "string",
741
- })
742
- .option("jsx-fragment", {
743
- describe: "The function that is called for each JSX fragment",
744
- type: "string",
745
- });
746
- },
747
- async (args) => {
748
- if (args.local) {
749
- throw new NotImplementedError(
750
- "🚫 Local publishing is not yet supported"
751
- );
752
- }
753
-
754
- if (args["experimental-public"]) {
755
- console.warn(
756
- "🚨 The --experimental-public field is experimental and will change in the future."
757
- );
758
- }
759
- if (args.public) {
760
- throw new Error(
761
- "🚨 The --public field has been renamed to --experimental-public, and will change behaviour in the future."
762
- );
763
- }
764
-
765
- const config = args.config as Config;
766
-
767
- if (args.latest) {
768
- console.warn(
769
- "⚠️ Using the latest version of the Workers runtime. To silence this warning, please choose a specific version of the runtime with --compatibility-date, or add a compatibility_date to your wrangler.toml.\n"
770
- );
771
- }
772
-
773
- if (!args.local) {
774
- // -- snip, extract --
775
- const loggedIn = await loginOrRefreshIfRequired();
776
- if (!loggedIn) {
777
- // didn't login, let's just quit
778
- console.log("Did not login, quitting...");
779
- return;
780
- }
781
-
782
- if (!config.account_id) {
783
- config.account_id = await getAccountId();
784
- if (!config.account_id) {
785
- throw new Error("No account id found, quitting...");
786
- }
787
- }
788
- // -- snip, end --
789
- }
790
-
791
- const assetPaths = getAssetPaths(
792
- config,
793
- args["experimental-public"] || args.site,
794
- args.siteInclude,
795
- args.siteExclude
796
- );
797
- await publish({
798
- config: args.config as Config,
799
- name: args.name,
800
- script: args.script,
801
- env: args.env,
802
- compatibilityDate: args.latest
803
- ? new Date().toISOString().substring(0, 10)
804
- : args["compatibility-date"],
805
- compatibilityFlags: args["compatibility-flags"] as string[],
806
- triggers: args.triggers,
807
- jsxFactory: args["jsx-factory"],
808
- jsxFragment: args["jsx-fragment"],
809
- routes: args.routes,
810
- assetPaths,
811
- format: undefined, // TODO: add args for this
812
- legacyEnv: undefined, // TODO: get this from somewhere... config?
813
- experimentalPublic: args["experimental-public"] !== undefined,
814
- });
815
- }
816
- );
817
-
818
- // tail
819
- wrangler.command(
820
- "tail [name]",
821
- "🦚 Starts a log tailing session for a deployed Worker.",
822
- (yargs) => {
823
- return (
824
- yargs
825
- .positional("name", {
826
- describe: "name of the worker",
827
- type: "string",
828
- })
829
- // TODO: auto-detect if this should be json or pretty based on atty
830
- .option("format", {
831
- default: "json",
832
- choices: ["json", "pretty"],
833
- describe: "The format of log entries",
834
- })
835
- .option("status", {
836
- choices: ["ok", "error", "canceled"],
837
- describe: "Filter by invocation status",
838
- })
839
- .option("header", {
840
- type: "string",
841
- describe: "Filter by HTTP header",
842
- })
843
- .option("method", {
844
- type: "string",
845
- describe: "Filter by HTTP method",
846
- })
847
- .option("sampling-rate", {
848
- type: "number",
849
- describe: "Adds a percentage of requests to log sampling rate",
850
- })
851
- .option("search", {
852
- type: "string",
853
- describe: "Filter by a text match in console.log messages",
854
- })
855
- .option("env", {
856
- type: "string",
857
- describe: "Perform on a specific environment",
858
- })
859
- );
860
- // TODO: filter by client ip, which can be 'self' or an ip address
861
- },
862
- async (args) => {
863
- if (args.local) {
864
- throw new NotImplementedError(
865
- `local mode is not yet supported for this command`
866
- );
867
- }
868
-
869
- const config = args.config as Config;
870
-
871
- if (!(args.name || config.name)) {
872
- throw new Error("Missing script name");
873
- }
874
- const scriptName = `${args.name || config.name}${
875
- args.env ? `-${args.env}` : ""
876
- }`;
877
-
878
- // -- snip, extract --
879
- const loggedIn = await loginOrRefreshIfRequired();
880
- if (!loggedIn) {
881
- // didn't login, let's just quit
882
- console.log("Did not login, quitting...");
883
- return;
884
- }
885
-
886
- if (!config.account_id) {
887
- config.account_id = await getAccountId();
888
- if (!config.account_id) {
889
- throw new Error("No account id found, quitting...");
890
- }
891
- }
892
- // -- snip, end --
893
-
894
- const accountId = config.account_id;
895
-
896
- const filters = {
897
- status: args.status as "ok" | "error" | "canceled",
898
- header: args.header,
899
- method: args.method,
900
- "sampling-rate": args["sampling-rate"],
901
- search: args.search,
902
- };
903
-
904
- const { tail, expiration, /* sendHeartbeat, */ deleteTail } =
905
- await createTail(accountId, scriptName, filters);
906
-
907
- console.log(
908
- `successfully created tail, expires at ${expiration.toLocaleString()}`
909
- );
910
-
911
- onExit(async () => {
912
- tail.terminate();
913
- await deleteTail();
914
- });
915
-
916
- tail.on("message", (data) => {
917
- console.log(JSON.stringify(JSON.parse(data.toString()), null, " "));
918
- });
919
-
920
- while (tail.readyState !== tail.OPEN) {
921
- switch (tail.readyState) {
922
- case tail.CONNECTING:
923
- await setTimeout(1000);
924
- break;
925
- case tail.CLOSING:
926
- await setTimeout(1000);
927
- break;
928
- case tail.CLOSED:
929
- process.exit(1);
930
- }
931
- }
932
-
933
- console.log(`Connected to ${scriptName}, waiting for logs...`);
934
- }
935
- );
936
-
937
- // preview
938
- wrangler.command(
939
- "preview [method] [body]",
940
- false,
941
- (yargs) => {
942
- return yargs
943
- .positional("method", {
944
- describe: "Type of request to preview your worker",
945
- choices: ["GET", "POST"],
946
- default: ["GET"],
947
- })
948
- .positional("body", {
949
- type: "string",
950
- describe: "Body string to post to your preview worker request.",
951
- default: "Null",
952
- })
953
- .option("env", {
954
- type: "string",
955
- describe: "Perform on a specific environment",
956
- })
957
- .option("watch", {
958
- default: true,
959
- describe: "Enable live preview",
960
- type: "boolean",
961
- });
962
- },
963
- () => {
964
- // "🔬 [DEPRECATED] Preview your code temporarily on https://cloudflareworkers.com"
965
- throw new DeprecationError(
966
- "`wrangler preview` has been deprecated, please refer to TODO://some/path for alternatives"
967
- );
968
- }
969
- );
970
-
971
- // route
972
- wrangler.command(
973
- "route",
974
- false, // I think we want to hide this command
975
- // "➡️ List or delete worker routes",
976
- (routeYargs) => {
977
- return routeYargs
978
- .command(
979
- "list",
980
- "List a route associated with a zone",
981
- (yargs) => {
982
- return yargs
983
- .option("env", {
984
- type: "string",
985
- describe: "Perform on a specific environment",
986
- })
987
- .option("zone", {
988
- type: "string",
989
- describe: "zone id",
990
- })
991
- .positional("zone", {
992
- describe: "zone id",
993
- type: "string",
994
- });
995
- },
996
- async (args) => {
997
- console.log(":route list", args);
998
- // TODO: use environment (current wrangler doesn't do so?)
999
- const zone = args.zone || (args.config as Config).zone_id;
1000
- if (!zone) {
1001
- throw new Error("missing zone id");
1002
- }
1003
-
1004
- console.log(await fetchResult(`/zones/${zone}/workers/routes`));
1005
- }
1006
- )
1007
- .command(
1008
- "delete <id>",
1009
- "Delete a route associated with a zone",
1010
- (yargs) => {
1011
- return yargs
1012
- .positional("id", {
1013
- describe: "The hash of the route ID to delete.",
1014
- type: "string",
1015
- })
1016
- .option("zone", {
1017
- type: "string",
1018
- describe: "zone id",
1019
- })
1020
- .option("env", {
1021
- type: "string",
1022
- describe: "Perform on a specific environment",
1023
- });
1024
- },
1025
- async (args) => {
1026
- console.log(":route delete", args);
1027
- // TODO: use environment (current wrangler doesn't do so?)
1028
- const zone = args.zone || (args.config as Config).zone_id;
1029
- if (!zone) {
1030
- throw new Error("missing zone id");
1031
- }
1032
-
1033
- console.log(
1034
- await fetchResult(`/zones/${zone}/workers/routes/${args.id}`, {
1035
- method: "DELETE",
1036
- })
1037
- );
1038
- }
1039
- );
1040
- }
1041
- );
1042
-
1043
- // subdomain
1044
- wrangler.command(
1045
- "subdomain [name]",
1046
- false,
1047
- // "👷 Create or change your workers.dev subdomain.",
1048
- (yargs) => {
1049
- return yargs.positional("name", { type: "string" });
1050
- },
1051
- () => {
1052
- throw new DeprecationError(
1053
- "`wrangler subdomain` has been deprecated, please refer to TODO://some/path for alternatives"
1054
- );
1055
- }
1056
- );
1057
-
1058
- // secret
1059
- wrangler.command(
1060
- "secret",
1061
- "🤫 Generate a secret that can be referenced in the worker script",
1062
- (secretYargs) => {
1063
- return secretYargs
1064
- .command(
1065
- "put <key>",
1066
- "Create or update a secret variable for a script",
1067
- (yargs) => {
1068
- return yargs
1069
- .positional("key", {
1070
- describe: "The variable name to be accessible in the script.",
1071
- type: "string",
1072
- })
1073
- .option("name", {
1074
- describe: "name of the script",
1075
- type: "string",
1076
- })
1077
- .option("env", {
1078
- type: "string",
1079
- describe:
1080
- "Binds the secret to the script of the specific environment.",
1081
- });
1082
- },
1083
- async (args) => {
1084
- const config = args.config as Config;
1085
-
1086
- // TODO: use environment (how does current wrangler do it?)
1087
- const scriptName = args.name || config.name;
1088
- if (!scriptName) {
1089
- throw new Error("Missing script name");
1090
- }
1091
-
1092
- if (args.local) {
1093
- console.warn("`wrangler secret put` is a no-op in --local mode");
1094
- }
1095
-
1096
- if (!args.local) {
1097
- // -- snip, extract --
1098
- const loggedIn = await loginOrRefreshIfRequired();
1099
- if (!loggedIn) {
1100
- // didn't login, let's just quit
1101
- console.log("Did not login, quitting...");
1102
- return;
1103
- }
1104
-
1105
- if (!config.account_id) {
1106
- config.account_id = await getAccountId();
1107
- if (!config.account_id) {
1108
- throw new Error("No account id found, quitting...");
1109
- }
1110
- }
1111
- // -- snip, end --
1112
- }
1113
-
1114
- const secretValue = await prompt(
1115
- "Enter a secret value:",
1116
- "password"
1117
- );
1118
-
1119
- if (args.local) {
1120
- return;
1121
- }
1122
-
1123
- console.log(`🌀 Creating the secret for script ${scriptName}`);
1124
-
1125
- async function submitSecret() {
1126
- return await fetchResult(
1127
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
1128
- {
1129
- method: "PUT",
1130
- headers: { "Content-Type": "application/json" },
1131
- body: JSON.stringify({
1132
- name: args.key,
1133
- text: secretValue,
1134
- type: "secret_text",
1135
- }),
1136
- }
1137
- );
1138
- }
1139
-
1140
- try {
1141
- await submitSecret();
1142
- } catch (e) {
1143
- if (e.code === 10007) {
1144
- // upload a draft worker
1145
- await fetchResult(
1146
- `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
1147
- {
1148
- method: "PUT",
1149
- // @ts-expect-error TODO: fix this error!
1150
- body: toFormData({
1151
- main: {
1152
- name: scriptName,
1153
- content: `export default { fetch() {} }`,
1154
- type: "esm",
1155
- },
1156
- bindings: {
1157
- kv_namespaces: [],
1158
- vars: {},
1159
- durable_objects: { bindings: [] },
1160
- },
1161
- modules: [],
1162
- }),
1163
- }
1164
- );
1165
-
1166
- // and then try again
1167
- await submitSecret();
1168
- // TODO: delete the draft worker if this failed too?
1169
- }
1170
- }
1171
- console.log(`✨ Success! Uploaded secret ${args.key}`);
1172
- }
1173
- )
1174
- .command(
1175
- "delete <key>",
1176
- "Delete a secret variable from a script",
1177
- (yargs) => {
1178
- return yargs
1179
- .positional("key", {
1180
- describe: "The variable name to be accessible in the script.",
1181
- type: "string",
1182
- })
1183
- .option("name", {
1184
- describe: "name of the script",
1185
- type: "string",
1186
- })
1187
- .option("env", {
1188
- type: "string",
1189
- describe:
1190
- "Binds the secret to the script of the specific environment.",
1191
- });
1192
- },
1193
- async (args) => {
1194
- const config = args.config as Config;
1195
-
1196
- // TODO: use environment (how does current wrangler do it?)
1197
- const scriptName = args.name || config.name;
1198
- if (!scriptName) {
1199
- throw new Error("Missing script name");
1200
- }
1201
-
1202
- if (args.local) {
1203
- console.warn(
1204
- "`wrangler secret delete` is a no-op in --local mode"
1205
- );
1206
- }
1207
-
1208
- if (!args.local) {
1209
- // -- snip, extract --
1210
- const loggedIn = await loginOrRefreshIfRequired();
1211
- if (!loggedIn) {
1212
- // didn't login, let's just quit
1213
- console.log("Did not login, quitting...");
1214
- return;
1215
- }
1216
-
1217
- if (!config.account_id) {
1218
- config.account_id = await getAccountId();
1219
- if (!config.account_id) {
1220
- throw new Error("No account id found, quitting...");
1221
- }
1222
- }
1223
- // -- snip, end --
1224
- }
1225
-
1226
- if (
1227
- await confirm(
1228
- `Are you sure you want to permanently delete the variable ${args.key} on the script ${scriptName}?`
1229
- )
1230
- ) {
1231
- console.log(
1232
- `🌀 Deleting the secret ${args.key} on script ${scriptName}.`
1233
- );
1234
-
1235
- if (args.local) {
1236
- return;
1237
- }
1238
-
1239
- await fetchResult(
1240
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1241
- { method: "DELETE" }
1242
- );
1243
- console.log(`✨ Success! Deleted secret ${args.key}`);
1244
- }
1245
- }
1246
- )
1247
- .command(
1248
- "list",
1249
- "List all secrets for a script",
1250
- (yargs) => {
1251
- return yargs
1252
- .option("name", {
1253
- describe: "name of the script",
1254
- type: "string",
1255
- })
1256
- .option("env", {
1257
- type: "string",
1258
- describe:
1259
- "Binds the secret to the script of the specific environment.",
1260
- });
1261
- },
1262
- async (args) => {
1263
- const config = args.config as Config;
1264
-
1265
- // TODO: use environment (how does current wrangler do it?)
1266
- const scriptName = args.name || config.name;
1267
- if (!scriptName) {
1268
- throw new Error("Missing script name");
1269
- }
1270
-
1271
- if (args.local) {
1272
- console.warn("`wrangler secret list` is a no-op in --local mode");
1273
- }
1274
-
1275
- if (!args.local) {
1276
- // -- snip, extract --
1277
- const loggedIn = await loginOrRefreshIfRequired();
1278
- if (!loggedIn) {
1279
- // didn't login, let's just quit
1280
- console.log("Did not login, quitting...");
1281
- return;
1282
- }
1283
-
1284
- if (!config.account_id) {
1285
- config.account_id = await getAccountId();
1286
- if (!config.account_id) {
1287
- throw new Error("No account id found, quitting...");
1288
- }
1289
- }
1290
- // -- snip, end --
1291
- }
1292
-
1293
- if (args.local) {
1294
- return;
1295
- }
1296
-
1297
- console.log(
1298
- JSON.stringify(
1299
- await fetchResult(
1300
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1301
- ),
1302
- null,
1303
- " "
1304
- )
1305
- );
1306
- }
1307
- );
1308
- }
1309
- );
1310
-
1311
- // kv
1312
- // :namespace
1313
- wrangler.command(
1314
- "kv:namespace",
1315
- "🗂️ Interact with your Workers KV Namespaces",
1316
- (namespaceYargs) => {
1317
- return namespaceYargs
1318
- .command(
1319
- "create <namespace>",
1320
- "Create a new namespace",
1321
- (yargs) => {
1322
- return yargs
1323
- .positional("namespace", {
1324
- describe: "The name of the new namespace",
1325
- type: "string",
1326
- demandOption: true,
1327
- })
1328
- .option("env", {
1329
- type: "string",
1330
- describe: "Perform on a specific environment",
1331
- })
1332
- .option("preview", {
1333
- type: "boolean",
1334
- describe: "Interact with a preview namespace",
1335
- });
1336
- },
1337
- async (args) => {
1338
- if (args._.length > 2) {
1339
- const extraArgs = args._.slice(2).join(" ");
1340
- throw new CommandLineArgsError(
1341
- `Unexpected additional positional arguments "${extraArgs}".`
1342
- );
1343
- }
1344
-
1345
- if (!isValidNamespaceBinding(args.namespace)) {
1346
- throw new CommandLineArgsError(
1347
- `The namespace binding name "${args.namespace}" is invalid. It can only have alphanumeric and _ characters, and cannot begin with a number.`
1348
- );
1349
- }
1350
-
1351
- const config = args.config as Config;
1352
- if (!config.name) {
1353
- console.warn(
1354
- "No configured name present, using `worker` as a prefix for the title"
1355
- );
1356
- }
1357
-
1358
- const name = config.name || "worker";
1359
- const environment = args.env ? `-${args.env}` : "";
1360
- const preview = args.preview ? "_preview" : "";
1361
- const title = `${name}${environment}-${args.namespace}${preview}`;
1362
-
1363
- if (args.local) {
1364
- const { Miniflare } = await import("miniflare");
1365
- const mf = new Miniflare({
1366
- kvPersist: (args.kvPersist as string) || true,
1367
- // TODO: these options shouldn't be required
1368
- script: ` `, // has to be a string with at least one char
1369
- });
1370
- await mf.getKVNamespace(title); // this should "create" the namespace
1371
- console.log(`✨ Success! Created KV namespace ${title}`);
1372
- } else {
1373
- // -- snip, extract --
1374
- const loggedIn = await loginOrRefreshIfRequired();
1375
- if (!loggedIn) {
1376
- // didn't login, let's just quit
1377
- console.log("Did not login, quitting...");
1378
- return;
1379
- }
1380
-
1381
- if (!config.account_id) {
1382
- config.account_id = await getAccountId();
1383
- if (!config.account_id) {
1384
- throw new Error("No account id found, quitting...");
1385
- }
1386
- }
1387
- // -- snip, end --
1388
-
1389
- // TODO: generate a binding name stripping non alphanumeric chars
1390
-
1391
- console.log(`🌀 Creating namespace with title "${title}"`);
1392
- const namespaceId = await createNamespace(
1393
- config.account_id,
1394
- title
1395
- );
1396
-
1397
- console.log("✨ Success!");
1398
- const envString = args.env ? ` under [env.${args.env}]` : "";
1399
- const previewString = args.preview ? "preview_" : "";
1400
- console.log(
1401
- `Add the following to your configuration file in your kv_namespaces array${envString}:`
1402
- );
1403
- console.log(
1404
- `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1405
- );
1406
-
1407
- // TODO: automatically write this block to the wrangler.toml config file??
1408
- }
1409
- }
1410
- )
1411
- .command(
1412
- "list",
1413
- "Outputs a list of all KV namespaces associated with your account id.",
1414
- {},
1415
- async (args) => {
1416
- const config = args.config as Config;
1417
-
1418
- if (args.local) {
1419
- throw new NotImplementedError(
1420
- `local mode is not yet supported for this command`
1421
- );
1422
- } else {
1423
- // -- snip, extract --
1424
- const loggedIn = await loginOrRefreshIfRequired();
1425
- if (!loggedIn) {
1426
- // didn't login, let's just quit
1427
- console.log("Did not login, quitting...");
1428
- return;
1429
- }
1430
-
1431
- if (!config.account_id) {
1432
- config.account_id = await getAccountId();
1433
- if (!config.account_id) {
1434
- throw new Error("No account id found, quitting...");
1435
- }
1436
- }
1437
- // -- snip, end --
1438
-
1439
- // TODO: we should show bindings if they exist for given ids
1440
-
1441
- console.log(
1442
- JSON.stringify(
1443
- await listNamespaces(config.account_id),
1444
- null,
1445
- " "
1446
- )
1447
- );
1448
- }
1449
- }
1450
- )
1451
- .command(
1452
- "delete",
1453
- "Deletes a given namespace.",
1454
- (yargs) => {
1455
- return yargs
1456
- .option("binding", {
1457
- type: "string",
1458
- describe: "The name of the namespace to delete",
1459
- })
1460
- .option("namespace-id", {
1461
- type: "string",
1462
- describe: "The id of the namespace to delete",
1463
- })
1464
- .check(demandOneOfOption("binding", "namespace-id"))
1465
- .option("env", {
1466
- type: "string",
1467
- describe: "Perform on a specific environment",
1468
- })
1469
- .option("preview", {
1470
- type: "boolean",
1471
- describe: "Interact with a preview namespace",
1472
- });
1473
- },
1474
- async (args) => {
1475
- const config = args.config as Config;
1476
-
1477
- if (args.local) {
1478
- throw new NotImplementedError(
1479
- `local mode is not yet supported for this command`
1480
- );
1481
- } else {
1482
- let id;
1483
- try {
1484
- id = getNamespaceId(args);
1485
- } catch (e) {
1486
- throw new CommandLineArgsError(
1487
- "Not able to delete namespace.\n" + e.message
1488
- );
1489
- }
1490
-
1491
- // -- snip, extract --
1492
- const loggedIn = await loginOrRefreshIfRequired();
1493
- if (!loggedIn) {
1494
- // didn't login, let's just quit
1495
- console.log("Did not login, quitting...");
1496
- return;
1497
- }
1498
-
1499
- if (!config.account_id) {
1500
- config.account_id = await getAccountId();
1501
- if (!config.account_id) {
1502
- throw new Error("No account id found, quitting...");
1503
- }
1504
- }
1505
- // -- snip, end --
1506
-
1507
- await fetchResult<{ id: string }>(
1508
- `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1509
- { method: "DELETE" }
1510
- );
1511
-
1512
- // TODO: recommend they remove it from wrangler.toml
1513
-
1514
- // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1515
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1516
- // n
1517
- // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1518
- // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1519
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1520
- // y
1521
- // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1522
- // ✨ Success
1523
- // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1524
- // ➜ test-mf
1525
-
1526
- // TODO: do it automatically
1527
-
1528
- // TODO: delete the preview namespace as well?
1529
- }
1530
- }
1531
- );
1532
- }
1533
- );
1534
-
1535
- // :key
1536
- wrangler.command(
1537
- "kv:key",
1538
- "🔑 Individually manage Workers KV key-value pairs",
1539
- (kvKeyYargs) => {
1540
- return kvKeyYargs
1541
- .command(
1542
- "put <key> [value]",
1543
- "Writes a single key/value pair to the given namespace.",
1544
- (yargs) => {
1545
- return yargs
1546
- .positional("key", {
1547
- type: "string",
1548
- describe: "The key to write to.",
1549
- demandOption: true,
1550
- })
1551
- .positional("value", {
1552
- type: "string",
1553
- describe: "The value to write.",
1554
- })
1555
- .option("binding", {
1556
- type: "string",
1557
- describe: "The binding of the namespace to write to.",
1558
- })
1559
- .option("namespace-id", {
1560
- type: "string",
1561
- describe: "The id of the namespace to write to.",
1562
- })
1563
- .check(demandOneOfOption("binding", "namespace-id"))
1564
- .option("env", {
1565
- type: "string",
1566
- describe: "Perform on a specific environment",
1567
- })
1568
- .option("preview", {
1569
- type: "boolean",
1570
- describe: "Interact with a preview namespace",
1571
- })
1572
- .option("ttl", {
1573
- type: "number",
1574
- describe: "Time for which the entries should be visible.",
1575
- })
1576
- .option("expiration", {
1577
- type: "number",
1578
- describe:
1579
- "Time since the UNIX epoch after which the entry expires",
1580
- })
1581
- .option("path", {
1582
- type: "string",
1583
- describe: "Read value from the file at a given path.",
1584
- })
1585
- .check(demandOneOfOption("value", "path"));
1586
- },
1587
- async ({ key, ttl, expiration, ...args }) => {
1588
- const namespaceId = getNamespaceId(args);
1589
- // One of `args.path` and `args.value` must be defined
1590
- const value = args.path
1591
- ? await readFile(args.path, "utf-8")
1592
- : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1593
- args.value!;
1594
- const config = args.config as Config;
1595
-
1596
- if (args.path) {
1597
- console.log(
1598
- `writing the contents of ${args.path} to the key "${key}" on namespace ${namespaceId}`
1599
- );
1600
- } else {
1601
- console.log(
1602
- `writing the value "${value}" to key "${key}" on namespace ${namespaceId}`
1603
- );
1604
- }
1605
-
1606
- if (args.local) {
1607
- const { Miniflare } = await import("miniflare");
1608
- const mf = new Miniflare({
1609
- kvPersist: (args.kvPersist as string) || true,
1610
- // TODO: these options shouldn't be required
1611
- script: ` `, // has to be a string with at least one char
1612
- });
1613
- const ns = await mf.getKVNamespace(namespaceId);
1614
- await ns.put(key, value, { expiration, expirationTtl: ttl });
1615
- } else {
1616
- // -- snip, extract --
1617
- const loggedIn = await loginOrRefreshIfRequired();
1618
- if (!loggedIn) {
1619
- // didn't login, let's just quit
1620
- console.log("Did not login, quitting...");
1621
- return;
1622
- }
1623
-
1624
- if (!config.account_id) {
1625
- config.account_id = await getAccountId();
1626
- if (!config.account_id) {
1627
- throw new Error("No account id found, quitting...");
1628
- }
1629
- }
1630
- // -- snip, end --
1631
-
1632
- await putKeyValue(config.account_id, namespaceId, key, value, {
1633
- expiration,
1634
- expiration_ttl: ttl,
1635
- });
1636
- }
1637
- }
1638
- )
1639
- .command(
1640
- "list",
1641
- "Outputs a list of all keys in a given namespace.",
1642
- (yargs) => {
1643
- return yargs
1644
- .option("binding", {
1645
- type: "string",
1646
- describe: "The name of the namespace to list",
1647
- })
1648
- .option("namespace-id", {
1649
- type: "string",
1650
- describe: "The id of the namespace to list",
1651
- })
1652
- .check(demandOneOfOption("binding", "namespace-id"))
1653
- .option("env", {
1654
- type: "string",
1655
- describe: "Perform on a specific environment",
1656
- })
1657
- .option("preview", {
1658
- type: "boolean",
1659
- // In the case of listing keys we will default to non-preview mode
1660
- default: false,
1661
- describe: "Interact with a preview namespace",
1662
- })
1663
- .option("prefix", {
1664
- type: "string",
1665
- describe: "A prefix to filter listed keys",
1666
- });
1667
- },
1668
- async ({ prefix, ...args }) => {
1669
- // TODO: support for limit+cursor (pagination)
1670
-
1671
- const namespaceId = getNamespaceId(args);
1672
- const config = args.config as Config;
1673
-
1674
- if (args.local) {
1675
- const { Miniflare } = await import("miniflare");
1676
- const mf = new Miniflare({
1677
- kvPersist: (args.kvPersist as string) || true,
1678
- // TODO: these options shouldn't be required
1679
- script: ` `, // has to be a string with at least one char
1680
- });
1681
- const ns = await mf.getKVNamespace(namespaceId);
1682
- const listResponse = await ns.list({ prefix });
1683
- console.log(JSON.stringify(listResponse.keys, null, " ")); // TODO: paginate, collate
1684
- } else {
1685
- // -- snip, extract --
1686
- const loggedIn = await loginOrRefreshIfRequired();
1687
- if (!loggedIn) {
1688
- // didn't login, let's just quit
1689
- console.log("Did not login, quitting...");
1690
- return;
1691
- }
1692
-
1693
- if (!config.account_id) {
1694
- config.account_id = await getAccountId();
1695
- if (!config.account_id) {
1696
- throw new Error("No account id found, quitting...");
1697
- }
1698
- }
1699
- // -- snip, end --
1700
-
1701
- const results = await listNamespaceKeys(
1702
- config.account_id,
1703
- namespaceId,
1704
- prefix
1705
- );
1706
- console.log(JSON.stringify(results, undefined, 2));
1707
- }
1708
- }
1709
- )
1710
- .command(
1711
- "get <key>",
1712
- "Reads a single value by key from the given namespace.",
1713
- (yargs) => {
1714
- return yargs
1715
- .positional("key", {
1716
- describe: "The key value to get.",
1717
- type: "string",
1718
- demandOption: true,
1719
- })
1720
- .option("binding", {
1721
- type: "string",
1722
- describe: "The name of the namespace to get from",
1723
- })
1724
- .option("namespace-id", {
1725
- type: "string",
1726
- describe: "The id of the namespace to get from",
1727
- })
1728
- .check(demandOneOfOption("binding", "namespace-id"))
1729
- .option("env", {
1730
- type: "string",
1731
- describe: "Perform on a specific environment",
1732
- })
1733
- .option("preview", {
1734
- type: "boolean",
1735
- describe: "Interact with a preview namespace",
1736
- })
1737
- .option("preview", {
1738
- type: "boolean",
1739
- // In the case of getting key values we will default to non-preview mode
1740
- default: false,
1741
- describe: "Interact with a preview namespace",
1742
- });
1743
- },
1744
- async ({ key, ...args }) => {
1745
- const namespaceId = getNamespaceId(args);
1746
- const config = args.config as Config;
1747
-
1748
- if (args.local) {
1749
- const { Miniflare } = await import("miniflare");
1750
- const mf = new Miniflare({
1751
- kvPersist: (args.kvPersist as string) || true,
1752
- // TODO: these options shouldn't be required
1753
- script: ` `, // has to be a string with at least one char
1754
- });
1755
- const ns = await mf.getKVNamespace(namespaceId);
1756
- console.log(await ns.get(key));
1757
- return;
1758
- }
1759
-
1760
- if (!args.local) {
1761
- // -- snip, extract --
1762
- const loggedIn = await loginOrRefreshIfRequired();
1763
- if (!loggedIn) {
1764
- // didn't login, let's just quit
1765
- console.log("Did not login, quitting...");
1766
- return;
1767
- }
1768
-
1769
- if (!config.account_id) {
1770
- config.account_id = await getAccountId();
1771
- if (!config.account_id) {
1772
- throw new Error("No account id found, quitting...");
1773
- }
1774
- }
1775
- // -- snip, end --
1776
- }
1777
-
1778
- console.log(
1779
- await fetchRaw(
1780
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`
1781
- )
1782
- );
1783
- }
1784
- )
1785
- .command(
1786
- "delete <key>",
1787
- "Removes a single key value pair from the given namespace.",
1788
- (yargs) => {
1789
- return yargs
1790
- .positional("key", {
1791
- describe: "The key value to delete",
1792
- type: "string",
1793
- demandOption: true,
1794
- })
1795
- .option("binding", {
1796
- type: "string",
1797
- describe: "The name of the namespace to delete from",
1798
- })
1799
- .option("namespace-id", {
1800
- type: "string",
1801
- describe: "The id of the namespace to delete from",
1802
- })
1803
- .check(demandOneOfOption("binding", "namespace-id"))
1804
- .option("env", {
1805
- type: "string",
1806
- describe: "Perform on a specific environment",
1807
- })
1808
- .option("preview", {
1809
- type: "boolean",
1810
- describe: "Interact with a preview namespace",
1811
- });
1812
- },
1813
- async ({ key, ...args }) => {
1814
- const namespaceId = getNamespaceId(args);
1815
-
1816
- console.log(
1817
- `deleting the key "${key}" on namespace ${namespaceId}`
1818
- );
1819
-
1820
- if (args.local) {
1821
- const { Miniflare } = await import("miniflare");
1822
- const mf = new Miniflare({
1823
- kvPersist: (args.kvPersist as string) || true,
1824
- // TODO: these options shouldn't be required
1825
- script: ` `, // has to be a string with at least one char
1826
- });
1827
- const ns = await mf.getKVNamespace(namespaceId);
1828
- console.log(await ns.delete(key));
1829
- return;
1830
- }
1831
-
1832
- const config = args.config as Config;
1833
-
1834
- if (!args.local) {
1835
- // -- snip, extract --
1836
- const loggedIn = await loginOrRefreshIfRequired();
1837
- if (!loggedIn) {
1838
- // didn't login, let's just quit
1839
- console.log("Did not login, quitting...");
1840
- return;
1841
- }
1842
-
1843
- if (!config.account_id) {
1844
- config.account_id = await getAccountId();
1845
- if (!config.account_id) {
1846
- throw new Error("No account id found, quitting...");
1847
- }
1848
- }
1849
- // -- snip, end --
1850
- }
1851
-
1852
- await fetchResult(
1853
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`,
1854
- { method: "DELETE" }
1855
- );
1856
- }
1857
- );
1858
- }
1859
- );
1860
-
1861
- // :bulk
1862
- wrangler.command(
1863
- "kv:bulk",
1864
- "💪 Interact with multiple Workers KV key-value pairs at once",
1865
- (kvBulkYargs) => {
1866
- return kvBulkYargs
1867
- .command(
1868
- "put <filename>",
1869
- "Upload multiple key-value pairs to a namespace",
1870
- (yargs) => {
1871
- return yargs
1872
- .positional("filename", {
1873
- describe: `The JSON file of key-value pairs to upload, in form [{"key":..., "value":...}"...]`,
1874
- type: "string",
1875
- demandOption: true,
1876
- })
1877
- .option("binding", {
1878
- type: "string",
1879
- describe: "The name of the namespace to put to",
1880
- })
1881
- .option("namespace-id", {
1882
- type: "string",
1883
- describe: "The id of the namespace to put to",
1884
- })
1885
- .check(demandOneOfOption("binding", "namespace-id"))
1886
- .option("env", {
1887
- type: "string",
1888
- describe: "Perform on a specific environment",
1889
- })
1890
- .option("preview", {
1891
- type: "boolean",
1892
- describe: "Interact with a preview namespace",
1893
- });
1894
- },
1895
- async ({ filename, ...args }) => {
1896
- // The simplest implementation I could think of.
1897
- // This could be made more efficient with a streaming parser/uploader
1898
- // but we'll do that in the future if needed.
1899
-
1900
- const namespaceId = getNamespaceId(args);
1901
- const config = args.config as Config;
1902
- const content = await readFile(filename, "utf-8");
1903
- let parsedContent;
1904
- try {
1905
- parsedContent = JSON.parse(content);
1906
- } catch (err) {
1907
- throw new Error(
1908
- `Could not parse json from ${filename}.\n${err.message ?? err}`
1909
- );
1910
- }
1911
-
1912
- if (args.local) {
1913
- const { Miniflare } = await import("miniflare");
1914
- const mf = new Miniflare({
1915
- kvPersist: (args.kvPersist as string) || true,
1916
- // TODO: these options shouldn't be required
1917
- script: ` `, // has to be a string with at least one char
1918
- });
1919
- const ns = await mf.getKVNamespace(namespaceId);
1920
- for (const {
1921
- key,
1922
- value,
1923
- expiration,
1924
- expiration_ttl,
1925
- } of parsedContent) {
1926
- await ns.put(key, value, {
1927
- expiration,
1928
- expirationTtl: expiration_ttl,
1929
- });
1930
- }
1931
- } else {
1932
- // -- snip, extract --
1933
- const loggedIn = await loginOrRefreshIfRequired();
1934
- if (!loggedIn) {
1935
- // didn't login, let's just quit
1936
- console.log("Did not login, quitting...");
1937
- return;
1938
- }
1939
-
1940
- if (!config.account_id) {
1941
- config.account_id = await getAccountId();
1942
- if (!config.account_id) {
1943
- throw new Error("No account id found, quitting...");
1944
- }
1945
- }
1946
- // -- snip, end --
1947
-
1948
- console.log(
1949
- await putBulkKeyValue(config.account_id, namespaceId, content)
1950
- );
1951
- }
1952
- }
1953
- )
1954
- .command(
1955
- "delete <filename>",
1956
- "Delete multiple key-value pairs from a namespace",
1957
- (yargs) => {
1958
- return yargs
1959
- .positional("filename", {
1960
- describe: `The JSON file of keys to delete, in the form ["key1", "key2", ...]`,
1961
- type: "string",
1962
- demandOption: true,
1963
- })
1964
- .option("binding", {
1965
- type: "string",
1966
- describe: "The name of the namespace to delete from",
1967
- })
1968
- .option("namespace-id", {
1969
- type: "string",
1970
- describe: "The id of the namespace to delete from",
1971
- })
1972
- .check(demandOneOfOption("binding", "namespace-id"))
1973
- .option("env", {
1974
- type: "string",
1975
- describe: "Perform on a specific environment",
1976
- })
1977
- .option("preview", {
1978
- type: "boolean",
1979
- describe: "Interact with a preview namespace",
1980
- });
1981
- },
1982
- async ({ filename, ...args }) => {
1983
- const namespaceId = getNamespaceId(args);
1984
- const config = args.config as Config;
1985
- const content = await readFile(filename, "utf-8");
1986
- let parsedContent;
1987
- try {
1988
- parsedContent = JSON.parse(content);
1989
- } catch (err) {
1990
- throw new Error(
1991
- `Could not parse json from ${filename}.\n${err.message ?? err}`
1992
- );
1993
- }
1994
-
1995
- if (args.local) {
1996
- const { Miniflare } = await import("miniflare");
1997
- const mf = new Miniflare({
1998
- kvPersist: (args.kvPersist as string) || true,
1999
- // TODO: these options shouldn't be required
2000
- script: ` `, // has to be a string with at least one char
2001
- });
2002
- const ns = await mf.getKVNamespace(namespaceId);
2003
- for (const key of parsedContent) {
2004
- await ns.delete(key);
2005
- }
2006
- } else {
2007
- // -- snip, extract --
2008
- const loggedIn = await loginOrRefreshIfRequired();
2009
- if (!loggedIn) {
2010
- // didn't login, let's just quit
2011
- console.log("Did not login, quitting...");
2012
- return;
2013
- }
2014
-
2015
- if (!config.account_id) {
2016
- config.account_id = await getAccountId();
2017
- if (!config.account_id) {
2018
- throw new Error("No account id found, quitting...");
2019
- }
2020
- }
2021
- // -- snip, end --
2022
-
2023
- console.log(
2024
- await deleteBulkKeyValue(
2025
- config.account_id,
2026
- namespaceId,
2027
- content
2028
- )
2029
- );
2030
- }
2031
- }
2032
- );
2033
- }
2034
- );
2035
-
2036
- wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
2037
-
2038
- wrangler
2039
- .option("config", {
2040
- alias: "c",
2041
- describe: "Path to .toml configuration file",
2042
- type: "string",
2043
- async coerce(arg) {
2044
- return await readConfig(arg);
2045
- },
2046
- })
2047
- .option("local", {
2048
- alias: "l",
2049
- describe: "Run on my machine",
2050
- type: "boolean",
2051
- default: false, // I bet this will a point of contention. We'll revisit it.
2052
- });
2053
-
2054
- wrangler.group(["config", "help", "version"], "Flags:");
2055
- wrangler.help().alias("h", "help");
2056
- wrangler.version(wranglerVersion).alias("v", "version");
2057
- wrangler.exitProcess(false);
2058
-
2059
- await initialiseUserConfig();
2060
-
2061
- try {
2062
- await wrangler.parse();
2063
- } catch (e) {
2064
- if (e instanceof CommandLineArgsError) {
2065
- wrangler.showHelp("error");
2066
- console.error(""); // Just adds a bit of space
2067
- console.error(e.message);
2068
- } else {
2069
- console.error(e.message);
2070
- console.error(""); // Just adds a bit of space
2071
- console.error(
2072
- `${fgGreenColor}%s${resetColor}`,
2073
- "If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
2074
- );
2075
- }
2076
- throw e;
2077
- }
2078
- }