wrangler 2.7.0 → 2.8.0

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/helpers/mock-dialogs.ts +2 -0
  3. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -5
  4. package/src/__tests__/helpers/mock-known-routes.ts +7 -2
  5. package/src/__tests__/helpers/mock-kv.ts +29 -16
  6. package/src/__tests__/helpers/mock-oauth-flow.ts +90 -98
  7. package/src/__tests__/helpers/msw/handlers/deployments.ts +18 -0
  8. package/src/__tests__/helpers/msw/index.ts +30 -1
  9. package/src/__tests__/init.test.ts +3 -14
  10. package/src/__tests__/jest.setup.ts +0 -23
  11. package/src/__tests__/pages-deployment-tail.test.ts +72 -1
  12. package/src/__tests__/pages.test.ts +18 -16
  13. package/src/__tests__/publish.test.ts +744 -522
  14. package/src/__tests__/secret.test.ts +1 -9
  15. package/src/__tests__/tail.test.ts +66 -1
  16. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  17. package/src/__tests__/user.test.ts +5 -15
  18. package/src/api/dev.ts +17 -5
  19. package/src/cfetch/internal.ts +0 -3
  20. package/src/d1/backups.tsx +1 -5
  21. package/src/dev/remote.tsx +2 -0
  22. package/src/docs/index.ts +2 -1
  23. package/src/init.ts +1 -1
  24. package/src/pages/dev.ts +57 -62
  25. package/src/pages/functions/buildPlugin.ts +2 -20
  26. package/src/pages/functions/buildWorker.ts +136 -20
  27. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  28. package/src/pages/publish.tsx +36 -9
  29. package/src/publish/publish.ts +29 -4
  30. package/src/tail/createTail.ts +28 -1
  31. package/src/tail/printing.ts +15 -0
  32. package/templates/checked-fetch.js +1 -3
  33. package/wrangler-dist/cli.js +2935 -2824
  34. package/src/__tests__/helpers/mock-cfetch.ts +0 -278
@@ -8,7 +8,10 @@ import {
8
8
  } from "../user";
9
9
  import { mockConsoleMethods } from "./helpers/mock-console";
10
10
  import { useMockIsTTY } from "./helpers/mock-istty";
11
- import { mockOAuthFlow } from "./helpers/mock-oauth-flow";
11
+ import {
12
+ mockExchangeRefreshTokenForAccessToken,
13
+ mockOAuthFlow,
14
+ } from "./helpers/mock-oauth-flow";
12
15
  import {
13
16
  msw,
14
17
  mswSuccessOauthHandlers,
@@ -77,19 +80,7 @@ describe("User", () => {
77
80
  oauth_token: "hunter2",
78
81
  refresh_token: "Order 66",
79
82
  });
80
- // TODO: Use MSW to handle `/token` endpoints from different calls
81
- let counter = 0;
82
- msw.use(
83
- rest.post("*/oauth2/token", async (request, response, context) => {
84
- counter += 1;
85
- return response.once(
86
- context.status(400),
87
- context.body(
88
- `<html> <body> This shouldn't be sent, but should be handled </body> </html>`
89
- )
90
- );
91
- })
92
- );
83
+ mockExchangeRefreshTokenForAccessToken({ respondWith: "refreshError" });
93
84
 
94
85
  // Handles the requireAuth error throw from failed login that is unhandled due to directly calling it here
95
86
  await expect(
@@ -97,7 +88,6 @@ describe("User", () => {
97
88
  ).rejects.toThrowErrorMatchingInlineSnapshot(
98
89
  `"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."`
99
90
  );
100
- expect(counter).toBe(0);
101
91
  });
102
92
 
103
93
  it("should confirm no error message when refresh is successful", async () => {
package/src/api/dev.ts CHANGED
@@ -79,15 +79,26 @@ export async function unstable_dev(
79
79
  apiOptions?: unknown
80
80
  ): Promise<UnstableDevWorker> {
81
81
  // Note that not every experimental option is passed directly through to the underlying dev API - experimental options can be used here in unstable_dev. Otherwise we could just pass experimental down to dev blindly.
82
+
83
+ const experimentalOptions = {
84
+ // Defaults for "experimental" options
85
+ disableDevRegistry: false,
86
+ disableExperimentalWarning: false,
87
+ showInteractiveDevSession: false,
88
+ testMode: true,
89
+ // Override all options, including overwriting with "undefined"
90
+ ...options?.experimental,
91
+ };
92
+
82
93
  const {
83
94
  // there are two types of "experimental" options:
84
95
  // 1. options to unstable_dev that we're still testing or are unsure of
85
- disableDevRegistry = false,
86
- disableExperimentalWarning = false,
96
+ disableDevRegistry,
97
+ disableExperimentalWarning,
87
98
  forceLocal,
88
99
  liveReload,
89
- showInteractiveDevSession = false,
90
- testMode = true,
100
+ showInteractiveDevSession,
101
+ testMode,
91
102
  testScheduled,
92
103
  watch,
93
104
  // 2. options for alpha/beta products/libs
@@ -95,7 +106,8 @@ export async function unstable_dev(
95
106
  experimentalLocal,
96
107
  experimentalLocalRemoteKv,
97
108
  enablePagesAssetsServiceBinding,
98
- } = options?.experimental ?? {};
109
+ } = experimentalOptions;
110
+
99
111
  if (apiOptions) {
100
112
  logger.error(
101
113
  "unstable_dev's third argument (apiOptions) has been deprecated in favor of an `experimental` property within the second argument (options).\nPlease update your code from:\n`await unstable_dev('...', {...}, {...});`\nto:\n`await unstable_dev('...', {..., experimental: {...}});`"
@@ -252,9 +252,6 @@ export async function fetchDashboardScript(
252
252
  }
253
253
 
254
254
  return file ?? "";
255
-
256
- // Follow up on issue in Undici about multipart/form-data support & replace the workaround: https://github.com/nodejs/undici/issues/974
257
- // This should be using a builtin formData() parser pattern.
258
255
  } else {
259
256
  return response.text();
260
257
  }
@@ -191,11 +191,7 @@ export const DownloadHandler = withConfig<BackupDownloadArgs>(
191
191
  name
192
192
  );
193
193
  const filename =
194
- output ||
195
- path.join(
196
- process.env.INIT_CWD as string,
197
- `${name}.${backupId.slice(0, 8)}.sqlite3`
198
- );
194
+ output || path.resolve(`${name}.${backupId.slice(0, 8)}.sqlite3`);
199
195
 
200
196
  logger.log(`🌀 Downloading backup ${backupId} from '${name}'`);
201
197
  const response = await getBackupResponse(accountId, db.uuid, backupId);
@@ -12,6 +12,7 @@ import {
12
12
  import useInspector from "../inspect";
13
13
  import { logger } from "../logger";
14
14
  import { startPreviewServer, usePreviewServer } from "../proxy";
15
+ import { helpIfErrorIsSizeOrScriptStartup } from "../publish/publish";
15
16
  import { syncAssets } from "../sites";
16
17
  import {
17
18
  getAccountChoices,
@@ -494,6 +495,7 @@ export async function getRemotePreviewToken(props: RemoteProps) {
494
495
  // code 10049 happens when the preview token expires
495
496
  logger.log("Preview token expired, restart server to fetch a new one");
496
497
  } else {
498
+ helpIfErrorIsSizeOrScriptStartup(err, props.bundle?.dependencies || {});
497
499
  logger.error("Error on remote worker:", err);
498
500
  }
499
501
  }
package/src/docs/index.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  import type { ArgumentsCamelCase, Argv } from "yargs";
12
12
 
13
13
  const argToUrlHash = {
14
+ d1: "d1",
14
15
  docs: "docs",
15
16
  init: "init",
16
17
  generate: "generate",
@@ -56,7 +57,7 @@ export function docsOptions(yargs: Argv<CommonYargsOptions>) {
56
57
  "r2 object",
57
58
  "r2 bucket",
58
59
  // "dispatch-namespace", // TODO: Undocumented - Workers for Platforms
59
- // "d1", //TODO: Undocumented
60
+ "d1",
60
61
  // "pubsub", //TODO: Undocumented
61
62
  "login",
62
63
  "logout",
package/src/init.ts CHANGED
@@ -599,7 +599,7 @@ export async function initHandler(args: InitArgs) {
599
599
  isWritingScripts: shouldWritePackageJsonScripts,
600
600
  isCreatingWranglerToml: justCreatedWranglerToml,
601
601
  packagePath: pathToPackageJson,
602
- scriptPath: "src/index.ts",
602
+ scriptPath: "src/index.js",
603
603
  //? Should we have Environment argument for `wrangler init --from-dash` - Jacob
604
604
  extraToml: (await getWorkerConfig(accountId, fromDashScriptName, {
605
605
  defaultEnvironment,
package/src/pages/dev.ts CHANGED
@@ -13,6 +13,7 @@ import { getBasePath } from "../paths";
13
13
  import { buildFunctions } from "./build";
14
14
  import { ROUTES_SPEC_VERSION, SECONDS_TO_WAIT_FOR_PROXY } from "./constants";
15
15
  import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning } from "./errors";
16
+ import { buildRawWorker, checkRawWorker } from "./functions/buildWorker";
16
17
  import { validateRoutes } from "./functions/routes-validation";
17
18
  import { CLEANUP, CLEANUP_CALLBACKS, pagesBetaWarning } from "./utils";
18
19
  import type { AdditionalDevProps } from "../dev";
@@ -78,6 +79,16 @@ export function Options(yargs: Argv) {
78
79
  description:
79
80
  "The location of the single Worker script if not using functions",
80
81
  },
82
+ bundle: {
83
+ type: "boolean",
84
+ default: false,
85
+ hidden: true,
86
+ },
87
+ "no-bundle": {
88
+ type: "boolean",
89
+ default: true,
90
+ description: "Whether to run bundling on `_worker.js`",
91
+ },
81
92
  binding: {
82
93
  type: "array",
83
94
  description: "Bind variable/secret (KEY=VALUE)",
@@ -162,6 +173,8 @@ export const Handler = async ({
162
173
  "inspector-port": inspectorPort,
163
174
  proxy: requestedProxyPort,
164
175
  "script-path": singleWorkerScriptPath,
176
+ bundle,
177
+ noBundle,
165
178
  binding: bindings = [],
166
179
  kv: kvs = [],
167
180
  do: durableObjects = [],
@@ -252,23 +265,34 @@ export const Handler = async ({
252
265
  let scriptPath = "";
253
266
 
254
267
  if (usingWorkerScript) {
255
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
256
- scriptReadyResolve!();
257
-
258
268
  scriptPath = workerScriptPath;
259
-
260
- const runBuild = async () => {
261
- try {
262
- await esbuild.build({
263
- entryPoints: [scriptPath],
264
- write: false,
265
- // we need it to be bundled so that any imports that are used are affected by the blocker plugin
266
- bundle: true,
267
- plugins: [blockWorkerJsImports],
268
- });
269
- } catch {}
269
+ let runBuild = async () => {
270
+ await checkRawWorker(workerScriptPath, () => scriptReadyResolve());
270
271
  };
271
272
 
273
+ const enableBundling = bundle || !noBundle;
274
+ if (enableBundling) {
275
+ // We want to actually run the `_worker.js` script through the bundler
276
+ // So update the final path to the script that will be uploaded and
277
+ // change the `runBuild()` function to bundle the `_worker.js`.
278
+ scriptPath = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
279
+ runBuild = async () => {
280
+ try {
281
+ await buildRawWorker({
282
+ workerScriptPath,
283
+ outfile: scriptPath,
284
+ directory: directory ?? ".",
285
+ local: true,
286
+ sourcemap: true,
287
+ watch: true,
288
+ onEnd: () => scriptReadyResolve(),
289
+ });
290
+ } catch (e: unknown) {
291
+ logger.warn("Failed to bundle _worker.js.", e);
292
+ }
293
+ };
294
+ }
295
+
272
296
  await runBuild();
273
297
  watch([scriptPath], {
274
298
  persistent: true,
@@ -278,8 +302,7 @@ export const Handler = async ({
278
302
  });
279
303
  } else if (usingFunctions) {
280
304
  // Try to use Functions
281
- const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.mjs`);
282
- scriptPath = outfile;
305
+ scriptPath = join(tmpdir(), `./functionsWorker-${Math.random()}.mjs`);
283
306
 
284
307
  if (nodeCompat) {
285
308
  console.warn(
@@ -287,38 +310,31 @@ export const Handler = async ({
287
310
  );
288
311
  }
289
312
 
290
- logger.log(`Compiling worker to "${outfile}"...`);
313
+ logger.log(`Compiling worker to "${scriptPath}"...`);
291
314
  const onEnd = () => scriptReadyResolve();
292
315
  try {
293
- await buildFunctions({
294
- outfile,
295
- functionsDirectory,
296
- sourcemap: true,
297
- watch: true,
298
- onEnd,
299
- buildOutputDirectory: directory,
300
- nodeCompat,
301
- local: true,
302
- });
303
- await metrics.sendMetricsEvent("build pages functions");
316
+ const buildFn = async () => {
317
+ await buildFunctions({
318
+ outfile: scriptPath,
319
+ functionsDirectory,
320
+ sourcemap: true,
321
+ watch: true,
322
+ onEnd,
323
+ buildOutputDirectory: directory,
324
+ nodeCompat,
325
+ local: true,
326
+ });
327
+ await metrics.sendMetricsEvent("build pages functions");
328
+ };
304
329
 
330
+ await buildFn();
305
331
  // If Functions found routes, continue using Functions
306
332
  watch([functionsDirectory], {
307
333
  persistent: true,
308
334
  ignoreInitial: true,
309
335
  }).on("all", async () => {
310
336
  try {
311
- await buildFunctions({
312
- outfile,
313
- functionsDirectory,
314
- sourcemap: true,
315
- watch: true,
316
- onEnd,
317
- buildOutputDirectory: directory,
318
- nodeCompat,
319
- local: true,
320
- });
321
- await metrics.sendMetricsEvent("build pages functions");
337
+ await buildFn();
322
338
  } catch (e) {
323
339
  if (e instanceof FunctionsNoRoutesError) {
324
340
  logger.warn(
@@ -359,7 +375,7 @@ export const Handler = async ({
359
375
 
360
376
  if (scriptPath === "") {
361
377
  // Failed to get a script with or without Functions,
362
- // something really bad must have happend.
378
+ // something really bad must have happened.
363
379
  throw new FatalError(
364
380
  "Failed to start wrangler pages dev due to an unknown error",
365
381
  1
@@ -441,7 +457,7 @@ export const Handler = async ({
441
457
  * If _routes.json is invalid, don't exit but instead fallback to a sensible default
442
458
  * and continue to serve the assets. At the same time make sure we warn users that we
443
459
  * we detected an invalid file and that we'll be using a default.
444
- * This basically equivalates to serving a Functions or _worker.js project as is,
460
+ * This basically equates to serving a Functions or _worker.js project as is,
445
461
  * without applying any additional routing rules on top.
446
462
  */
447
463
  const error =
@@ -688,24 +704,3 @@ async function spawnProxyProcess({
688
704
 
689
705
  return port;
690
706
  }
691
-
692
- // TODO: Kill this once we have https://github.com/cloudflare/wrangler2/issues/2153
693
- const blockWorkerJsImports: esbuild.Plugin = {
694
- name: "block-worker-js-imports",
695
- setup(build) {
696
- build.onResolve({ filter: /.*/g }, (args) => {
697
- // If it's the entrypoint, let it be as is
698
- if (args.kind === "entry-point") {
699
- return {
700
- path: args.path,
701
- };
702
- }
703
- // Otherwise, block any imports that the file is requesting
704
- logger.error(
705
- `_worker.js is importing from another file. This will throw an error if deployed.\nYou should bundle your Worker or remove the import if it is unused.`
706
- );
707
- // Miniflare will error with this briefly down the line -- there's no point in continuing.
708
- process.exit(1);
709
- });
710
- },
711
- };
@@ -3,6 +3,7 @@ import { dirname, relative, resolve } from "node:path";
3
3
  import { bundleWorker } from "../../bundle";
4
4
  import { getBasePath } from "../../paths";
5
5
  import { D1_BETA_PREFIX } from "../../worker";
6
+ import { buildNotifierPlugin } from "./buildWorker";
6
7
  import type { Options as WorkerOptions } from "./buildWorker";
7
8
 
8
9
  type Options = Omit<WorkerOptions, "fallbackService" | "buildOutputDirectory">;
@@ -37,26 +38,7 @@ export function buildPlugin({
37
38
  (binding) => `${D1_BETA_PREFIX}${binding}`
38
39
  ),
39
40
  plugins: [
40
- {
41
- name: "wrangler notifier and monitor",
42
- setup(pluginBuild) {
43
- pluginBuild.onEnd((result) => {
44
- if (result.errors.length > 0) {
45
- console.error(
46
- `${result.errors.length} error(s) and ${result.warnings.length} warning(s) when compiling Worker.`
47
- );
48
- } else if (result.warnings.length > 0) {
49
- console.warn(
50
- `${result.warnings.length} warning(s) when compiling Worker.`
51
- );
52
- onEnd();
53
- } else {
54
- console.log("Compiled Worker successfully.");
55
- onEnd();
56
- }
57
- });
58
- },
59
- },
41
+ buildNotifierPlugin(onEnd),
60
42
  {
61
43
  name: "Assets",
62
44
  setup(pluginBuild) {
@@ -1,9 +1,12 @@
1
1
  import { access, cp, lstat, rm } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
+ import { build as esBuild } from "esbuild";
3
4
  import { nanoid } from "nanoid";
4
5
  import { bundleWorker } from "../../bundle";
6
+ import { logger } from "../../logger";
5
7
  import { getBasePath } from "../../paths";
6
8
  import { D1_BETA_PREFIX } from "../../worker";
9
+ import type { Plugin } from "esbuild";
7
10
 
8
11
  export type Options = {
9
12
  routesModule: string;
@@ -58,26 +61,7 @@ export function buildWorker({
58
61
  (binding) => `${D1_BETA_PREFIX}${binding}`
59
62
  ),
60
63
  plugins: [
61
- {
62
- name: "wrangler notifier and monitor",
63
- setup(pluginBuild) {
64
- pluginBuild.onEnd((result) => {
65
- if (result.errors.length > 0) {
66
- console.error(
67
- `${result.errors.length} error(s) and ${result.warnings.length} warning(s) when compiling Worker.`
68
- );
69
- } else if (result.warnings.length > 0) {
70
- console.warn(
71
- `${result.warnings.length} warning(s) when compiling Worker.`
72
- );
73
- onEnd();
74
- } else {
75
- console.log("Compiled Worker successfully.");
76
- onEnd();
77
- }
78
- });
79
- },
80
- },
64
+ buildNotifierPlugin(onEnd),
81
65
  {
82
66
  name: "Assets",
83
67
  setup(pluginBuild) {
@@ -164,3 +148,135 @@ export function buildWorker({
164
148
  }
165
149
  );
166
150
  }
151
+
152
+ export type RawOptions = {
153
+ workerScriptPath: string;
154
+ outfile: string;
155
+ directory: string;
156
+ minify?: boolean;
157
+ sourcemap?: boolean;
158
+ watch?: boolean;
159
+ plugins?: Plugin[];
160
+ onEnd?: () => void;
161
+ buildOutputDirectory?: string;
162
+ nodeCompat?: boolean;
163
+ local: boolean;
164
+ betaD1Shims?: string[];
165
+ };
166
+
167
+ /**
168
+ * This function bundles a raw `_worker.js` Pages file
169
+ * before it gets deployed.
170
+ *
171
+ * This allows Wrangler to add shims and other wrappers
172
+ * around the handlers, which is useful to support beta features.
173
+ */
174
+ export function buildRawWorker({
175
+ workerScriptPath,
176
+ outfile,
177
+ directory,
178
+ minify = false,
179
+ sourcemap = false,
180
+ watch = false,
181
+ plugins = [],
182
+ onEnd = () => {},
183
+ nodeCompat,
184
+ local,
185
+ betaD1Shims,
186
+ }: RawOptions) {
187
+ return bundleWorker(
188
+ {
189
+ file: workerScriptPath,
190
+ directory: resolve(directory),
191
+ format: "modules",
192
+ },
193
+ resolve(outfile),
194
+ {
195
+ minify,
196
+ sourcemap,
197
+ watch,
198
+ nodeCompat,
199
+ loader: {
200
+ ".txt": "text",
201
+ ".html": "text",
202
+ },
203
+ define: {},
204
+ betaD1Shims: (betaD1Shims || []).map(
205
+ (binding) => `${D1_BETA_PREFIX}${binding}`
206
+ ),
207
+ plugins: [...plugins, buildNotifierPlugin(onEnd)],
208
+ isOutfile: true,
209
+ serveAssetsFromWorker: false,
210
+ disableModuleCollection: true,
211
+ rules: [],
212
+ checkFetch: local,
213
+ targetConsumer: local ? "dev" : "publish",
214
+ local,
215
+ experimentalLocal: false,
216
+ }
217
+ );
218
+ }
219
+
220
+ /**
221
+ * Creates an esbuild plugin that can notify Wrangler (via the `onEnd()`)
222
+ * when the build completes.
223
+ */
224
+ export function buildNotifierPlugin(onEnd: () => void): Plugin {
225
+ return {
226
+ name: "wrangler notifier and monitor",
227
+ setup(pluginBuild) {
228
+ pluginBuild.onEnd((result) => {
229
+ if (result.errors.length > 0) {
230
+ logger.error(
231
+ `${result.errors.length} error(s) and ${result.warnings.length} warning(s) when compiling Worker.`
232
+ );
233
+ } else if (result.warnings.length > 0) {
234
+ logger.warn(
235
+ `${result.warnings.length} warning(s) when compiling Worker.`
236
+ );
237
+ onEnd();
238
+ } else {
239
+ logger.log("✨ Compiled Worker successfully");
240
+ onEnd();
241
+ }
242
+ });
243
+ },
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Runs the script through a simple esbuild bundle step to check for unwanted imports.
249
+ *
250
+ * This is useful when the user chooses not to bundle the `_worker.js` file by setting
251
+ * `--no-bundle` at the command line.
252
+ */
253
+ export async function checkRawWorker(scriptPath: string, onEnd: () => void) {
254
+ await esBuild({
255
+ entryPoints: [scriptPath],
256
+ write: false,
257
+ // we need it to be bundled so that any imports that are used are affected by the blocker plugin
258
+ bundle: true,
259
+ plugins: [blockWorkerJsImports, buildNotifierPlugin(onEnd)],
260
+ });
261
+ }
262
+
263
+ const blockWorkerJsImports: Plugin = {
264
+ name: "block-worker-js-imports",
265
+ setup(build) {
266
+ build.onResolve({ filter: /.*/g }, (args) => {
267
+ // If it's the entrypoint, let it be as is
268
+ if (args.kind === "entry-point") {
269
+ return {
270
+ path: args.path,
271
+ };
272
+ }
273
+ // Otherwise, block any imports that the file is requesting
274
+ logger.error(
275
+ "_worker.js is not being bundled by Wrangler but it is importing from another file.\n" +
276
+ "This will throw an error if deployed.\n" +
277
+ "You should bundle the Worker in a pre-build step, remove the import if it is unused, or ask Wrangler to bundle it by setting `--bundle`."
278
+ );
279
+ process.exit(1);
280
+ });
281
+ },
282
+ };