wrangler 0.0.0-e151223 → 0.0.0-e29a241

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 (92) hide show
  1. package/miniflare-dist/index.mjs +79 -0
  2. package/miniflare-dist/index.mjs.map +7 -0
  3. package/package.json +29 -13
  4. package/pages/functions/filepath-routing.test.ts +212 -36
  5. package/pages/functions/filepath-routing.ts +69 -119
  6. package/pages/functions/routes.ts +8 -17
  7. package/pages/functions/template-worker.ts +3 -9
  8. package/src/__tests__/configuration.test.ts +2754 -0
  9. package/src/__tests__/dev.test.tsx +488 -63
  10. package/src/__tests__/dev2.test.tsx +154 -0
  11. package/src/__tests__/guess-worker-format.test.ts +58 -0
  12. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  13. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  14. package/src/__tests__/helpers/mock-account-id.ts +26 -8
  15. package/src/__tests__/helpers/mock-bin.ts +36 -0
  16. package/src/__tests__/helpers/mock-cfetch.ts +44 -10
  17. package/src/__tests__/helpers/mock-console.ts +21 -7
  18. package/src/__tests__/helpers/mock-kv.ts +9 -16
  19. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  20. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  21. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  22. package/src/__tests__/helpers/run-wrangler.ts +5 -3
  23. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  24. package/src/__tests__/https-options.test.ts +127 -0
  25. package/src/__tests__/index.test.ts +617 -32
  26. package/src/__tests__/jest.setup.ts +41 -6
  27. package/src/__tests__/kv.test.ts +455 -222
  28. package/src/__tests__/logout.test.ts +15 -13
  29. package/src/__tests__/package-manager.test.ts +206 -0
  30. package/src/__tests__/pages.test.ts +44 -0
  31. package/src/__tests__/parse.test.ts +322 -0
  32. package/src/__tests__/publish.test.ts +1919 -437
  33. package/src/__tests__/r2.test.ts +198 -0
  34. package/src/__tests__/route.test.ts +45 -0
  35. package/src/__tests__/secret.test.ts +301 -86
  36. package/src/__tests__/sentry.test.ts +146 -0
  37. package/src/__tests__/tail.test.ts +564 -0
  38. package/src/__tests__/whoami.test.tsx +21 -7
  39. package/src/bundle.ts +142 -0
  40. package/src/cfetch/index.ts +9 -16
  41. package/src/cfetch/internal.ts +89 -22
  42. package/src/cli.ts +10 -0
  43. package/src/config/README.md +107 -0
  44. package/src/config/config.ts +274 -0
  45. package/src/config/diagnostics.ts +80 -0
  46. package/src/config/environment.ts +310 -0
  47. package/src/config/index.ts +56 -0
  48. package/src/config/validation-helpers.ts +486 -0
  49. package/src/config/validation.ts +1096 -0
  50. package/src/{api/preview.ts → create-worker-preview.ts} +59 -21
  51. package/src/create-worker-upload-form.ts +217 -0
  52. package/src/dev/dev.tsx +364 -0
  53. package/src/dev/local.tsx +226 -0
  54. package/src/dev/remote.tsx +203 -0
  55. package/src/dev/use-esbuild.ts +100 -0
  56. package/src/entry.ts +208 -0
  57. package/src/environment-variables.ts +33 -0
  58. package/src/https-options.ts +122 -0
  59. package/src/index.tsx +1079 -766
  60. package/src/inspect.ts +25 -4
  61. package/src/{kv.tsx → kv.ts} +104 -61
  62. package/src/miniflare-cli/README.md +30 -0
  63. package/src/miniflare-cli/enum-keys.ts +17 -0
  64. package/src/miniflare-cli/index.ts +37 -0
  65. package/src/module-collection.ts +226 -31
  66. package/src/open-in-browser.ts +25 -0
  67. package/src/package-manager.ts +126 -0
  68. package/src/pages.tsx +11 -11
  69. package/src/parse.ts +183 -0
  70. package/src/proxy.ts +115 -45
  71. package/src/publish.ts +265 -328
  72. package/src/r2.ts +50 -0
  73. package/src/reporting.ts +131 -0
  74. package/src/selfsigned.d.ts +29 -0
  75. package/src/sites.tsx +83 -43
  76. package/src/tail/filters.ts +279 -0
  77. package/src/tail/index.ts +299 -0
  78. package/src/tail/printing.ts +62 -0
  79. package/src/user.tsx +223 -124
  80. package/src/{api/worker.ts → worker.ts} +44 -36
  81. package/templates/new-worker.js +15 -0
  82. package/templates/new-worker.ts +15 -0
  83. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  84. package/templates/tsconfig.json +105 -0
  85. package/wrangler-dist/cli.js +60837 -43876
  86. package/wrangler-dist/cli.js.map +3 -3
  87. package/src/__tests__/helpers/mock-user.ts +0 -27
  88. package/src/api/form_data.ts +0 -134
  89. package/src/config.ts +0 -641
  90. package/src/dev.tsx +0 -834
  91. package/src/npm-installer.ts +0 -20
  92. package/src/tail.tsx +0 -73
package/src/index.tsx CHANGED
@@ -1,19 +1,23 @@
1
1
  import * as fs from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
3
- import path from "node:path/posix";
2
+ import { writeFile, mkdir } from "node:fs/promises";
3
+ import path from "node:path";
4
4
  import { setTimeout } from "node:timers/promises";
5
5
  import TOML from "@iarna/toml";
6
+ import chalk from "chalk";
6
7
  import { findUp } from "find-up";
8
+ import getPort from "get-port";
7
9
  import { render } from "ink";
8
10
  import React from "react";
9
11
  import onExit from "signal-exit";
12
+ import supportsColor from "supports-color";
10
13
  import makeCLI from "yargs";
11
14
  import { version as wranglerVersion } from "../package.json";
12
- import { toFormData } from "./api/form_data";
13
- import { fetchResult, fetchRaw } from "./cfetch";
14
- import { normaliseAndValidateEnvironmentsConfig } from "./config";
15
- import Dev from "./dev";
15
+ import { fetchResult } from "./cfetch";
16
+ import { findWranglerToml, readConfig } from "./config";
17
+ import { createWorkerUploadForm } from "./create-worker-upload-form";
18
+ import Dev from "./dev/dev";
16
19
  import { confirm, prompt } from "./dialogs";
20
+ import { getEntry } from "./entry";
17
21
  import {
18
22
  getNamespaceId,
19
23
  listNamespaces,
@@ -23,17 +27,26 @@ import {
23
27
  deleteBulkKeyValue,
24
28
  createNamespace,
25
29
  isValidNamespaceBinding,
30
+ getKeyValue,
31
+ isKeyValue,
32
+ unexpectedKeyValueProps,
26
33
  } from "./kv";
27
- import { npm } from "./npm-installer";
34
+ import { getPackageManager } from "./package-manager";
28
35
  import { pages } from "./pages";
36
+ import { formatMessage, ParseError, parseJSON, readFileSync } from "./parse";
29
37
  import publish from "./publish";
38
+ import { createR2Bucket, deleteR2Bucket, listR2Buckets } from "./r2";
30
39
  import { getAssetPaths } from "./sites";
31
- import { createTail } from "./tail";
40
+ import {
41
+ createTail,
42
+ jsonPrintLogs,
43
+ prettyPrintLogs,
44
+ translateCLICommandToFilterMessage,
45
+ } from "./tail";
32
46
  import {
33
47
  login,
34
48
  logout,
35
49
  listScopes,
36
- initialise as initialiseUserConfig,
37
50
  loginOrRefreshIfRequired,
38
51
  getAccountId,
39
52
  validateScopeKeys,
@@ -41,39 +54,123 @@ import {
41
54
  import { whoami } from "./whoami";
42
55
 
43
56
  import type { Config } from "./config";
57
+ import type { TailCLIFilters } from "./tail";
58
+ import type { RawData } from "ws";
59
+ import type { CommandModule } from "yargs";
44
60
  import type Yargs from "yargs";
45
61
 
62
+ type ConfigPath = string | undefined;
63
+
46
64
  const resetColor = "\x1b[0m";
47
65
  const fgGreenColor = "\x1b[32m";
48
66
 
49
- async function readConfig(configPath?: string): Promise<Config> {
50
- const config: Config = {};
51
- if (!configPath) {
52
- configPath = await findUp("wrangler.toml");
53
- // TODO - terminate this early instead of going all the way to the root
54
- }
67
+ function getRules(config: Config): Config["rules"] {
68
+ const rules = config.rules ?? config.build?.upload?.rules ?? [];
55
69
 
56
- if (configPath) {
57
- const tml: string = await readFile(configPath, "utf-8");
58
- const parsed = TOML.parse(tml) as Config;
59
- Object.assign(config, parsed);
70
+ if (config.rules && config.build?.upload?.rules) {
71
+ throw new Error(
72
+ `You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
73
+ );
60
74
  }
61
75
 
62
- normaliseAndValidateEnvironmentsConfig(config);
63
-
64
- if ("experimental_services" in config) {
76
+ if (config.build?.upload?.rules) {
65
77
  console.warn(
66
- "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"
78
+ `Deprecation notice: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:
79
+
80
+ ${TOML.stringify({ rules: config.build.upload.rules })}`
67
81
  );
68
82
  }
83
+ return rules;
84
+ }
85
+
86
+ function printWranglerBanner() {
87
+ // Let's not print this in tests
88
+ if (typeof jest !== "undefined") {
89
+ return;
90
+ }
91
+ const text = ` ⛅️ wrangler ${wranglerVersion} `;
92
+
93
+ console.log(
94
+ text +
95
+ "\n" +
96
+ (supportsColor.stdout
97
+ ? chalk.hex("#FF8800")("-".repeat(text.length))
98
+ : "-".repeat(text.length))
99
+ );
100
+ }
101
+
102
+ function isLegacyEnv(args: unknown, config: Config): boolean {
103
+ return (
104
+ (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
105
+ config.legacy_env
106
+ );
107
+ }
108
+
109
+ function getScriptName(
110
+ args: { name: string | undefined; env: string | undefined },
111
+ config: Config
112
+ ): string | undefined {
113
+ const shortScriptName = args.name ?? config.name;
114
+ if (!shortScriptName) {
115
+ return;
116
+ }
117
+
118
+ return isLegacyEnv(args, config)
119
+ ? `${shortScriptName}${args.env ? `-${args.env}` : ""}`
120
+ : shortScriptName;
121
+ }
122
+
123
+ /**
124
+ * Ensure that a user is logged in, and a valid account_id is available.
125
+ */
126
+ async function requireAuth(
127
+ config: Config,
128
+ isInteractive = true
129
+ ): Promise<string> {
130
+ const loggedIn = await loginOrRefreshIfRequired(isInteractive);
131
+ if (!loggedIn) {
132
+ // didn't login, let's just quit
133
+ throw new Error("Did not login, quitting...");
134
+ }
135
+ const accountId = config.account_id || (await getAccountId(isInteractive));
136
+ if (!accountId) {
137
+ throw new Error("No account id found, quitting...");
138
+ }
69
139
 
70
- // todo: validate, add defaults
71
- // let's just do some basics for now
140
+ return accountId;
141
+ }
72
142
 
73
- // @ts-expect-error we're being sneaky here for now
74
- config.__path__ = configPath;
143
+ /**
144
+ * Get a promise to the streamed input from stdin.
145
+ *
146
+ * This function can be used to grab the incoming stream of data from, say,
147
+ * piping the output of another process into the wrangler process.
148
+ */
149
+ function readFromStdin(): Promise<string> {
150
+ return new Promise((resolve, reject) => {
151
+ const stdin = process.stdin;
152
+ const chunks: string[] = [];
153
+
154
+ // When there is data ready to be read, the `readable` event will be triggered.
155
+ // In the handler for `readable` we call `read()` over and over until all the available data has been read.
156
+ stdin.on("readable", () => {
157
+ let chunk;
158
+ while (null !== (chunk = stdin.read())) {
159
+ chunks.push(chunk);
160
+ }
161
+ });
75
162
 
76
- return config;
163
+ // When the streamed data is complete the `end` event will be triggered.
164
+ // In the handler for `end` we join the chunks together and resolve the promise.
165
+ stdin.on("end", () => {
166
+ resolve(chunks.join(""));
167
+ });
168
+
169
+ // If there is an `error` event then the handler will reject the promise.
170
+ stdin.on("error", (err) => {
171
+ reject(err);
172
+ });
173
+ });
77
174
  }
78
175
 
79
176
  // a helper to demand one of a set of options
@@ -102,8 +199,11 @@ function demandOneOfOption(...options: string[]) {
102
199
  }
103
200
 
104
201
  class CommandLineArgsError extends Error {}
105
- class DeprecationError extends Error {}
106
- class NotImplementedError extends Error {}
202
+ class DeprecationError extends Error {
203
+ constructor(message: string) {
204
+ super(`DEPRECATION WARNING:\n${message}`);
205
+ }
206
+ }
107
207
 
108
208
  export async function main(argv: string[]): Promise<void> {
109
209
  const wrangler = makeCLI(argv)
@@ -121,7 +221,15 @@ export async function main(argv: string[]): Promise<void> {
121
221
  .scriptName("wrangler")
122
222
  .wrap(null);
123
223
 
124
- // the default is to simply print the help menu
224
+ // Default help command that supports the subcommands
225
+ const subHelp: CommandModule = {
226
+ command: ["*"],
227
+ handler: async (args) => {
228
+ setImmediate(() =>
229
+ wrangler.parse([...args._.map((a) => `${a}`), "--help"])
230
+ );
231
+ },
232
+ };
125
233
  wrangler.command(
126
234
  ["*"],
127
235
  false,
@@ -162,7 +270,7 @@ export async function main(argv: string[]): Promise<void> {
162
270
  () => {
163
271
  // "👯 [DEPRECATED]. Scaffold a Cloudflare Workers project from a public GitHub repository.",
164
272
  throw new DeprecationError(
165
- "`wrangler generate` has been deprecated, please refer to TODO://some/path for alternatives"
273
+ "`wrangler generate` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#generate for alternatives"
166
274
  );
167
275
  }
168
276
  );
@@ -172,12 +280,19 @@ export async function main(argv: string[]): Promise<void> {
172
280
  "init [name]",
173
281
  "📥 Create a wrangler.toml configuration file",
174
282
  (yargs) => {
175
- return yargs.positional("name", {
176
- describe: "The name of your worker.",
177
- type: "string",
178
- });
283
+ return yargs
284
+ .positional("name", {
285
+ describe: "The name of your worker",
286
+ type: "string",
287
+ })
288
+ .option("yes", {
289
+ describe: 'Answer "yes" to any prompts for new projects',
290
+ type: "boolean",
291
+ alias: "y",
292
+ });
179
293
  },
180
294
  async (args) => {
295
+ printWranglerBanner();
181
296
  if ("type" in args) {
182
297
  let message = "The --type option is no longer supported.";
183
298
  if (args.type === "webpack") {
@@ -188,9 +303,24 @@ export async function main(argv: string[]): Promise<void> {
188
303
  throw new CommandLineArgsError(message);
189
304
  }
190
305
 
191
- const destination = path.join(process.cwd(), "wrangler.toml");
192
- if (fs.existsSync(destination)) {
193
- console.warn(`${destination} file already exists!`);
306
+ // TODO: make sure args.name is a valid identifier for a worker name
307
+
308
+ const creationDirectory = path.join(process.cwd(), args.name ?? "");
309
+
310
+ const packageManager = await getPackageManager(creationDirectory);
311
+
312
+ // TODO: ask which directory to make the worker in (defaults to args.name)
313
+ // TODO: if args.name isn't provided, ask what to name the worker
314
+
315
+ const wranglerTomlDestination = path.join(
316
+ creationDirectory,
317
+ "./wrangler.toml"
318
+ );
319
+ let justCreatedWranglerToml = false;
320
+ const workerName =
321
+ args.name || path.basename(path.resolve(process.cwd()));
322
+ if (fs.existsSync(wranglerTomlDestination)) {
323
+ console.warn(`${wranglerTomlDestination} file already exists!`);
194
324
  const shouldContinue = await confirm(
195
325
  "Do you want to continue initializing this project?"
196
326
  );
@@ -198,33 +328,42 @@ export async function main(argv: string[]): Promise<void> {
198
328
  return;
199
329
  }
200
330
  } else {
331
+ await mkdir(creationDirectory, { recursive: true });
201
332
  const compatibilityDate = new Date().toISOString().substring(0, 10);
202
333
  try {
203
334
  await writeFile(
204
- destination,
205
- `compatibility_date = "${compatibilityDate}"` + "\n"
335
+ wranglerTomlDestination,
336
+ TOML.stringify({
337
+ name: workerName,
338
+ compatibility_date: compatibilityDate,
339
+ }) + "\n"
206
340
  );
207
341
  console.log(`✨ Successfully created wrangler.toml`);
208
- // TODO: suggest next steps?
342
+ justCreatedWranglerToml = true;
209
343
  } catch (err) {
210
344
  throw new Error(
211
- `Failed to create wrangler.toml.\n${err.message ?? err}`
345
+ `Failed to create wrangler.toml.\n${(err as Error).message ?? err}`
212
346
  );
213
347
  }
214
348
  }
215
349
 
216
350
  let pathToPackageJson = await findUp("package.json");
351
+ let shouldCreatePackageJson = false;
352
+ const yesFlag = args.yes ?? false;
217
353
  if (!pathToPackageJson) {
218
354
  // If no package.json exists, ask to create one
219
- const shouldCreatePackageJson = await confirm(
220
- "No package.json found. Would you like to create one?"
221
- );
355
+ shouldCreatePackageJson =
356
+ yesFlag ||
357
+ (await confirm(
358
+ "No package.json found. Would you like to create one?"
359
+ ));
360
+
222
361
  if (shouldCreatePackageJson) {
223
362
  await writeFile(
224
- path.join(process.cwd(), "package.json"),
363
+ path.join(creationDirectory, "./package.json"),
225
364
  JSON.stringify(
226
365
  {
227
- name: "worker",
366
+ name: workerName,
228
367
  version: "0.0.0",
229
368
  devDependencies: {
230
369
  wrangler: wranglerVersion,
@@ -235,17 +374,18 @@ export async function main(argv: string[]): Promise<void> {
235
374
  " "
236
375
  ) + "\n"
237
376
  );
238
- await npm.install();
377
+ await packageManager.install();
239
378
  console.log(`✨ Created package.json`);
240
- pathToPackageJson = path.join(process.cwd(), "package.json");
379
+ pathToPackageJson = path.join(creationDirectory, "package.json");
241
380
  } else {
242
381
  return;
243
382
  }
244
383
  } else {
245
384
  // If package.json exists and wrangler isn't installed,
246
385
  // then ask to add wrangler to devDependencies
247
- const packageJson = JSON.parse(
248
- await readFile(pathToPackageJson, "utf-8")
386
+ const packageJson = parseJSON(
387
+ readFileSync(pathToPackageJson),
388
+ pathToPackageJson
249
389
  );
250
390
  if (
251
391
  !(
@@ -253,54 +393,46 @@ export async function main(argv: string[]): Promise<void> {
253
393
  packageJson.dependencies?.wrangler
254
394
  )
255
395
  ) {
256
- const shouldInstall = await confirm(
257
- "Would you like to install wrangler into your package.json?"
258
- );
396
+ const shouldInstall =
397
+ yesFlag ||
398
+ (await confirm(
399
+ "Would you like to install wrangler into your package.json?"
400
+ ));
259
401
  if (shouldInstall) {
260
- await npm.addDevDeps(`wrangler@${wranglerVersion}`);
402
+ await packageManager.addDevDeps(`wrangler@${wranglerVersion}`);
261
403
  console.log(`✨ Installed wrangler`);
262
404
  }
263
405
  }
264
406
  }
265
407
 
408
+ let isTypescriptProject = false;
266
409
  let pathToTSConfig = await findUp("tsconfig.json");
267
410
  if (!pathToTSConfig) {
268
411
  // If there's no tsconfig, offer to create one
269
412
  // and install @cloudflare/workers-types
270
- if (await confirm("Would you like to use typescript?")) {
413
+ if (yesFlag || (await confirm("Would you like to use TypeScript?"))) {
414
+ isTypescriptProject = true;
271
415
  await writeFile(
272
- path.join(process.cwd(), "tsconfig.json"),
273
- JSON.stringify(
274
- {
275
- compilerOptions: {
276
- target: "es2021",
277
- module: "es2022",
278
- moduleResolution: "node",
279
- allowJs: true,
280
- allowSyntheticDefaultImports: true,
281
- isolatedModules: true,
282
- noEmit: true,
283
- lib: ["es2021"],
284
- resolveJsonModule: true,
285
- types: ["@cloudflare/workers-types"],
286
- },
287
- },
288
- null,
289
- " "
290
- ) + "\n"
416
+ path.join(creationDirectory, "./tsconfig.json"),
417
+ readFileSync(path.join(__dirname, "../templates/tsconfig.json"))
418
+ );
419
+ await packageManager.addDevDeps(
420
+ "@cloudflare/workers-types",
421
+ "typescript"
291
422
  );
292
- await npm.addDevDeps("@cloudflare/workers-types", "typescript");
293
423
 
294
424
  console.log(
295
425
  `✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies`
296
426
  );
297
- pathToTSConfig = path.join(process.cwd(), "tsconfig.json");
427
+ pathToTSConfig = path.join(creationDirectory, "tsconfig.json");
298
428
  }
299
429
  } else {
430
+ isTypescriptProject = true;
300
431
  // If there's a tsconfig, check if @cloudflare/workers-types
301
432
  // is already installed, and offer to install it if not
302
- const packageJson = JSON.parse(
303
- await readFile(pathToPackageJson, "utf-8")
433
+ const packageJson = parseJSON(
434
+ readFileSync(pathToPackageJson),
435
+ pathToPackageJson
304
436
  );
305
437
  if (
306
438
  !(
@@ -312,7 +444,7 @@ export async function main(argv: string[]): Promise<void> {
312
444
  "Would you like to install the type definitions for Workers into your package.json?"
313
445
  );
314
446
  if (shouldInstall) {
315
- await npm.addDevDeps("@cloudflare/workers-types");
447
+ await packageManager.addDevDeps("@cloudflare/workers-types");
316
448
  // We don't update the tsconfig.json because
317
449
  // it could be complicated in existing projects
318
450
  // and we don't want to break them. Instead, we simply
@@ -320,8 +452,135 @@ export async function main(argv: string[]): Promise<void> {
320
452
  console.log(
321
453
  `✨ Installed @cloudflare/workers-types.\nPlease add "@cloudflare/workers-types" to compilerOptions.types in your tsconfig.json`
322
454
  );
323
- } else {
324
- return;
455
+ }
456
+ }
457
+ }
458
+
459
+ const packageJsonContent = parseJSON(
460
+ readFileSync(pathToPackageJson),
461
+ pathToPackageJson
462
+ );
463
+ const shouldWritePackageJsonScripts =
464
+ !packageJsonContent.scripts?.start &&
465
+ !packageJsonContent.scripts?.publish &&
466
+ shouldCreatePackageJson;
467
+
468
+ async function writePackageJsonScriptsAndUpdateWranglerToml(
469
+ isWritingScripts: boolean,
470
+ isCreatingWranglerToml: boolean,
471
+ packagePath: string,
472
+ scriptPath: string
473
+ ) {
474
+ if (isCreatingWranglerToml) {
475
+ // rewrite wrangler.toml with main = "path/to/script"
476
+ const parsedWranglerToml = TOML.parse(
477
+ readFileSync(wranglerTomlDestination)
478
+ );
479
+ fs.writeFileSync(
480
+ wranglerTomlDestination,
481
+ TOML.stringify({
482
+ name: parsedWranglerToml.name,
483
+ main: scriptPath,
484
+ compatibility_date: parsedWranglerToml.compatibility_date,
485
+ })
486
+ );
487
+ }
488
+ const isNamedWorker =
489
+ isCreatingWranglerToml && path.dirname(packagePath) !== process.cwd();
490
+
491
+ if (isWritingScripts) {
492
+ await writeFile(
493
+ packagePath,
494
+ JSON.stringify(
495
+ {
496
+ ...packageJsonContent,
497
+ scripts: {
498
+ ...packageJsonContent.scripts,
499
+ start: isCreatingWranglerToml
500
+ ? `wrangler dev`
501
+ : `wrangler dev ${scriptPath}`,
502
+ publish: isCreatingWranglerToml
503
+ ? `wrangler publish`
504
+ : `wrangler publish ${scriptPath}`,
505
+ },
506
+ },
507
+ null,
508
+ 2
509
+ ) + "\n"
510
+ );
511
+ console.log(
512
+ `\nTo start developing your Worker, run \`${
513
+ isNamedWorker ? `cd ${args.name} && ` : ""
514
+ }npm start\``
515
+ );
516
+ console.log(
517
+ `To publish your Worker to the Internet, run \`npm run publish\``
518
+ );
519
+ } else {
520
+ console.log(
521
+ `\nTo start developing your Worker, run \`npx wrangler dev\`${
522
+ isCreatingWranglerToml ? "" : ` ${scriptPath}`
523
+ }`
524
+ );
525
+ console.log(
526
+ `To publish your Worker to the Internet, run \`npx wrangler publish\`${
527
+ isCreatingWranglerToml ? "" : ` ${scriptPath}`
528
+ }`
529
+ );
530
+ }
531
+ }
532
+
533
+ if (isTypescriptProject) {
534
+ if (!fs.existsSync(path.join(creationDirectory, "./src/index.ts"))) {
535
+ let shouldCreateSource = false;
536
+
537
+ shouldCreateSource =
538
+ yesFlag ||
539
+ (await confirm(
540
+ `Would you like to create a Worker at src/index.ts?`
541
+ ));
542
+
543
+ if (shouldCreateSource) {
544
+ await mkdir(path.join(creationDirectory, "./src"), {
545
+ recursive: true,
546
+ });
547
+ await writeFile(
548
+ path.join(creationDirectory, "./src/index.ts"),
549
+ readFileSync(path.join(__dirname, "../templates/new-worker.ts"))
550
+ );
551
+
552
+ console.log(`✨ Created src/index.ts`);
553
+
554
+ await writePackageJsonScriptsAndUpdateWranglerToml(
555
+ shouldWritePackageJsonScripts,
556
+ justCreatedWranglerToml,
557
+ pathToPackageJson,
558
+ "src/index.ts"
559
+ );
560
+ }
561
+ }
562
+ } else {
563
+ if (!fs.existsSync("./src/index.js")) {
564
+ const shouldCreateSource = await confirm(
565
+ `Would you like to create a Worker at src/index.js?`
566
+ );
567
+ if (shouldCreateSource) {
568
+ await mkdir(path.join(creationDirectory, "./src"), {
569
+ recursive: true,
570
+ });
571
+ await writeFile(
572
+ path.join(path.join(creationDirectory, "./src/index.js")),
573
+ readFileSync(path.join(__dirname, "../templates/new-worker.js"))
574
+ );
575
+
576
+ console.log(`✨ Created src/index.js`);
577
+
578
+ await writePackageJsonScriptsAndUpdateWranglerToml(
579
+ shouldWritePackageJsonScripts,
580
+ justCreatedWranglerToml,
581
+ pathToPackageJson,
582
+ "src/index.js"
583
+ );
325
584
  }
326
585
  }
327
586
  }
@@ -340,7 +599,7 @@ export async function main(argv: string[]): Promise<void> {
340
599
  () => {
341
600
  // "[DEPRECATED] 🦀 Build your project (if applicable)",
342
601
  throw new DeprecationError(
343
- "`wrangler build` has been deprecated, please refer to TODO://some/path for alternatives"
602
+ "`wrangler build` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#build for alternatives"
344
603
  );
345
604
  }
346
605
  );
@@ -356,10 +615,10 @@ export async function main(argv: string[]): Promise<void> {
356
615
  // I mean, this entire app does, but this too.
357
616
  return yargs
358
617
  .option("scopes-list", {
359
- describe: "list all the available OAuth scopes with descriptions.",
618
+ describe: "List all the available OAuth scopes with descriptions",
360
619
  })
361
620
  .option("scopes", {
362
- describe: "allows to choose your set of OAuth scopes.",
621
+ describe: "Pick the set of applicable OAuth scopes when logging in",
363
622
  array: true,
364
623
  type: "string",
365
624
  });
@@ -367,6 +626,7 @@ export async function main(argv: string[]): Promise<void> {
367
626
  // TODO: scopes
368
627
  },
369
628
  async (args) => {
629
+ printWranglerBanner();
370
630
  if (args["scopes-list"]) {
371
631
  listScopes();
372
632
  return;
@@ -401,6 +661,7 @@ export async function main(argv: string[]): Promise<void> {
401
661
  // "🚪 Logout from Cloudflare",
402
662
  () => {},
403
663
  async () => {
664
+ printWranglerBanner();
404
665
  await logout();
405
666
  }
406
667
  );
@@ -411,6 +672,7 @@ export async function main(argv: string[]): Promise<void> {
411
672
  "🕵️ Retrieve your user info and test your auth config",
412
673
  () => {},
413
674
  async () => {
675
+ printWranglerBanner();
414
676
  await whoami();
415
677
  }
416
678
  );
@@ -423,24 +685,23 @@ export async function main(argv: string[]): Promise<void> {
423
685
  () => {
424
686
  // "🕵️ Authenticate Wrangler with a Cloudflare API Token",
425
687
  throw new DeprecationError(
426
- "`wrangler config` has been deprecated, please refer to TODO://some/path for alternatives"
688
+ "`wrangler config` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#config for alternatives"
427
689
  );
428
690
  }
429
691
  );
430
692
 
431
693
  // dev
432
694
  wrangler.command(
433
- "dev <filename>",
695
+ "dev [script]",
434
696
  "👂 Start a local server for developing your worker",
435
697
  (yargs) => {
436
698
  return yargs
437
- .positional("filename", {
438
- describe: "entry point",
699
+ .positional("script", {
700
+ describe: "The path to an entry point for your worker",
439
701
  type: "string",
440
- demandOption: true,
441
702
  })
442
703
  .option("name", {
443
- describe: "name of the script",
704
+ describe: "Name of the worker",
444
705
  type: "string",
445
706
  })
446
707
  .option("format", {
@@ -450,7 +711,7 @@ export async function main(argv: string[]): Promise<void> {
450
711
  .option("env", {
451
712
  describe: "Perform on a specific environment",
452
713
  type: "string",
453
- // TODO: get choices for the toml file?
714
+ alias: "e",
454
715
  })
455
716
  .option("compatibility-date", {
456
717
  describe: "Date to use for compatibility checks",
@@ -467,14 +728,21 @@ export async function main(argv: string[]): Promise<void> {
467
728
  default: true,
468
729
  })
469
730
  .option("ip", {
470
- describe: "IP address to listen on",
731
+ describe: "IP address to listen on, defaults to `localhost`",
471
732
  type: "string",
472
- default: "127.0.0.1",
473
733
  })
474
734
  .option("port", {
475
- describe: "Port to listen on, defaults to 8787",
735
+ describe: "Port to listen on",
476
736
  type: "number",
477
- default: 8787,
737
+ })
738
+ .option("inspector-port", {
739
+ describe: "Port for devtools to connect to",
740
+ type: "number",
741
+ })
742
+ .option("routes", {
743
+ describe: "Routes to upload",
744
+ alias: "route",
745
+ type: "array",
478
746
  })
479
747
  .option("host", {
480
748
  type: "string",
@@ -482,9 +750,8 @@ export async function main(argv: string[]): Promise<void> {
482
750
  "Host to forward requests to, defaults to the zone of project",
483
751
  })
484
752
  .option("local-protocol", {
485
- default: "http",
486
753
  describe: "Protocol to listen to requests on, defaults to http.",
487
- choices: ["http", "https"],
754
+ choices: ["http", "https"] as const,
488
755
  })
489
756
  .option("experimental-public", {
490
757
  describe: "Static assets to be served",
@@ -507,10 +774,9 @@ export async function main(argv: string[]): Promise<void> {
507
774
  array: true,
508
775
  })
509
776
  .option("upstream-protocol", {
510
- default: "https",
511
777
  describe:
512
778
  "Protocol to forward requests to host on, defaults to https.",
513
- choices: ["http", "https"],
779
+ choices: ["http", "https"] as const,
514
780
  })
515
781
  .option("jsx-factory", {
516
782
  describe: "The function that is called for each JSX element",
@@ -520,14 +786,24 @@ export async function main(argv: string[]): Promise<void> {
520
786
  describe: "The function that is called for each JSX fragment",
521
787
  type: "string",
522
788
  })
789
+ .option("local", {
790
+ alias: "l",
791
+ describe: "Run on my machine",
792
+ type: "boolean",
793
+ default: false, // I bet this will a point of contention. We'll revisit it.
794
+ })
523
795
  .option("experimental-enable-local-persistence", {
524
796
  describe: "Enable persistence for this session (only for local mode)",
525
797
  type: "boolean",
526
798
  });
527
799
  },
528
800
  async (args) => {
529
- const { filename, format } = args;
530
- const config = args.config as Config;
801
+ printWranglerBanner();
802
+ const configPath =
803
+ (args.config as ConfigPath) ||
804
+ (args.script && findWranglerToml(path.dirname(args.script)));
805
+ const config = readConfig(configPath, args.env);
806
+ const entry = await getEntry(args, config, "dev");
531
807
 
532
808
  if (args["experimental-public"]) {
533
809
  console.warn(
@@ -541,67 +817,122 @@ export async function main(argv: string[]): Promise<void> {
541
817
  );
542
818
  }
543
819
 
544
- if (config.site?.["entry-point"]) {
820
+ const upstreamProtocol =
821
+ (args["upstream-protocol"] as "http" | "https") ||
822
+ config.dev.upstream_protocol;
823
+ if (upstreamProtocol === "http") {
545
824
  console.warn(
546
- "Deprecation notice: The `site.entry-point` config field is no longer used.\n" +
547
- "The entry-point is specified via the command line (e.g. `wrangler dev path/to/script`).\n" +
548
- "Please remove the `site.entry-point` field from the `wrangler.toml` file."
825
+ "Setting upstream-protocol to http is not currently implemented.\n" +
826
+ "If this is required in your project, please add your use case to the following issue:\n" +
827
+ "https://github.com/cloudflare/wrangler2/issues/583."
549
828
  );
550
829
  }
551
830
 
552
- if (!args.local) {
553
- // -- snip, extract --
554
- const loggedIn = await loginOrRefreshIfRequired();
555
- if (!loggedIn) {
556
- // didn't login, let's just quit
557
- console.log("Did not login, quitting...");
558
- return;
559
- }
831
+ const accountId = !args.local ? await requireAuth(config) : undefined;
560
832
 
561
- if (!config.account_id) {
562
- config.account_id = await getAccountId();
563
- if (!config.account_id) {
564
- throw new Error("No account id found, quitting...");
565
- }
833
+ // TODO: if worker_dev = false and no routes, then error (only for dev)
834
+
835
+ /**
836
+ * Given something that resembles a URL,
837
+ * try to extract a host from it
838
+ */
839
+ function getHost(urlLike: string): string | undefined {
840
+ if (
841
+ !(urlLike.startsWith("http://") || urlLike.startsWith("https://"))
842
+ ) {
843
+ urlLike = "http://" + urlLike;
566
844
  }
567
- // -- snip, end --
845
+ return new URL(urlLike).host;
568
846
  }
569
847
 
570
- const environments = config.env ?? {};
571
- const envRootObj = args.env ? environments[args.env] || {} : config;
848
+ /**
849
+ * Given something that resembles a host,
850
+ * try to infer a zone id from it
851
+ */
852
+ async function getZoneId(host: string): Promise<string | undefined> {
853
+ const zones = await fetchResult<{ id: string }[]>(
854
+ `/zones`,
855
+ {},
856
+ new URLSearchParams({ name: host })
857
+ );
858
+ return zones[0]?.id;
859
+ }
572
860
 
573
- // TODO: this error shouldn't actually happen,
574
- // but we haven't fixed it internally yet
575
- if ("durable_objects" in envRootObj) {
576
- if (!(args.name || config.name)) {
577
- console.warn(
578
- '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.'
579
- );
580
- }
581
- // TODO: if not already published, publish a draft worker
861
+ const hostLike =
862
+ args.host ||
863
+ config.dev.host ||
864
+ (args.routes && args.routes[0]) ||
865
+ config.route ||
866
+ (config.routes && config.routes[0]);
867
+
868
+ // When we're given a host (in one of the above ways), we do 2 things:
869
+ // - We try to extract a host from it
870
+ // - We try to get a zone id from the host
871
+ //
872
+ // So it turns out it's particularly hard to get a 'valid' domain
873
+ // from a string, so we don't even try to validate TLDs, etc.
874
+ // Once we get something that looks like w.x.y.z-ish, we then try to
875
+ // get a zone id for it, by lopping off subdomains until we get a hit
876
+ // from the API. That's it!
877
+
878
+ const host = typeof hostLike === "string" ? getHost(hostLike) : undefined;
879
+
880
+ let zoneId: string | undefined;
881
+ const hostPieces = typeof host === "string" ? host.split(".") : undefined;
882
+
883
+ while (hostPieces && hostPieces.length > 1) {
884
+ zoneId = await getZoneId(hostPieces.join("."));
885
+ if (zoneId) break;
886
+ hostPieces.shift();
887
+ }
888
+ if (host && !zoneId) {
889
+ throw new Error(`Could not find zone for ${hostLike}`);
582
890
  }
583
891
 
892
+ const zone =
893
+ typeof zoneId === "string" && typeof host === "string"
894
+ ? {
895
+ host,
896
+ id: zoneId,
897
+ }
898
+ : undefined;
899
+
584
900
  const { waitUntilExit } = render(
585
901
  <Dev
586
- name={args.name || config.name}
587
- entry={path.relative(process.cwd(), filename)}
902
+ name={getScriptName(args, config)}
903
+ entry={entry}
588
904
  env={args.env}
905
+ zone={zone}
906
+ rules={getRules(config)}
907
+ legacyEnv={isLegacyEnv(args, config)}
589
908
  buildCommand={config.build || {}}
590
- format={format}
591
909
  initialMode={args.local ? "local" : "remote"}
592
- jsxFactory={args["jsx-factory"] || envRootObj?.jsx_factory}
593
- jsxFragment={args["jsx-fragment"] || envRootObj?.jsx_fragment}
910
+ jsxFactory={args["jsx-factory"] || config.jsx_factory}
911
+ jsxFragment={args["jsx-fragment"] || config.jsx_fragment}
912
+ upstreamProtocol={upstreamProtocol}
913
+ localProtocol={
914
+ // The typings are not quite clever enough to handle element accesses, only property accesses,
915
+ // so we need to cast here.
916
+ (args["local-protocol"] as "http" | "https") ||
917
+ config.dev.local_protocol
918
+ }
594
919
  enableLocalPersistence={
595
920
  args["experimental-enable-local-persistence"] || false
596
921
  }
597
- accountId={config.account_id}
922
+ accountId={accountId}
598
923
  assetPaths={getAssetPaths(
599
924
  config,
600
925
  args.site,
601
926
  args.siteInclude,
602
927
  args.siteExclude
603
928
  )}
604
- port={args.port || config.dev?.port}
929
+ port={
930
+ args.port || config.dev?.port || (await getPort({ port: 8787 }))
931
+ }
932
+ ip={args.ip || config.dev.ip}
933
+ inspectorPort={
934
+ args["inspector-port"] ?? (await getPort({ port: 9229 }))
935
+ }
605
936
  public={args["experimental-public"]}
606
937
  compatibilityDate={
607
938
  args["compatibility-date"] ||
@@ -614,7 +945,7 @@ export async function main(argv: string[]): Promise<void> {
614
945
  }
615
946
  usageModel={config.usage_model}
616
947
  bindings={{
617
- kv_namespaces: envRootObj.kv_namespaces?.map(
948
+ kv_namespaces: config.kv_namespaces?.map(
618
949
  ({ binding, preview_id, id: _id }) => {
619
950
  // In `dev`, we make folks use a separate kv namespace called
620
951
  // `preview_id` instead of `id` so that they don't
@@ -635,9 +966,12 @@ export async function main(argv: string[]): Promise<void> {
635
966
  };
636
967
  }
637
968
  ),
638
- vars: envRootObj.vars,
639
- durable_objects: envRootObj.durable_objects,
640
- services: envRootObj.experimental_services,
969
+ vars: config.vars,
970
+ wasm_modules: config.wasm_modules,
971
+ text_blobs: config.text_blobs,
972
+ durable_objects: config.durable_objects,
973
+ r2_buckets: config.r2_buckets,
974
+ unsafe: config.unsafe?.bindings,
641
975
  }}
642
976
  />
643
977
  );
@@ -654,15 +988,20 @@ export async function main(argv: string[]): Promise<void> {
654
988
  .option("env", {
655
989
  type: "string",
656
990
  describe: "Perform on a specific environment",
991
+ alias: "e",
657
992
  })
658
993
  .positional("script", {
659
- describe: "script to upload",
994
+ describe: "The path to an entry point for your worker",
660
995
  type: "string",
661
996
  })
662
997
  .option("name", {
663
- describe: "name to use when uploading",
998
+ describe: "Name of the worker",
664
999
  type: "string",
665
1000
  })
1001
+ .option("format", {
1002
+ choices: ["modules", "service-worker"] as const,
1003
+ describe: "Choose an entry type",
1004
+ })
666
1005
  .option("compatibility-date", {
667
1006
  describe: "Date to use for compatibility checks",
668
1007
  type: "string",
@@ -703,16 +1042,10 @@ export async function main(argv: string[]): Promise<void> {
703
1042
  type: "array",
704
1043
  })
705
1044
  .option("routes", {
706
- describe: "routes to upload",
1045
+ describe: "Routes to upload",
707
1046
  alias: "route",
708
1047
  type: "array",
709
1048
  })
710
- .option("services", {
711
- describe: "experimental support for services",
712
- type: "boolean",
713
- default: "false",
714
- hidden: true,
715
- })
716
1049
  .option("jsx-factory", {
717
1050
  describe: "The function that is called for each JSX element",
718
1051
  type: "string",
@@ -723,12 +1056,7 @@ export async function main(argv: string[]): Promise<void> {
723
1056
  });
724
1057
  },
725
1058
  async (args) => {
726
- if (args.local) {
727
- throw new NotImplementedError(
728
- "🚫 Local publishing is not yet supported"
729
- );
730
- }
731
-
1059
+ printWranglerBanner();
732
1060
  if (args["experimental-public"]) {
733
1061
  console.warn(
734
1062
  "🚨 The --experimental-public field is experimental and will change in the future."
@@ -740,7 +1068,11 @@ export async function main(argv: string[]): Promise<void> {
740
1068
  );
741
1069
  }
742
1070
 
743
- const config = args.config as Config;
1071
+ const configPath =
1072
+ (args.config as ConfigPath) ||
1073
+ (args.script && findWranglerToml(path.dirname(args.script)));
1074
+ const config = readConfig(configPath, args.env);
1075
+ const entry = await getEntry(args, config, "publish");
744
1076
 
745
1077
  if (args.latest) {
746
1078
  console.warn(
@@ -748,23 +1080,7 @@ export async function main(argv: string[]): Promise<void> {
748
1080
  );
749
1081
  }
750
1082
 
751
- if (!args.local) {
752
- // -- snip, extract --
753
- const loggedIn = await loginOrRefreshIfRequired();
754
- if (!loggedIn) {
755
- // didn't login, let's just quit
756
- console.log("Did not login, quitting...");
757
- return;
758
- }
759
-
760
- if (!config.account_id) {
761
- config.account_id = await getAccountId();
762
- if (!config.account_id) {
763
- throw new Error("No account id found, quitting...");
764
- }
765
- }
766
- // -- snip, end --
767
- }
1083
+ const accountId = await requireAuth(config);
768
1084
 
769
1085
  const assetPaths = getAssetPaths(
770
1086
  config,
@@ -773,9 +1089,11 @@ export async function main(argv: string[]): Promise<void> {
773
1089
  args.siteExclude
774
1090
  );
775
1091
  await publish({
776
- config: args.config as Config,
777
- name: args.name,
778
- script: args.script,
1092
+ config,
1093
+ accountId,
1094
+ name: getScriptName(args, config),
1095
+ rules: getRules(config),
1096
+ entry,
779
1097
  env: args.env,
780
1098
  compatibilityDate: args.latest
781
1099
  ? new Date().toISOString().substring(0, 10)
@@ -786,8 +1104,7 @@ export async function main(argv: string[]): Promise<void> {
786
1104
  jsxFragment: args["jsx-fragment"],
787
1105
  routes: args.routes,
788
1106
  assetPaths,
789
- format: undefined, // TODO: add args for this
790
- legacyEnv: undefined, // TODO: get this from somewhere... config?
1107
+ legacyEnv: isLegacyEnv(args, config),
791
1108
  experimentalPublic: args["experimental-public"] !== undefined,
792
1109
  });
793
1110
  }
@@ -796,12 +1113,12 @@ export async function main(argv: string[]): Promise<void> {
796
1113
  // tail
797
1114
  wrangler.command(
798
1115
  "tail [name]",
799
- "🦚 Starts a log tailing session for a deployed Worker.",
1116
+ "🦚 Starts a log tailing session for a published Worker.",
800
1117
  (yargs) => {
801
1118
  return (
802
1119
  yargs
803
1120
  .positional("name", {
804
- describe: "name of the worker",
1121
+ describe: "Name of the worker",
805
1122
  type: "string",
806
1123
  })
807
1124
  // TODO: auto-detect if this should be json or pretty based on atty
@@ -813,6 +1130,7 @@ export async function main(argv: string[]): Promise<void> {
813
1130
  .option("status", {
814
1131
  choices: ["ok", "error", "canceled"],
815
1132
  describe: "Filter by invocation status",
1133
+ array: true,
816
1134
  })
817
1135
  .option("header", {
818
1136
  type: "string",
@@ -821,6 +1139,7 @@ export async function main(argv: string[]): Promise<void> {
821
1139
  .option("method", {
822
1140
  type: "string",
823
1141
  describe: "Filter by HTTP method",
1142
+ array: true,
824
1143
  })
825
1144
  .option("sampling-rate", {
826
1145
  type: "number",
@@ -830,57 +1149,64 @@ export async function main(argv: string[]): Promise<void> {
830
1149
  type: "string",
831
1150
  describe: "Filter by a text match in console.log messages",
832
1151
  })
1152
+ .option("ip", {
1153
+ type: "string",
1154
+ describe:
1155
+ 'Filter by the IP address the request originates from. Use "self" to filter for your own IP',
1156
+ array: true,
1157
+ })
833
1158
  .option("env", {
834
1159
  type: "string",
835
1160
  describe: "Perform on a specific environment",
1161
+ alias: "e",
1162
+ })
1163
+ .option("debug", {
1164
+ type: "boolean",
1165
+ hidden: true,
1166
+ default: false,
1167
+ describe:
1168
+ "If a log would have been filtered out, send it through anyway alongside the filter which would have blocked it.",
836
1169
  })
837
1170
  );
838
- // TODO: filter by client ip, which can be 'self' or an ip address
839
1171
  },
840
1172
  async (args) => {
841
- if (args.local) {
842
- throw new NotImplementedError(
843
- `local mode is not yet supported for this command`
844
- );
1173
+ if (args.format === "pretty") {
1174
+ printWranglerBanner();
845
1175
  }
1176
+ const config = readConfig(args.config as ConfigPath, args.env);
846
1177
 
847
- const config = args.config as Config;
1178
+ const scriptName = getScriptName(args, config);
848
1179
 
849
- if (!(args.name || config.name)) {
1180
+ if (!scriptName) {
850
1181
  throw new Error("Missing script name");
851
1182
  }
852
- const scriptName = `${args.name || config.name}${
853
- args.env ? `-${args.env}` : ""
854
- }`;
855
-
856
- // -- snip, extract --
857
- const loggedIn = await loginOrRefreshIfRequired();
858
- if (!loggedIn) {
859
- // didn't login, let's just quit
860
- console.log("Did not login, quitting...");
861
- return;
862
- }
863
-
864
- if (!config.account_id) {
865
- config.account_id = await getAccountId();
866
- if (!config.account_id) {
867
- throw new Error("No account id found, quitting...");
868
- }
869
- }
870
- // -- snip, end --
871
1183
 
872
- const accountId = config.account_id;
1184
+ const accountId = await requireAuth(config);
873
1185
 
874
- const filters = {
875
- status: args.status as "ok" | "error" | "canceled",
1186
+ const cliFilters: TailCLIFilters = {
1187
+ status: args.status as ("ok" | "error" | "canceled")[],
876
1188
  header: args.header,
877
1189
  method: args.method,
878
- "sampling-rate": args["sampling-rate"],
1190
+ samplingRate: args["sampling-rate"],
879
1191
  search: args.search,
1192
+ clientIp: args.ip,
880
1193
  };
881
1194
 
882
- const { tail, expiration, /* sendHeartbeat, */ deleteTail } =
883
- await createTail(accountId, scriptName, filters);
1195
+ const filters = translateCLICommandToFilterMessage(
1196
+ cliFilters,
1197
+ args.debug
1198
+ );
1199
+
1200
+ const { tail, expiration, deleteTail } = await createTail(
1201
+ accountId,
1202
+ scriptName,
1203
+ filters,
1204
+ !isLegacyEnv(args, config) ? args.env : undefined
1205
+ );
1206
+
1207
+ const scriptDisplayName = `${scriptName}${
1208
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1209
+ }`;
884
1210
 
885
1211
  console.log(
886
1212
  `successfully created tail, expires at ${expiration.toLocaleString()}`
@@ -891,24 +1217,32 @@ export async function main(argv: string[]): Promise<void> {
891
1217
  await deleteTail();
892
1218
  });
893
1219
 
894
- tail.on("message", (data) => {
895
- console.log(JSON.stringify(JSON.parse(data.toString()), null, " "));
896
- });
1220
+ const printLog: (data: RawData) => void =
1221
+ args.format === "pretty" ? prettyPrintLogs : jsonPrintLogs;
1222
+
1223
+ tail.on("message", printLog);
897
1224
 
898
1225
  while (tail.readyState !== tail.OPEN) {
899
1226
  switch (tail.readyState) {
900
1227
  case tail.CONNECTING:
901
- await setTimeout(1000);
1228
+ await setTimeout(100);
902
1229
  break;
903
1230
  case tail.CLOSING:
904
- await setTimeout(1000);
1231
+ await setTimeout(100);
905
1232
  break;
906
1233
  case tail.CLOSED:
907
- process.exit(1);
1234
+ throw new Error(
1235
+ `Connection to ${scriptDisplayName} closed unexpectedly.`
1236
+ );
908
1237
  }
909
1238
  }
910
1239
 
911
- console.log(`Connected to ${scriptName}, waiting for logs...`);
1240
+ console.log(`Connected to ${scriptDisplayName}, waiting for logs...`);
1241
+
1242
+ tail.on("close", async () => {
1243
+ tail.terminate();
1244
+ await deleteTail();
1245
+ });
912
1246
  }
913
1247
  );
914
1248
 
@@ -919,14 +1253,12 @@ export async function main(argv: string[]): Promise<void> {
919
1253
  (yargs) => {
920
1254
  return yargs
921
1255
  .positional("method", {
1256
+ type: "string",
922
1257
  describe: "Type of request to preview your worker",
923
- choices: ["GET", "POST"],
924
- default: ["GET"],
925
1258
  })
926
1259
  .positional("body", {
927
1260
  type: "string",
928
1261
  describe: "Body string to post to your preview worker request.",
929
- default: "Null",
930
1262
  })
931
1263
  .option("env", {
932
1264
  type: "string",
@@ -938,15 +1270,93 @@ export async function main(argv: string[]): Promise<void> {
938
1270
  type: "boolean",
939
1271
  });
940
1272
  },
941
- () => {
942
- // "🔬 [DEPRECATED] Preview your code temporarily on https://cloudflareworkers.com"
943
- throw new DeprecationError(
944
- "`wrangler preview` has been deprecated, please refer to TODO://some/path for alternatives"
1273
+ async (args) => {
1274
+ if (args.method || args.body) {
1275
+ throw new DeprecationError(
1276
+ "The `wrangler preview` command has been deprecated.\n" +
1277
+ "Try using `wrangler dev` to to try out a worker during development.\n"
1278
+ );
1279
+ }
1280
+
1281
+ // Delegate to `wrangler dev`
1282
+ console.warn(
1283
+ "***************************************************\n" +
1284
+ "The `wrangler preview` command has been deprecated.\n" +
1285
+ "Attempting to run `wrangler dev` instead.\n" +
1286
+ "***************************************************\n"
945
1287
  );
1288
+
1289
+ const config = readConfig(args.config as ConfigPath, args.env);
1290
+ const entry = await getEntry({}, config, "dev");
1291
+
1292
+ const accountId = await requireAuth(config);
1293
+
1294
+ const { waitUntilExit } = render(
1295
+ <Dev
1296
+ name={config.name}
1297
+ entry={entry}
1298
+ rules={getRules(config)}
1299
+ env={args.env}
1300
+ zone={undefined}
1301
+ legacyEnv={isLegacyEnv(args, config)}
1302
+ buildCommand={config.build || {}}
1303
+ initialMode={args.local ? "local" : "remote"}
1304
+ jsxFactory={config.jsx_factory}
1305
+ jsxFragment={config.jsx_fragment}
1306
+ upstreamProtocol={config.dev.upstream_protocol}
1307
+ localProtocol={config.dev.local_protocol}
1308
+ enableLocalPersistence={false}
1309
+ accountId={accountId}
1310
+ assetPaths={undefined}
1311
+ port={config.dev?.port}
1312
+ ip={config.dev.ip}
1313
+ public={undefined}
1314
+ compatibilityDate={
1315
+ config.compatibility_date ||
1316
+ new Date().toISOString().substring(0, 10)
1317
+ }
1318
+ compatibilityFlags={
1319
+ (args["compatibility-flags"] as string[]) ||
1320
+ config.compatibility_flags
1321
+ }
1322
+ usageModel={config.usage_model}
1323
+ bindings={{
1324
+ kv_namespaces: config.kv_namespaces?.map(
1325
+ ({ binding, preview_id, id: _id }) => {
1326
+ // In `dev`, we make folks use a separate kv namespace called
1327
+ // `preview_id` instead of `id` so that they don't
1328
+ // break production data. So here we check that a `preview_id`
1329
+ // has actually been configured.
1330
+ // This whole block of code will be obsoleted in the future
1331
+ // when we have copy-on-write for previews on edge workers.
1332
+ if (!preview_id) {
1333
+ // TODO: This error has to be a _lot_ better, ideally just asking
1334
+ // to create a preview namespace for the user automatically
1335
+ throw new Error(
1336
+ `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`
1337
+ ); // Ugh, I really don't like this message very much
1338
+ }
1339
+ return {
1340
+ binding,
1341
+ id: preview_id,
1342
+ };
1343
+ }
1344
+ ),
1345
+ vars: config.vars,
1346
+ wasm_modules: config.wasm_modules,
1347
+ text_blobs: config.text_blobs,
1348
+ durable_objects: config.durable_objects,
1349
+ r2_buckets: config.r2_buckets,
1350
+ unsafe: config.unsafe?.bindings,
1351
+ }}
1352
+ inspectorPort={await getPort({ port: 9229 })}
1353
+ />
1354
+ );
1355
+ await waitUntilExit();
946
1356
  }
947
1357
  );
948
1358
 
949
- // route
1359
+ // [DEPRECATED] route
950
1360
  wrangler.command(
951
1361
  "route",
952
1362
  false, // I think we want to hide this command
@@ -955,7 +1365,7 @@ export async function main(argv: string[]): Promise<void> {
955
1365
  return routeYargs
956
1366
  .command(
957
1367
  "list",
958
- "List a route associated with a zone",
1368
+ "List the routes associated with a zone",
959
1369
  (yargs) => {
960
1370
  return yargs
961
1371
  .option("env", {
@@ -964,26 +1374,28 @@ export async function main(argv: string[]): Promise<void> {
964
1374
  })
965
1375
  .option("zone", {
966
1376
  type: "string",
967
- describe: "zone id",
1377
+ describe: "Zone id",
968
1378
  })
969
1379
  .positional("zone", {
970
- describe: "zone id",
1380
+ describe: "Zone id",
971
1381
  type: "string",
972
1382
  });
973
1383
  },
974
- async (args) => {
975
- console.log(":route list", args);
976
- // TODO: use environment (current wrangler doesn't do so?)
977
- const zone = args.zone || (args.config as Config).zone_id;
978
- if (!zone) {
979
- throw new Error("missing zone id");
980
- }
981
-
982
- console.log(await fetchResult(`/zones/${zone}/workers/routes`));
1384
+ () => {
1385
+ // "👯 [DEPRECATED]. Use wrangler.toml to manage routes.
1386
+ const deprecationNotice =
1387
+ "`wrangler route list` has been deprecated.";
1388
+ const futureRoutes =
1389
+ "Refer to wrangler.toml for a list of routes the worker will be deployed to upon publishing.";
1390
+ const presentRoutes =
1391
+ "Refer to the Cloudflare Dashboard to see the routes this worker is currently running on.";
1392
+ throw new DeprecationError(
1393
+ `${deprecationNotice}\n${futureRoutes}\n${presentRoutes}`
1394
+ );
983
1395
  }
984
1396
  )
985
1397
  .command(
986
- "delete <id>",
1398
+ "delete",
987
1399
  "Delete a route associated with a zone",
988
1400
  (yargs) => {
989
1401
  return yargs
@@ -1000,21 +1412,22 @@ export async function main(argv: string[]): Promise<void> {
1000
1412
  describe: "Perform on a specific environment",
1001
1413
  });
1002
1414
  },
1003
- async (args) => {
1004
- console.log(":route delete", args);
1005
- // TODO: use environment (current wrangler doesn't do so?)
1006
- const zone = args.zone || (args.config as Config).zone_id;
1007
- if (!zone) {
1008
- throw new Error("missing zone id");
1009
- }
1010
-
1011
- console.log(
1012
- await fetchResult(`/zones/${zone}/workers/routes/${args.id}`, {
1013
- method: "DELETE",
1014
- })
1015
- );
1415
+ () => {
1416
+ // "👯 [DEPRECATED]. Use wrangler.toml to manage routes.
1417
+ const deprecationNotice =
1418
+ "`wrangler route delete` has been deprecated.";
1419
+ const shouldDo =
1420
+ "Remove the unwanted route(s) from wrangler.toml and run `wrangler publish` to remove your worker from those routes.";
1421
+ throw new DeprecationError(`${deprecationNotice}\n${shouldDo}`);
1016
1422
  }
1017
1423
  );
1424
+ },
1425
+ () => {
1426
+ // "👯 [DEPRECATED]. Use wrangler.toml to manage routes.
1427
+ const deprecationNotice = "`wrangler route` has been deprecated.";
1428
+ const shouldDo =
1429
+ "Please use wrangler.toml and/or `wrangler publish --routes` to modify routes";
1430
+ throw new DeprecationError(`${deprecationNotice}\n${shouldDo}`);
1018
1431
  }
1019
1432
  );
1020
1433
 
@@ -1028,7 +1441,7 @@ export async function main(argv: string[]): Promise<void> {
1028
1441
  },
1029
1442
  () => {
1030
1443
  throw new DeprecationError(
1031
- "`wrangler subdomain` has been deprecated, please refer to TODO://some/path for alternatives"
1444
+ "`wrangler subdomain` has been deprecated, please refer to https://github.com/cloudflare/wrangler2/blob/main/docs/deprecations.md#subdomain for alternatives"
1032
1445
  );
1033
1446
  }
1034
1447
  );
@@ -1039,117 +1452,121 @@ export async function main(argv: string[]): Promise<void> {
1039
1452
  "🤫 Generate a secret that can be referenced in the worker script",
1040
1453
  (secretYargs) => {
1041
1454
  return secretYargs
1455
+ .command(subHelp)
1042
1456
  .command(
1043
1457
  "put <key>",
1044
1458
  "Create or update a secret variable for a script",
1045
1459
  (yargs) => {
1046
1460
  return yargs
1047
1461
  .positional("key", {
1048
- describe: "The variable name to be accessible in the script.",
1462
+ describe: "The variable name to be accessible in the script",
1049
1463
  type: "string",
1050
1464
  })
1051
1465
  .option("name", {
1052
- describe: "name of the script",
1466
+ describe: "Name of the worker",
1053
1467
  type: "string",
1054
1468
  })
1055
1469
  .option("env", {
1056
1470
  type: "string",
1057
1471
  describe:
1058
- "Binds the secret to the script of the specific environment.",
1472
+ "Binds the secret to the Worker of the specific environment",
1473
+ alias: "e",
1059
1474
  });
1060
1475
  },
1061
1476
  async (args) => {
1062
- const config = args.config as Config;
1477
+ printWranglerBanner();
1478
+ const config = readConfig(args.config as ConfigPath, args.env);
1063
1479
 
1064
- // TODO: use environment (how does current wrangler do it?)
1065
- const scriptName = args.name || config.name;
1480
+ const scriptName = getScriptName(args, config);
1066
1481
  if (!scriptName) {
1067
1482
  throw new Error("Missing script name");
1068
1483
  }
1069
1484
 
1070
- if (args.local) {
1071
- console.warn("`wrangler secret put` is a no-op in --local mode");
1072
- }
1073
-
1074
- if (!args.local) {
1075
- // -- snip, extract --
1076
- const loggedIn = await loginOrRefreshIfRequired();
1077
- if (!loggedIn) {
1078
- // didn't login, let's just quit
1079
- console.log("Did not login, quitting...");
1080
- return;
1081
- }
1485
+ const isInteractive = process.stdin.isTTY;
1486
+ const accountId = await requireAuth(config, isInteractive);
1082
1487
 
1083
- if (!config.account_id) {
1084
- config.account_id = await getAccountId();
1085
- if (!config.account_id) {
1086
- throw new Error("No account id found, quitting...");
1087
- }
1088
- }
1089
- // -- snip, end --
1090
- }
1488
+ const secretValue = isInteractive
1489
+ ? await prompt("Enter a secret value:", "password")
1490
+ : await readFromStdin();
1091
1491
 
1092
- const secretValue = await prompt(
1093
- "Enter a secret value:",
1094
- "password"
1492
+ console.log(
1493
+ `🌀 Creating the secret for script ${scriptName} ${
1494
+ args.env && !isLegacyEnv(args, config) ? `(${args.env})` : ""
1495
+ }`
1095
1496
  );
1096
1497
 
1097
- if (args.local) {
1098
- return;
1498
+ async function submitSecret() {
1499
+ const url =
1500
+ !args.env || isLegacyEnv(args, config)
1501
+ ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1502
+ : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1503
+
1504
+ return await fetchResult(url, {
1505
+ method: "PUT",
1506
+ headers: { "Content-Type": "application/json" },
1507
+ body: JSON.stringify({
1508
+ name: args.key,
1509
+ text: secretValue,
1510
+ type: "secret_text",
1511
+ }),
1512
+ });
1099
1513
  }
1100
1514
 
1101
- console.log(`🌀 Creating the secret for script ${scriptName}`);
1102
-
1103
- async function submitSecret() {
1104
- return await fetchResult(
1105
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/`,
1515
+ const createDraftWorker = async () => {
1516
+ // TODO: log a warning
1517
+ await fetchResult(
1518
+ !args["legacy-env"] && args.env
1519
+ ? `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}`
1520
+ : `/accounts/${accountId}/workers/scripts/${scriptName}`,
1106
1521
  {
1107
1522
  method: "PUT",
1108
- headers: { "Content-Type": "application/json" },
1109
- body: JSON.stringify({
1110
- name: args.key,
1111
- text: secretValue,
1112
- type: "secret_text",
1523
+ body: createWorkerUploadForm({
1524
+ name: scriptName,
1525
+ main: {
1526
+ name: scriptName,
1527
+ content: `export default { fetch() {} }`,
1528
+ type: "esm",
1529
+ },
1530
+ bindings: {
1531
+ kv_namespaces: [],
1532
+ vars: {},
1533
+ durable_objects: { bindings: [] },
1534
+ r2_buckets: [],
1535
+ wasm_modules: {},
1536
+ text_blobs: {},
1537
+ unsafe: [],
1538
+ },
1539
+ modules: [],
1540
+ migrations: undefined,
1541
+ compatibility_date: undefined,
1542
+ compatibility_flags: undefined,
1543
+ usage_model: undefined,
1113
1544
  }),
1114
1545
  }
1115
1546
  );
1547
+ };
1548
+
1549
+ function isMissingWorkerError(e: unknown): e is { code: 10007 } {
1550
+ return (
1551
+ typeof e === "object" &&
1552
+ e !== null &&
1553
+ (e as { code: 10007 }).code === 10007
1554
+ );
1116
1555
  }
1117
1556
 
1118
1557
  try {
1119
1558
  await submitSecret();
1120
1559
  } catch (e) {
1121
- if (e.code === 10007) {
1122
- // upload a draft worker
1123
- await fetchResult(
1124
- `/accounts/${config.account_id}/workers/scripts/${scriptName}`,
1125
- {
1126
- method: "PUT",
1127
- body: toFormData({
1128
- name: scriptName,
1129
- main: {
1130
- name: scriptName,
1131
- content: `export default { fetch() {} }`,
1132
- type: "esm",
1133
- },
1134
- bindings: {
1135
- kv_namespaces: [],
1136
- vars: {},
1137
- durable_objects: { bindings: [] },
1138
- },
1139
- modules: [],
1140
- migrations: undefined,
1141
- compatibility_date: undefined,
1142
- compatibility_flags: undefined,
1143
- usage_model: undefined,
1144
- }),
1145
- }
1146
- );
1147
-
1148
- // and then try again
1560
+ if (isMissingWorkerError(e)) {
1561
+ // create a draft worker and try again
1562
+ await createDraftWorker();
1149
1563
  await submitSecret();
1150
1564
  // TODO: delete the draft worker if this failed too?
1565
+ } else {
1566
+ throw e;
1151
1567
  }
1152
1568
  }
1569
+
1153
1570
  console.log(`✨ Success! Uploaded secret ${args.key}`);
1154
1571
  }
1155
1572
  )
@@ -1157,71 +1574,54 @@ export async function main(argv: string[]): Promise<void> {
1157
1574
  "delete <key>",
1158
1575
  "Delete a secret variable from a script",
1159
1576
  (yargs) => {
1577
+ printWranglerBanner();
1160
1578
  return yargs
1161
1579
  .positional("key", {
1162
- describe: "The variable name to be accessible in the script.",
1580
+ describe: "The variable name to be accessible in the script",
1163
1581
  type: "string",
1164
1582
  })
1165
1583
  .option("name", {
1166
- describe: "name of the script",
1584
+ describe: "Name of the worker",
1167
1585
  type: "string",
1168
1586
  })
1169
1587
  .option("env", {
1170
1588
  type: "string",
1171
1589
  describe:
1172
- "Binds the secret to the script of the specific environment.",
1590
+ "Binds the secret to the Worker of the specific environment",
1591
+ alias: "e",
1173
1592
  });
1174
1593
  },
1175
1594
  async (args) => {
1176
- const config = args.config as Config;
1595
+ const config = readConfig(args.config as ConfigPath, args.env);
1177
1596
 
1178
- // TODO: use environment (how does current wrangler do it?)
1179
- const scriptName = args.name || config.name;
1597
+ const scriptName = getScriptName(args, config);
1180
1598
  if (!scriptName) {
1181
1599
  throw new Error("Missing script name");
1182
1600
  }
1183
1601
 
1184
- if (args.local) {
1185
- console.warn(
1186
- "`wrangler secret delete` is a no-op in --local mode"
1187
- );
1188
- }
1189
-
1190
- if (!args.local) {
1191
- // -- snip, extract --
1192
- const loggedIn = await loginOrRefreshIfRequired();
1193
- if (!loggedIn) {
1194
- // didn't login, let's just quit
1195
- console.log("Did not login, quitting...");
1196
- return;
1197
- }
1198
-
1199
- if (!config.account_id) {
1200
- config.account_id = await getAccountId();
1201
- if (!config.account_id) {
1202
- throw new Error("No account id found, quitting...");
1203
- }
1204
- }
1205
- // -- snip, end --
1206
- }
1602
+ const accountId = await requireAuth(config);
1207
1603
 
1208
1604
  if (
1209
1605
  await confirm(
1210
- `Are you sure you want to permanently delete the variable ${args.key} on the script ${scriptName}?`
1606
+ `Are you sure you want to permanently delete the variable ${
1607
+ args.key
1608
+ } on the script ${scriptName}${
1609
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1610
+ }?`
1211
1611
  )
1212
1612
  ) {
1213
1613
  console.log(
1214
- `🌀 Deleting the secret ${args.key} on script ${scriptName}.`
1614
+ `🌀 Deleting the secret ${args.key} on script ${scriptName}${
1615
+ args.env && !isLegacyEnv(args, config) ? ` (${args.env})` : ""
1616
+ }`
1215
1617
  );
1216
1618
 
1217
- if (args.local) {
1218
- return;
1219
- }
1619
+ const url =
1620
+ !args.env || isLegacyEnv(args, config)
1621
+ ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1622
+ : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1220
1623
 
1221
- await fetchResult(
1222
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets/${args.key}`,
1223
- { method: "DELETE" }
1224
- );
1624
+ await fetchResult(`${url}/${args.key}`, { method: "DELETE" });
1225
1625
  console.log(`✨ Success! Deleted secret ${args.key}`);
1226
1626
  }
1227
1627
  }
@@ -1232,59 +1632,32 @@ export async function main(argv: string[]): Promise<void> {
1232
1632
  (yargs) => {
1233
1633
  return yargs
1234
1634
  .option("name", {
1235
- describe: "name of the script",
1635
+ describe: "Name of the worker",
1236
1636
  type: "string",
1237
1637
  })
1238
1638
  .option("env", {
1239
1639
  type: "string",
1240
1640
  describe:
1241
- "Binds the secret to the script of the specific environment.",
1641
+ "Binds the secret to the Worker of the specific environment.",
1642
+ alias: "e",
1242
1643
  });
1243
1644
  },
1244
1645
  async (args) => {
1245
- const config = args.config as Config;
1646
+ const config = readConfig(args.config as ConfigPath, args.env);
1246
1647
 
1247
- // TODO: use environment (how does current wrangler do it?)
1248
- const scriptName = args.name || config.name;
1648
+ const scriptName = getScriptName(args, config);
1249
1649
  if (!scriptName) {
1250
1650
  throw new Error("Missing script name");
1251
1651
  }
1252
1652
 
1253
- if (args.local) {
1254
- console.warn("`wrangler secret list` is a no-op in --local mode");
1255
- }
1256
-
1257
- if (!args.local) {
1258
- // -- snip, extract --
1259
- const loggedIn = await loginOrRefreshIfRequired();
1260
- if (!loggedIn) {
1261
- // didn't login, let's just quit
1262
- console.log("Did not login, quitting...");
1263
- return;
1264
- }
1265
-
1266
- if (!config.account_id) {
1267
- config.account_id = await getAccountId();
1268
- if (!config.account_id) {
1269
- throw new Error("No account id found, quitting...");
1270
- }
1271
- }
1272
- // -- snip, end --
1273
- }
1653
+ const accountId = await requireAuth(config);
1274
1654
 
1275
- if (args.local) {
1276
- return;
1277
- }
1655
+ const url =
1656
+ !args.env || isLegacyEnv(args, config)
1657
+ ? `/accounts/${accountId}/workers/scripts/${scriptName}/secrets`
1658
+ : `/accounts/${accountId}/workers/services/${scriptName}/environments/${args.env}/secrets`;
1278
1659
 
1279
- console.log(
1280
- JSON.stringify(
1281
- await fetchResult(
1282
- `/accounts/${config.account_id}/workers/scripts/${scriptName}/secrets`
1283
- ),
1284
- null,
1285
- " "
1286
- )
1287
- );
1660
+ console.log(JSON.stringify(await fetchResult(url), null, " "));
1288
1661
  }
1289
1662
  );
1290
1663
  }
@@ -1297,6 +1670,7 @@ export async function main(argv: string[]): Promise<void> {
1297
1670
  "🗂️ Interact with your Workers KV Namespaces",
1298
1671
  (namespaceYargs) => {
1299
1672
  return namespaceYargs
1673
+ .command(subHelp)
1300
1674
  .command(
1301
1675
  "create <namespace>",
1302
1676
  "Create a new namespace",
@@ -1310,6 +1684,7 @@ export async function main(argv: string[]): Promise<void> {
1310
1684
  .option("env", {
1311
1685
  type: "string",
1312
1686
  describe: "Perform on a specific environment",
1687
+ alias: "e",
1313
1688
  })
1314
1689
  .option("preview", {
1315
1690
  type: "boolean",
@@ -1317,6 +1692,7 @@ export async function main(argv: string[]): Promise<void> {
1317
1692
  });
1318
1693
  },
1319
1694
  async (args) => {
1695
+ printWranglerBanner();
1320
1696
  if (args._.length > 2) {
1321
1697
  const extraArgs = args._.slice(2).join(" ");
1322
1698
  throw new CommandLineArgsError(
@@ -1330,7 +1706,7 @@ export async function main(argv: string[]): Promise<void> {
1330
1706
  );
1331
1707
  }
1332
1708
 
1333
- const config = args.config as Config;
1709
+ const config = readConfig(args.config as ConfigPath, args.env);
1334
1710
  if (!config.name) {
1335
1711
  console.warn(
1336
1712
  "No configured name present, using `worker` as a prefix for the title"
@@ -1342,52 +1718,24 @@ export async function main(argv: string[]): Promise<void> {
1342
1718
  const preview = args.preview ? "_preview" : "";
1343
1719
  const title = `${name}${environment}-${args.namespace}${preview}`;
1344
1720
 
1345
- if (args.local) {
1346
- const { Miniflare } = await import("miniflare");
1347
- const mf = new Miniflare({
1348
- kvPersist: (args.kvPersist as string) || true,
1349
- // TODO: these options shouldn't be required
1350
- script: ` `, // has to be a string with at least one char
1351
- });
1352
- await mf.getKVNamespace(title); // this should "create" the namespace
1353
- console.log(`✨ Success! Created KV namespace ${title}`);
1354
- } else {
1355
- // -- snip, extract --
1356
- const loggedIn = await loginOrRefreshIfRequired();
1357
- if (!loggedIn) {
1358
- // didn't login, let's just quit
1359
- console.log("Did not login, quitting...");
1360
- return;
1361
- }
1721
+ const accountId = await requireAuth(config);
1362
1722
 
1363
- if (!config.account_id) {
1364
- config.account_id = await getAccountId();
1365
- if (!config.account_id) {
1366
- throw new Error("No account id found, quitting...");
1367
- }
1368
- }
1369
- // -- snip, end --
1723
+ // TODO: generate a binding name stripping non alphanumeric chars
1370
1724
 
1371
- // TODO: generate a binding name stripping non alphanumeric chars
1725
+ console.log(`🌀 Creating namespace with title "${title}"`);
1726
+ const namespaceId = await createNamespace(accountId, title);
1372
1727
 
1373
- console.log(`🌀 Creating namespace with title "${title}"`);
1374
- const namespaceId = await createNamespace(
1375
- config.account_id,
1376
- title
1377
- );
1378
-
1379
- console.log("✨ Success!");
1380
- const envString = args.env ? ` under [env.${args.env}]` : "";
1381
- const previewString = args.preview ? "preview_" : "";
1382
- console.log(
1383
- `Add the following to your configuration file in your kv_namespaces array${envString}:`
1384
- );
1385
- console.log(
1386
- `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1387
- );
1728
+ console.log("✨ Success!");
1729
+ const envString = args.env ? ` under [env.${args.env}]` : "";
1730
+ const previewString = args.preview ? "preview_" : "";
1731
+ console.log(
1732
+ `Add the following to your configuration file in your kv_namespaces array${envString}:`
1733
+ );
1734
+ console.log(
1735
+ `{ binding = "${args.namespace}", ${previewString}id = "${namespaceId}" }`
1736
+ );
1388
1737
 
1389
- // TODO: automatically write this block to the wrangler.toml config file??
1390
- }
1738
+ // TODO: automatically write this block to the wrangler.toml config file??
1391
1739
  }
1392
1740
  )
1393
1741
  .command(
@@ -1395,39 +1743,15 @@ export async function main(argv: string[]): Promise<void> {
1395
1743
  "Outputs a list of all KV namespaces associated with your account id.",
1396
1744
  {},
1397
1745
  async (args) => {
1398
- const config = args.config as Config;
1399
-
1400
- if (args.local) {
1401
- throw new NotImplementedError(
1402
- `local mode is not yet supported for this command`
1403
- );
1404
- } else {
1405
- // -- snip, extract --
1406
- const loggedIn = await loginOrRefreshIfRequired();
1407
- if (!loggedIn) {
1408
- // didn't login, let's just quit
1409
- console.log("Did not login, quitting...");
1410
- return;
1411
- }
1746
+ const config = readConfig(args.config as ConfigPath, undefined);
1412
1747
 
1413
- if (!config.account_id) {
1414
- config.account_id = await getAccountId();
1415
- if (!config.account_id) {
1416
- throw new Error("No account id found, quitting...");
1417
- }
1418
- }
1419
- // -- snip, end --
1748
+ const accountId = await requireAuth(config);
1420
1749
 
1421
- // TODO: we should show bindings if they exist for given ids
1750
+ // TODO: we should show bindings if they exist for given ids
1422
1751
 
1423
- console.log(
1424
- JSON.stringify(
1425
- await listNamespaces(config.account_id),
1426
- null,
1427
- " "
1428
- )
1429
- );
1430
- }
1752
+ console.log(
1753
+ JSON.stringify(await listNamespaces(accountId), null, " ")
1754
+ );
1431
1755
  }
1432
1756
  )
1433
1757
  .command(
@@ -1447,6 +1771,7 @@ export async function main(argv: string[]): Promise<void> {
1447
1771
  .option("env", {
1448
1772
  type: "string",
1449
1773
  describe: "Perform on a specific environment",
1774
+ alias: "e",
1450
1775
  })
1451
1776
  .option("preview", {
1452
1777
  type: "boolean",
@@ -1454,61 +1779,42 @@ export async function main(argv: string[]): Promise<void> {
1454
1779
  });
1455
1780
  },
1456
1781
  async (args) => {
1457
- const config = args.config as Config;
1782
+ printWranglerBanner();
1783
+ const config = readConfig(args.config as ConfigPath, args.env);
1458
1784
 
1459
- if (args.local) {
1460
- throw new NotImplementedError(
1461
- `local mode is not yet supported for this command`
1785
+ let id;
1786
+ try {
1787
+ id = getNamespaceId(args, config);
1788
+ } catch (e) {
1789
+ throw new CommandLineArgsError(
1790
+ "Not able to delete namespace.\n" + ((e as Error).message ?? e)
1462
1791
  );
1463
- } else {
1464
- let id;
1465
- try {
1466
- id = getNamespaceId(args);
1467
- } catch (e) {
1468
- throw new CommandLineArgsError(
1469
- "Not able to delete namespace.\n" + e.message
1470
- );
1471
- }
1472
-
1473
- // -- snip, extract --
1474
- const loggedIn = await loginOrRefreshIfRequired();
1475
- if (!loggedIn) {
1476
- // didn't login, let's just quit
1477
- console.log("Did not login, quitting...");
1478
- return;
1479
- }
1792
+ }
1480
1793
 
1481
- if (!config.account_id) {
1482
- config.account_id = await getAccountId();
1483
- if (!config.account_id) {
1484
- throw new Error("No account id found, quitting...");
1485
- }
1486
- }
1487
- // -- snip, end --
1794
+ const accountId = await requireAuth(config);
1488
1795
 
1489
- await fetchResult<{ id: string }>(
1490
- `/accounts/${config.account_id}/storage/kv/namespaces/${id}`,
1491
- { method: "DELETE" }
1492
- );
1796
+ await fetchResult<{ id: string }>(
1797
+ `/accounts/${accountId}/storage/kv/namespaces/${id}`,
1798
+ { method: "DELETE" }
1799
+ );
1493
1800
 
1494
- // TODO: recommend they remove it from wrangler.toml
1801
+ // TODO: recommend they remove it from wrangler.toml
1495
1802
 
1496
- // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1497
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1498
- // n
1499
- // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1500
- // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1501
- // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1502
- // y
1503
- // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1504
- // ✨ Success
1505
- // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1506
- // ➜ test-mf
1803
+ // test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1804
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1805
+ // n
1806
+ // 💁 Not deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1807
+ // ➜ test-mf wrangler kv:namespace delete --namespace-id 2a7d3d8b23fc4159b5afa489d6cfd388
1808
+ // Are you sure you want to delete namespace 2a7d3d8b23fc4159b5afa489d6cfd388? [y/n]
1809
+ // y
1810
+ // 🌀 Deleting namespace 2a7d3d8b23fc4159b5afa489d6cfd388
1811
+ // ✨ Success
1812
+ // ⚠️ Make sure to remove this "kv-namespace" entry from your configuration file!
1813
+ // ➜ test-mf
1507
1814
 
1508
- // TODO: do it automatically
1815
+ // TODO: do it automatically
1509
1816
 
1510
- // TODO: delete the preview namespace as well?
1511
- }
1817
+ // TODO: delete the preview namespace as well?
1512
1818
  }
1513
1819
  );
1514
1820
  }
@@ -1520,6 +1826,7 @@ export async function main(argv: string[]): Promise<void> {
1520
1826
  "🔑 Individually manage Workers KV key-value pairs",
1521
1827
  (kvKeyYargs) => {
1522
1828
  return kvKeyYargs
1829
+ .command(subHelp)
1523
1830
  .command(
1524
1831
  "put <key> [value]",
1525
1832
  "Writes a single key/value pair to the given namespace.",
@@ -1527,25 +1834,26 @@ export async function main(argv: string[]): Promise<void> {
1527
1834
  return yargs
1528
1835
  .positional("key", {
1529
1836
  type: "string",
1530
- describe: "The key to write to.",
1837
+ describe: "The key to write to",
1531
1838
  demandOption: true,
1532
1839
  })
1533
1840
  .positional("value", {
1534
1841
  type: "string",
1535
- describe: "The value to write.",
1842
+ describe: "The value to write",
1536
1843
  })
1537
1844
  .option("binding", {
1538
1845
  type: "string",
1539
- describe: "The binding of the namespace to write to.",
1846
+ describe: "The binding of the namespace to write to",
1540
1847
  })
1541
1848
  .option("namespace-id", {
1542
1849
  type: "string",
1543
- describe: "The id of the namespace to write to.",
1850
+ describe: "The id of the namespace to write to",
1544
1851
  })
1545
1852
  .check(demandOneOfOption("binding", "namespace-id"))
1546
1853
  .option("env", {
1547
1854
  type: "string",
1548
1855
  describe: "Perform on a specific environment",
1856
+ alias: "e",
1549
1857
  })
1550
1858
  .option("preview", {
1551
1859
  type: "boolean",
@@ -1553,7 +1861,7 @@ export async function main(argv: string[]): Promise<void> {
1553
1861
  })
1554
1862
  .option("ttl", {
1555
1863
  type: "number",
1556
- describe: "Time for which the entries should be visible.",
1864
+ describe: "Time for which the entries should be visible",
1557
1865
  })
1558
1866
  .option("expiration", {
1559
1867
  type: "number",
@@ -1562,18 +1870,19 @@ export async function main(argv: string[]): Promise<void> {
1562
1870
  })
1563
1871
  .option("path", {
1564
1872
  type: "string",
1565
- describe: "Read value from the file at a given path.",
1873
+ describe: "Read value from the file at a given path",
1566
1874
  })
1567
1875
  .check(demandOneOfOption("value", "path"));
1568
1876
  },
1569
1877
  async ({ key, ttl, expiration, ...args }) => {
1570
- const namespaceId = getNamespaceId(args);
1878
+ printWranglerBanner();
1879
+ const config = readConfig(args.config as ConfigPath, args.env);
1880
+ const namespaceId = getNamespaceId(args, config);
1571
1881
  // One of `args.path` and `args.value` must be defined
1572
1882
  const value = args.path
1573
- ? await readFile(args.path, "utf-8")
1883
+ ? readFileSync(args.path)
1574
1884
  : // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1575
1885
  args.value!;
1576
- const config = args.config as Config;
1577
1886
 
1578
1887
  if (args.path) {
1579
1888
  console.log(
@@ -1585,37 +1894,14 @@ export async function main(argv: string[]): Promise<void> {
1585
1894
  );
1586
1895
  }
1587
1896
 
1588
- if (args.local) {
1589
- const { Miniflare } = await import("miniflare");
1590
- const mf = new Miniflare({
1591
- kvPersist: (args.kvPersist as string) || true,
1592
- // TODO: these options shouldn't be required
1593
- script: ` `, // has to be a string with at least one char
1594
- });
1595
- const ns = await mf.getKVNamespace(namespaceId);
1596
- await ns.put(key, value, { expiration, expirationTtl: ttl });
1597
- } else {
1598
- // -- snip, extract --
1599
- const loggedIn = await loginOrRefreshIfRequired();
1600
- if (!loggedIn) {
1601
- // didn't login, let's just quit
1602
- console.log("Did not login, quitting...");
1603
- return;
1604
- }
1605
-
1606
- if (!config.account_id) {
1607
- config.account_id = await getAccountId();
1608
- if (!config.account_id) {
1609
- throw new Error("No account id found, quitting...");
1610
- }
1611
- }
1612
- // -- snip, end --
1897
+ const accountId = await requireAuth(config);
1613
1898
 
1614
- await putKeyValue(config.account_id, namespaceId, key, value, {
1615
- expiration,
1616
- expiration_ttl: ttl,
1617
- });
1618
- }
1899
+ await putKeyValue(accountId, namespaceId, {
1900
+ key,
1901
+ value,
1902
+ expiration,
1903
+ expiration_ttl: ttl,
1904
+ });
1619
1905
  }
1620
1906
  )
1621
1907
  .command(
@@ -1635,6 +1921,7 @@ export async function main(argv: string[]): Promise<void> {
1635
1921
  .option("env", {
1636
1922
  type: "string",
1637
1923
  describe: "Perform on a specific environment",
1924
+ alias: "e",
1638
1925
  })
1639
1926
  .option("preview", {
1640
1927
  type: "boolean",
@@ -1649,44 +1936,17 @@ export async function main(argv: string[]): Promise<void> {
1649
1936
  },
1650
1937
  async ({ prefix, ...args }) => {
1651
1938
  // TODO: support for limit+cursor (pagination)
1939
+ const config = readConfig(args.config as ConfigPath, args.env);
1940
+ const namespaceId = getNamespaceId(args, config);
1652
1941
 
1653
- const namespaceId = getNamespaceId(args);
1654
- const config = args.config as Config;
1942
+ const accountId = await requireAuth(config);
1655
1943
 
1656
- if (args.local) {
1657
- const { Miniflare } = await import("miniflare");
1658
- const mf = new Miniflare({
1659
- kvPersist: (args.kvPersist as string) || true,
1660
- // TODO: these options shouldn't be required
1661
- script: ` `, // has to be a string with at least one char
1662
- });
1663
- const ns = await mf.getKVNamespace(namespaceId);
1664
- const listResponse = await ns.list({ prefix });
1665
- console.log(JSON.stringify(listResponse.keys, null, " ")); // TODO: paginate, collate
1666
- } else {
1667
- // -- snip, extract --
1668
- const loggedIn = await loginOrRefreshIfRequired();
1669
- if (!loggedIn) {
1670
- // didn't login, let's just quit
1671
- console.log("Did not login, quitting...");
1672
- return;
1673
- }
1674
-
1675
- if (!config.account_id) {
1676
- config.account_id = await getAccountId();
1677
- if (!config.account_id) {
1678
- throw new Error("No account id found, quitting...");
1679
- }
1680
- }
1681
- // -- snip, end --
1682
-
1683
- const results = await listNamespaceKeys(
1684
- config.account_id,
1685
- namespaceId,
1686
- prefix
1687
- );
1688
- console.log(JSON.stringify(results, undefined, 2));
1689
- }
1944
+ const results = await listNamespaceKeys(
1945
+ accountId,
1946
+ namespaceId,
1947
+ prefix
1948
+ );
1949
+ console.log(JSON.stringify(results, undefined, 2));
1690
1950
  }
1691
1951
  )
1692
1952
  .command(
@@ -1711,6 +1971,7 @@ export async function main(argv: string[]): Promise<void> {
1711
1971
  .option("env", {
1712
1972
  type: "string",
1713
1973
  describe: "Perform on a specific environment",
1974
+ alias: "e",
1714
1975
  })
1715
1976
  .option("preview", {
1716
1977
  type: "boolean",
@@ -1724,44 +1985,12 @@ export async function main(argv: string[]): Promise<void> {
1724
1985
  });
1725
1986
  },
1726
1987
  async ({ key, ...args }) => {
1727
- const namespaceId = getNamespaceId(args);
1728
- const config = args.config as Config;
1729
-
1730
- if (args.local) {
1731
- const { Miniflare } = await import("miniflare");
1732
- const mf = new Miniflare({
1733
- kvPersist: (args.kvPersist as string) || true,
1734
- // TODO: these options shouldn't be required
1735
- script: ` `, // has to be a string with at least one char
1736
- });
1737
- const ns = await mf.getKVNamespace(namespaceId);
1738
- console.log(await ns.get(key));
1739
- return;
1740
- }
1741
-
1742
- if (!args.local) {
1743
- // -- snip, extract --
1744
- const loggedIn = await loginOrRefreshIfRequired();
1745
- if (!loggedIn) {
1746
- // didn't login, let's just quit
1747
- console.log("Did not login, quitting...");
1748
- return;
1749
- }
1988
+ const config = readConfig(args.config as ConfigPath, args.env);
1989
+ const namespaceId = getNamespaceId(args, config);
1750
1990
 
1751
- if (!config.account_id) {
1752
- config.account_id = await getAccountId();
1753
- if (!config.account_id) {
1754
- throw new Error("No account id found, quitting...");
1755
- }
1756
- }
1757
- // -- snip, end --
1758
- }
1991
+ const accountId = await requireAuth(config);
1759
1992
 
1760
- console.log(
1761
- await fetchRaw(
1762
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`
1763
- )
1764
- );
1993
+ console.log(await getKeyValue(accountId, namespaceId, key));
1765
1994
  }
1766
1995
  )
1767
1996
  .command(
@@ -1786,6 +2015,7 @@ export async function main(argv: string[]): Promise<void> {
1786
2015
  .option("env", {
1787
2016
  type: "string",
1788
2017
  describe: "Perform on a specific environment",
2018
+ alias: "e",
1789
2019
  })
1790
2020
  .option("preview", {
1791
2021
  type: "boolean",
@@ -1793,46 +2023,18 @@ export async function main(argv: string[]): Promise<void> {
1793
2023
  });
1794
2024
  },
1795
2025
  async ({ key, ...args }) => {
1796
- const namespaceId = getNamespaceId(args);
2026
+ printWranglerBanner();
2027
+ const config = readConfig(args.config as ConfigPath, args.env);
2028
+ const namespaceId = getNamespaceId(args, config);
1797
2029
 
1798
2030
  console.log(
1799
2031
  `deleting the key "${key}" on namespace ${namespaceId}`
1800
2032
  );
1801
2033
 
1802
- if (args.local) {
1803
- const { Miniflare } = await import("miniflare");
1804
- const mf = new Miniflare({
1805
- kvPersist: (args.kvPersist as string) || true,
1806
- // TODO: these options shouldn't be required
1807
- script: ` `, // has to be a string with at least one char
1808
- });
1809
- const ns = await mf.getKVNamespace(namespaceId);
1810
- console.log(await ns.delete(key));
1811
- return;
1812
- }
1813
-
1814
- const config = args.config as Config;
1815
-
1816
- if (!args.local) {
1817
- // -- snip, extract --
1818
- const loggedIn = await loginOrRefreshIfRequired();
1819
- if (!loggedIn) {
1820
- // didn't login, let's just quit
1821
- console.log("Did not login, quitting...");
1822
- return;
1823
- }
1824
-
1825
- if (!config.account_id) {
1826
- config.account_id = await getAccountId();
1827
- if (!config.account_id) {
1828
- throw new Error("No account id found, quitting...");
1829
- }
1830
- }
1831
- // -- snip, end --
1832
- }
2034
+ const accountId = await requireAuth(config);
1833
2035
 
1834
2036
  await fetchResult(
1835
- `/accounts/${config.account_id}/storage/kv/namespaces/${namespaceId}/values/${key}`,
2037
+ `/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
1836
2038
  { method: "DELETE" }
1837
2039
  );
1838
2040
  }
@@ -1846,6 +2048,7 @@ export async function main(argv: string[]): Promise<void> {
1846
2048
  "💪 Interact with multiple Workers KV key-value pairs at once",
1847
2049
  (kvBulkYargs) => {
1848
2050
  return kvBulkYargs
2051
+ .command(subHelp)
1849
2052
  .command(
1850
2053
  "put <filename>",
1851
2054
  "Upload multiple key-value pairs to a namespace",
@@ -1858,16 +2061,17 @@ export async function main(argv: string[]): Promise<void> {
1858
2061
  })
1859
2062
  .option("binding", {
1860
2063
  type: "string",
1861
- describe: "The name of the namespace to put to",
2064
+ describe: "The name of the namespace to insert values into",
1862
2065
  })
1863
2066
  .option("namespace-id", {
1864
2067
  type: "string",
1865
- describe: "The id of the namespace to put to",
2068
+ describe: "The id of the namespace to insert values into",
1866
2069
  })
1867
2070
  .check(demandOneOfOption("binding", "namespace-id"))
1868
2071
  .option("env", {
1869
2072
  type: "string",
1870
2073
  describe: "Perform on a specific environment",
2074
+ alias: "e",
1871
2075
  })
1872
2076
  .option("preview", {
1873
2077
  type: "boolean",
@@ -1875,62 +2079,80 @@ export async function main(argv: string[]): Promise<void> {
1875
2079
  });
1876
2080
  },
1877
2081
  async ({ filename, ...args }) => {
2082
+ printWranglerBanner();
1878
2083
  // The simplest implementation I could think of.
1879
2084
  // This could be made more efficient with a streaming parser/uploader
1880
2085
  // but we'll do that in the future if needed.
1881
2086
 
1882
- const namespaceId = getNamespaceId(args);
1883
- const config = args.config as Config;
1884
- const content = await readFile(filename, "utf-8");
1885
- let parsedContent;
1886
- try {
1887
- parsedContent = JSON.parse(content);
1888
- } catch (err) {
2087
+ const config = readConfig(args.config as ConfigPath, args.env);
2088
+ const namespaceId = getNamespaceId(args, config);
2089
+ const content = parseJSON(readFileSync(filename), filename);
2090
+
2091
+ if (!Array.isArray(content)) {
1889
2092
  throw new Error(
1890
- `Could not parse json from ${filename}.\n${err.message ?? err}`
2093
+ `Unexpected JSON input from "${filename}".\n` +
2094
+ `Expected an array of key-value objects but got type "${typeof content}".`
1891
2095
  );
1892
2096
  }
1893
2097
 
1894
- if (args.local) {
1895
- const { Miniflare } = await import("miniflare");
1896
- const mf = new Miniflare({
1897
- kvPersist: (args.kvPersist as string) || true,
1898
- // TODO: these options shouldn't be required
1899
- script: ` `, // has to be a string with at least one char
1900
- });
1901
- const ns = await mf.getKVNamespace(namespaceId);
1902
- for (const {
1903
- key,
1904
- value,
1905
- expiration,
1906
- expiration_ttl,
1907
- } of parsedContent) {
1908
- await ns.put(key, value, {
1909
- expiration,
1910
- expirationTtl: expiration_ttl,
1911
- });
1912
- }
1913
- } else {
1914
- // -- snip, extract --
1915
- const loggedIn = await loginOrRefreshIfRequired();
1916
- if (!loggedIn) {
1917
- // didn't login, let's just quit
1918
- console.log("Did not login, quitting...");
1919
- return;
1920
- }
1921
-
1922
- if (!config.account_id) {
1923
- config.account_id = await getAccountId();
1924
- if (!config.account_id) {
1925
- throw new Error("No account id found, quitting...");
2098
+ const errors: string[] = [];
2099
+ const warnings: string[] = [];
2100
+ for (let i = 0; i < content.length; i++) {
2101
+ const keyValue = content[i];
2102
+ if (typeof keyValue !== "object") {
2103
+ errors.push(
2104
+ `The item at index ${i} is type: "${typeof keyValue}" - ${JSON.stringify(
2105
+ keyValue
2106
+ )}`
2107
+ );
2108
+ } else if (!isKeyValue(keyValue)) {
2109
+ errors.push(
2110
+ `The item at index ${i} is ${JSON.stringify(keyValue)}`
2111
+ );
2112
+ } else {
2113
+ const props = unexpectedKeyValueProps(keyValue);
2114
+ if (props.length > 0) {
2115
+ warnings.push(
2116
+ `The item at index ${i} contains unexpected properties: ${JSON.stringify(
2117
+ props
2118
+ )}.`
2119
+ );
1926
2120
  }
1927
2121
  }
1928
- // -- snip, end --
1929
-
1930
- console.log(
1931
- await putBulkKeyValue(config.account_id, namespaceId, content)
2122
+ }
2123
+ if (warnings.length > 0) {
2124
+ console.warn(
2125
+ `Unexpected key-value properties in "${filename}".\n` +
2126
+ warnings.join("\n")
2127
+ );
2128
+ }
2129
+ if (errors.length > 0) {
2130
+ throw new Error(
2131
+ `Unexpected JSON input from "${filename}".\n` +
2132
+ `Each item in the array should be an object that matches:\n\n` +
2133
+ `interface KeyValue {\n` +
2134
+ ` key: string;\n` +
2135
+ ` value: string;\n` +
2136
+ ` expiration?: number;\n` +
2137
+ ` expiration_ttl?: number;\n` +
2138
+ ` metadata?: object;\n` +
2139
+ ` base64?: boolean;\n` +
2140
+ `}\n\n` +
2141
+ errors.join("\n")
1932
2142
  );
1933
2143
  }
2144
+
2145
+ const accountId = await requireAuth(config);
2146
+ await putBulkKeyValue(
2147
+ accountId,
2148
+ namespaceId,
2149
+ content,
2150
+ (index, total) => {
2151
+ console.log(`Uploaded ${index} of ${total}.`);
2152
+ }
2153
+ );
2154
+
2155
+ console.log("Success!");
1934
2156
  }
1935
2157
  )
1936
2158
  .command(
@@ -1955,91 +2177,176 @@ export async function main(argv: string[]): Promise<void> {
1955
2177
  .option("env", {
1956
2178
  type: "string",
1957
2179
  describe: "Perform on a specific environment",
2180
+ alias: "e",
1958
2181
  })
1959
2182
  .option("preview", {
1960
2183
  type: "boolean",
1961
2184
  describe: "Interact with a preview namespace",
2185
+ })
2186
+ .option("force", {
2187
+ type: "boolean",
2188
+ alias: "f",
2189
+ describe: "Do not ask for confirmation before deleting",
1962
2190
  });
1963
2191
  },
1964
2192
  async ({ filename, ...args }) => {
1965
- const namespaceId = getNamespaceId(args);
1966
- const config = args.config as Config;
1967
- const content = await readFile(filename, "utf-8");
1968
- let parsedContent;
1969
- try {
1970
- parsedContent = JSON.parse(content);
1971
- } catch (err) {
2193
+ printWranglerBanner();
2194
+ const config = readConfig(args.config as ConfigPath, args.env);
2195
+ const namespaceId = getNamespaceId(args, config);
2196
+
2197
+ if (!args.force) {
2198
+ const result = await confirm(
2199
+ `Are you sure you want to delete all the keys read from "${filename}" from kv-namespace with id "${namespaceId}"?`
2200
+ );
2201
+ if (!result) {
2202
+ console.log(`Not deleting keys read from "${filename}".`);
2203
+ return;
2204
+ }
2205
+ }
2206
+
2207
+ const content = parseJSON(
2208
+ readFileSync(filename),
2209
+ filename
2210
+ ) as string[];
2211
+
2212
+ if (!Array.isArray(content)) {
1972
2213
  throw new Error(
1973
- `Could not parse json from ${filename}.\n${err.message ?? err}`
2214
+ `Unexpected JSON input from "${filename}".\n` +
2215
+ `Expected an array of strings but got:\n${content}`
1974
2216
  );
1975
2217
  }
1976
2218
 
1977
- if (args.local) {
1978
- const { Miniflare } = await import("miniflare");
1979
- const mf = new Miniflare({
1980
- kvPersist: (args.kvPersist as string) || true,
1981
- // TODO: these options shouldn't be required
1982
- script: ` `, // has to be a string with at least one char
1983
- });
1984
- const ns = await mf.getKVNamespace(namespaceId);
1985
- for (const key of parsedContent) {
1986
- await ns.delete(key);
1987
- }
1988
- } else {
1989
- // -- snip, extract --
1990
- const loggedIn = await loginOrRefreshIfRequired();
1991
- if (!loggedIn) {
1992
- // didn't login, let's just quit
1993
- console.log("Did not login, quitting...");
1994
- return;
2219
+ const errors: string[] = [];
2220
+ for (let i = 0; i < content.length; i++) {
2221
+ const key = content[i];
2222
+ if (typeof key !== "string") {
2223
+ errors.push(
2224
+ `The item at index ${i} is type: "${typeof key}" - ${JSON.stringify(
2225
+ key
2226
+ )}`
2227
+ );
1995
2228
  }
2229
+ }
2230
+ if (errors.length > 0) {
2231
+ throw new Error(
2232
+ `Unexpected JSON input from "${filename}".\n` +
2233
+ `Expected an array of strings.\n` +
2234
+ errors.join("\n")
2235
+ );
2236
+ }
1996
2237
 
1997
- if (!config.account_id) {
1998
- config.account_id = await getAccountId();
1999
- if (!config.account_id) {
2000
- throw new Error("No account id found, quitting...");
2001
- }
2238
+ const accountId = await requireAuth(config);
2239
+
2240
+ await deleteBulkKeyValue(
2241
+ accountId,
2242
+ namespaceId,
2243
+ content,
2244
+ (index, total) => {
2245
+ console.log(`Deleted ${index} of ${total}.`);
2002
2246
  }
2003
- // -- snip, end --
2247
+ );
2004
2248
 
2005
- console.log(
2006
- await deleteBulkKeyValue(
2007
- config.account_id,
2008
- namespaceId,
2009
- content
2010
- )
2011
- );
2012
- }
2249
+ console.log("Success!");
2013
2250
  }
2014
2251
  );
2015
2252
  }
2016
2253
  );
2017
2254
 
2018
- wrangler.command("pages", "⚡️ Configure Cloudflare Pages", pages);
2255
+ wrangler.command("pages", "⚡️ Configure Cloudflare Pages", (pagesYargs) =>
2256
+ pages(pagesYargs.command(subHelp))
2257
+ );
2258
+
2259
+ wrangler.command("r2", "📦 Interact with an R2 store", (r2Yargs) => {
2260
+ return r2Yargs
2261
+ .command(subHelp)
2262
+ .command("bucket", "Manage R2 buckets", (r2BucketYargs) => {
2263
+ r2BucketYargs.command(
2264
+ "create <name>",
2265
+ "Create a new R2 bucket",
2266
+ (yargs) => {
2267
+ return yargs.positional("name", {
2268
+ describe: "The name of the new bucket",
2269
+ type: "string",
2270
+ demandOption: true,
2271
+ });
2272
+ },
2273
+ async (args) => {
2274
+ printWranglerBanner();
2275
+ // We expect three values in `_`: `r2`, `bucket`, `create`.
2276
+ if (args._.length > 3) {
2277
+ const extraArgs = args._.slice(3).join(" ");
2278
+ throw new CommandLineArgsError(
2279
+ `Unexpected additional positional arguments "${extraArgs}".`
2280
+ );
2281
+ }
2282
+
2283
+ const config = readConfig(args.config as ConfigPath, undefined);
2284
+
2285
+ const accountId = await requireAuth(config);
2286
+
2287
+ console.log(`Creating bucket ${args.name}.`);
2288
+ await createR2Bucket(accountId, args.name);
2289
+ console.log(`Created bucket ${args.name}.`);
2290
+ }
2291
+ );
2292
+
2293
+ r2BucketYargs.command("list", "List R2 buckets", {}, async (args) => {
2294
+ const config = readConfig(args.config as ConfigPath, undefined);
2295
+
2296
+ const accountId = await requireAuth(config);
2297
+
2298
+ console.log(JSON.stringify(await listR2Buckets(accountId), null, 2));
2299
+ });
2300
+
2301
+ r2BucketYargs.command(
2302
+ "delete <name>",
2303
+ "Delete an R2 bucket",
2304
+ (yargs) => {
2305
+ return yargs.positional("name", {
2306
+ describe: "The name of the bucket to delete",
2307
+ type: "string",
2308
+ demandOption: true,
2309
+ });
2310
+ },
2311
+ async (args) => {
2312
+ printWranglerBanner();
2313
+ // We expect three values in `_`: `r2`, `bucket`, `delete`.
2314
+ if (args._.length > 3) {
2315
+ const extraArgs = args._.slice(3).join(" ");
2316
+ throw new CommandLineArgsError(
2317
+ `Unexpected additional positional arguments "${extraArgs}".`
2318
+ );
2319
+ }
2320
+
2321
+ const config = readConfig(args.config as ConfigPath, undefined);
2322
+
2323
+ const accountId = await requireAuth(config);
2324
+
2325
+ console.log(`Deleting bucket ${args.name}.`);
2326
+ await deleteR2Bucket(accountId, args.name);
2327
+ console.log(`Deleted bucket ${args.name}.`);
2328
+ }
2329
+ );
2330
+ return r2BucketYargs;
2331
+ });
2332
+ });
2019
2333
 
2020
2334
  wrangler
2335
+ .option("legacy-env", {
2336
+ type: "boolean",
2337
+ describe: "Use legacy environments",
2338
+ })
2021
2339
  .option("config", {
2022
2340
  alias: "c",
2023
2341
  describe: "Path to .toml configuration file",
2024
2342
  type: "string",
2025
- async coerce(arg) {
2026
- return await readConfig(arg);
2027
- },
2028
- })
2029
- .option("local", {
2030
- alias: "l",
2031
- describe: "Run on my machine",
2032
- type: "boolean",
2033
- default: false, // I bet this will a point of contention. We'll revisit it.
2034
2343
  });
2035
2344
 
2036
- wrangler.group(["config", "help", "version"], "Flags:");
2345
+ wrangler.group(["config", "help", "version", "legacy-env"], "Flags:");
2037
2346
  wrangler.help().alias("h", "help");
2038
2347
  wrangler.version(wranglerVersion).alias("v", "version");
2039
2348
  wrangler.exitProcess(false);
2040
2349
 
2041
- await initialiseUserConfig();
2042
-
2043
2350
  try {
2044
2351
  await wrangler.parse();
2045
2352
  } catch (e) {
@@ -2047,8 +2354,14 @@ export async function main(argv: string[]): Promise<void> {
2047
2354
  wrangler.showHelp("error");
2048
2355
  console.error(""); // Just adds a bit of space
2049
2356
  console.error(e.message);
2357
+ } else if (e instanceof ParseError) {
2358
+ console.error("");
2359
+ e.notes.push({
2360
+ text: "\nIf you think this is a bug, please open an issue at: https://github.com/cloudflare/wrangler2/issues/new",
2361
+ });
2362
+ console.error(formatMessage(e));
2050
2363
  } else {
2051
- console.error(e.message);
2364
+ console.error(e instanceof Error ? e.message : e);
2052
2365
  console.error(""); // Just adds a bit of space
2053
2366
  console.error(
2054
2367
  `${fgGreenColor}%s${resetColor}`,