From 1c58a7470757508e64003d05c76d9deb7f223763 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 25 Sep 2024 20:42:09 +0100 Subject: [PATCH] Fix nodejs compat v2 upgrade (#6824) * fix: tidy up error messaging for unexpected use of Node.js APIs Fixes #6822 * fix: teach Wrangler about node_compat version date switch A compatibility of Sept 23, 2024 or later means that `nodejs_compat` is equivalent to `nodejs_compat_v2`. * refactor: move the node-compat parsing from Wrangler into Miniflare This logic is now also needed inside Miniflare to determine how to handle node imports. So this commit moves the relevant code in there and references it from Wrangler. The validation of the config is kept in Wrangler, since Miniflare doesn't need that particularly and the validation relies upon Wrangler logger and UserError. * fix: teach Miniflare about node_compat version date switch A compatibility of Sept 23, 2024 or later means that `nodejs_compat` is equivalent to `nodejs_compat_v2`. * test: update Node.js hybrid fixture to use nodejs_compat + compat date Now that we can set the compat date to Sept 23, 2024, we can update this fixture not to use the `nodejs_compat_v2` flag. * fixup! fix: teach Miniflare about node_compat version date switch * fixup! refactor: move the node-compat parsing from Wrangler into Miniflare * fixup! fix: tidy up error messaging for unexpected use of Node.js APIs * fixup! fix: tidy up error messaging for unexpected use of Node.js APIs * fixup! refactor: move the node-compat parsing from Wrangler into Miniflare * fixup! fix: tidy up error messaging for unexpected use of Node.js APIs * fixup! refactor: move the node-compat parsing from Wrangler into Miniflare * fix: also handle the case with `no_nodejs_compat_v2` * test: normalize output strings for tests * refactor: rename `getNodejsCompatMode()` to `getNodejsCompat()` --- .changeset/early-tigers-buy.md | 7 + .changeset/tender-items-exercise.md | 7 + fixtures/nodejs-hybrid-app/wrangler.toml | 5 +- packages/miniflare/src/plugins/core/index.ts | 7 +- .../miniflare/src/plugins/core/modules.ts | 25 ++- .../miniflare/src/plugins/core/node-compat.ts | 78 +++++++ packages/miniflare/src/plugins/index.ts | 2 + .../src/__tests__/configuration.test.ts | 12 +- .../wrangler/src/__tests__/deploy.test.ts | 211 +++++++++++++++--- .../src/__tests__/helpers/mock-console.ts | 55 +---- .../src/__tests__/helpers/normalize.ts | 58 +++++ .../src/__tests__/helpers/run-wrangler.ts | 4 +- packages/wrangler/src/__tests__/user.test.ts | 11 +- packages/wrangler/src/api/pages/deploy.tsx | 5 +- .../wrangler/src/api/startDevWorker/types.ts | 2 +- packages/wrangler/src/deploy/deploy.ts | 16 +- .../src/deployment-bundle/build-failures.ts | 19 +- .../wrangler/src/deployment-bundle/bundle.ts | 8 +- .../esbuild-plugins/log-build-output.ts | 4 +- .../src/deployment-bundle/node-compat.ts | 72 ++---- packages/wrangler/src/dev.tsx | 11 +- packages/wrangler/src/dev/dev.tsx | 2 +- packages/wrangler/src/dev/start-server.ts | 2 +- packages/wrangler/src/dev/use-esbuild.ts | 2 +- packages/wrangler/src/pages/build.ts | 16 +- packages/wrangler/src/pages/buildFunctions.ts | 2 +- packages/wrangler/src/pages/dev.ts | 5 +- .../src/pages/functions/buildPlugin.ts | 1 - .../src/pages/functions/buildWorker.ts | 4 +- .../wrangler/src/type-generation/index.ts | 13 +- packages/wrangler/src/versions/upload.ts | 5 +- 31 files changed, 451 insertions(+), 220 deletions(-) create mode 100644 .changeset/early-tigers-buy.md create mode 100644 .changeset/tender-items-exercise.md create mode 100644 packages/miniflare/src/plugins/core/node-compat.ts create mode 100644 packages/wrangler/src/__tests__/helpers/normalize.ts diff --git a/.changeset/early-tigers-buy.md b/.changeset/early-tigers-buy.md new file mode 100644 index 000000000000..fd1edfad238a --- /dev/null +++ b/.changeset/early-tigers-buy.md @@ -0,0 +1,7 @@ +--- +"wrangler": patch +--- + +fix: tidy up error messaging for unexpected use of Node.js APIs + +Fixes #6822 diff --git a/.changeset/tender-items-exercise.md b/.changeset/tender-items-exercise.md new file mode 100644 index 000000000000..caa27aae2f3d --- /dev/null +++ b/.changeset/tender-items-exercise.md @@ -0,0 +1,7 @@ +--- +"miniflare": patch +--- + +fix: teach Miniflare about node_compat version date switch + +A compatibility of Sept 23, 2024 or later means that `nodejs_compat` is equivalent to `nodejs_compat_v2`. diff --git a/fixtures/nodejs-hybrid-app/wrangler.toml b/fixtures/nodejs-hybrid-app/wrangler.toml index c71823b5dba6..5accaab35a2e 100644 --- a/fixtures/nodejs-hybrid-app/wrangler.toml +++ b/fixtures/nodejs-hybrid-app/wrangler.toml @@ -1,7 +1,8 @@ name = "nodejs-hybrid-app" main = "src/index.ts" -compatibility_date = "2024-06-03" -compatibility_flags = ["nodejs_compat_v2"] +# Setting compat date to 2024/09/23 means we don't need to use `nodejs_compat_v2` +compatibility_date = "2024-09-23" +compatibility_flags = ["nodejs_compat"] [vars] # These DB connection values are to a public database containing information about diff --git a/packages/miniflare/src/plugins/core/index.ts b/packages/miniflare/src/plugins/core/index.ts index c03752d7688f..95c2fd823861 100644 --- a/packages/miniflare/src/plugins/core/index.ts +++ b/packages/miniflare/src/plugins/core/index.ts @@ -807,7 +807,10 @@ export function getGlobalServices({ } function getWorkerScript( - options: SourceOptions & { compatibilityFlags?: string[] }, + options: SourceOptions & { + compatibilityDate?: string; + compatibilityFlags?: string[]; + }, workerIndex: number, additionalModuleNames: string[] ): { serviceWorkerScript: string } | { modules: Worker_Module[] } { @@ -843,6 +846,7 @@ function getWorkerScript( modulesRoot, additionalModuleNames, options.modulesRules, + options.compatibilityDate, options.compatibilityFlags ); // If `script` and `scriptPath` are set, resolve modules in `script` @@ -862,3 +866,4 @@ export * from "./proxy"; export * from "./constants"; export * from "./modules"; export * from "./services"; +export * from "./node-compat"; diff --git a/packages/miniflare/src/plugins/core/modules.ts b/packages/miniflare/src/plugins/core/modules.ts index bc9d1ffb02f6..669dc84489d4 100644 --- a/packages/miniflare/src/plugins/core/modules.ts +++ b/packages/miniflare/src/plugins/core/modules.ts @@ -11,6 +11,7 @@ import { z } from "zod"; import { Worker_Module } from "../../runtime"; import { globsToRegExps, MiniflareCoreError, PathSchema } from "../../shared"; import { MatcherRegExps, testRegExps } from "../../workers"; +import { getNodeCompat, NodeJSCompatMode } from "./node-compat"; import type estree from "estree"; const SUGGEST_BUNDLE = @@ -156,7 +157,7 @@ function getResolveErrorPrefix(referencingPath: string): string { export class ModuleLocator { readonly #compiledRules: CompiledModuleRule[]; - readonly #nodejsCompat: boolean; + readonly #nodejsCompatMode: NodeJSCompatMode; readonly #visitedPaths = new Set(); readonly modules: Worker_Module[] = []; @@ -164,15 +165,16 @@ export class ModuleLocator { private readonly modulesRoot: string, private readonly additionalModuleNames: string[], rules: ModuleRule[] = [], + compatibilityDate?: string, compatibilityFlags?: string[] ) { // Implicit shallow-copy to avoid mutating argument rules = rules.concat(DEFAULT_MODULE_RULES); this.#compiledRules = compileModuleRules(rules); - // `nodejs_compat` doesn't have a default-on date, so we know whether it's - // enabled just by looking at flags: - // https://github.com/cloudflare/workerd/blob/edcd0300bc7b8f56040d090177db947edd22f91b/src/workerd/io/compatibility-date.capnp#L237-L240 - this.#nodejsCompat = compatibilityFlags?.includes("nodejs_compat") ?? false; + this.#nodejsCompatMode = getNodeCompat( + compatibilityDate, + compatibilityFlags ?? [] + ).mode; } visitEntrypoint(code: string, modulePath: string) { @@ -289,14 +291,19 @@ ${dim(modulesConfig)}`; } const spec = specExpression.value; - // `node:` (assuming `nodejs_compat` flag enabled), `cloudflare:` and - // `workerd:` imports don't need to be included explicitly const isNodeJsCompatModule = referencingType === "NodeJsCompatModule"; if ( - (this.#nodejsCompat && spec.startsWith("node:")) || + // `cloudflare:` and `workerd:` imports don't need to be included explicitly spec.startsWith("cloudflare:") || spec.startsWith("workerd:") || - (isNodeJsCompatModule && builtinModulesWithPrefix.includes(spec)) || + // Node.js compat v1 requires imports to be prefixed with `node:` + (this.#nodejsCompatMode === "v1" && spec.startsWith("node:")) || + // Node.js compat modules and v2 can also handle non-prefixed imports + ((this.#nodejsCompatMode === "v2" || isNodeJsCompatModule) && + builtinModulesWithPrefix.includes(spec)) || + // Async Local Storage mode (node_als) only deals with `node:async_hooks` imports + (this.#nodejsCompatMode === "als" && spec === "node:async_hooks") || + // Any "additional" external modules can be ignored this.additionalModuleNames.includes(spec) ) { return; diff --git a/packages/miniflare/src/plugins/core/node-compat.ts b/packages/miniflare/src/plugins/core/node-compat.ts new file mode 100644 index 000000000000..5c3a532756a2 --- /dev/null +++ b/packages/miniflare/src/plugins/core/node-compat.ts @@ -0,0 +1,78 @@ +/** + * We can provide Node.js compatibility in a number of different modes: + * - "legacy" - this mode adds compile-time polyfills that are not well maintained and cannot work with workerd runtime builtins. + * - "als": this mode tells the workerd runtime to enable only the Async Local Storage builtin library (accessible via `node:async_hooks`). + * - "v1" - this mode tells the workerd runtime to enable some Node.js builtin libraries (accessible only via `node:...` imports) but no globals. + * - "v2" - this mode tells the workerd runtime to enable more Node.js builtin libraries (accessible both with and without the `node:` prefix) + * and also some Node.js globals such as `Buffer`; it also turns on additional compile-time polyfills for those that are not provided by the runtime. + * - null - no Node.js compatibility. + */ +export type NodeJSCompatMode = "legacy" | "als" | "v1" | "v2" | null; + +/** + * Computes the Node.js compatibility mode we are running. + * + * NOTES: + * - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` plus a compatibility date of Sept 23rd. 2024 or later. + * - See `EnvironmentInheritable` for `nodeCompat` and `noBundle`. + * + * @param compatibilityDateStr The compatibility date + * @param compatibilityFlags The compatibility flags + * @param opts.nodeCompat Whether the legacy node_compat arg is being used + * @returns the mode and flags to indicate specific configuration for validating. + */ +export function getNodeCompat( + compatibilityDate: string = "2000-01-01", // Default to some arbitrary old date + compatibilityFlags: string[], + opts?: { + nodeCompat?: boolean; + } +) { + const { nodeCompat = false } = opts ?? {}; + const { + hasNodejsAlsFlag, + hasNodejsCompatFlag, + hasNodejsCompatV2Flag, + hasNoNodejsCompatV2Flag, + hasExperimentalNodejsCompatV2Flag, + } = parseNodeCompatibilityFlags(compatibilityFlags); + + const nodeCompatSwitchOverDate = "2024-09-23"; + const legacy = nodeCompat === true; + let mode: NodeJSCompatMode = null; + if ( + hasNodejsCompatV2Flag || + (hasNodejsCompatFlag && + compatibilityDate >= nodeCompatSwitchOverDate && + !hasNoNodejsCompatV2Flag) + ) { + mode = "v2"; + } else if (hasNodejsCompatFlag) { + mode = "v1"; + } else if (hasNodejsAlsFlag) { + mode = "als"; + } else if (legacy) { + mode = "legacy"; + } + + return { + mode, + hasNodejsAlsFlag, + hasNodejsCompatFlag, + hasNodejsCompatV2Flag, + hasNoNodejsCompatV2Flag, + hasExperimentalNodejsCompatV2Flag, + }; +} + +function parseNodeCompatibilityFlags(compatibilityFlags: string[]) { + return { + hasNodejsAlsFlag: compatibilityFlags.includes("nodejs_als"), + hasNodejsCompatFlag: compatibilityFlags.includes("nodejs_compat"), + hasNodejsCompatV2Flag: compatibilityFlags.includes("nodejs_compat_v2"), + hasNoNodejsCompatV2Flag: compatibilityFlags.includes("no_nodejs_compat_v2"), + hasExperimentalNodejsCompatV2Flag: compatibilityFlags.includes( + "experimental:nodejs_compat_v2" + ), + }; +} diff --git a/packages/miniflare/src/plugins/index.ts b/packages/miniflare/src/plugins/index.ts index 3673ea7bcbf7..7175d6ebe9b0 100644 --- a/packages/miniflare/src/plugins/index.ts +++ b/packages/miniflare/src/plugins/index.ts @@ -103,6 +103,7 @@ export { ProxyClient, getFreshSourceMapSupport, kCurrentWorker, + getNodeCompat, } from "./core"; export type { CompiledModuleRule, @@ -111,6 +112,7 @@ export type { ModuleDefinition, GlobalServicesOptions, SourceOptions, + NodeJSCompatMode, } from "./core"; export type * from "./core/proxy/types"; export * from "./d1"; diff --git a/packages/wrangler/src/__tests__/configuration.test.ts b/packages/wrangler/src/__tests__/configuration.test.ts index fa0912fc2912..07709626448f 100644 --- a/packages/wrangler/src/__tests__/configuration.test.ts +++ b/packages/wrangler/src/__tests__/configuration.test.ts @@ -1,7 +1,7 @@ import path from "node:path"; import { readConfig } from "../config"; import { normalizeAndValidateConfig } from "../config/validation"; -import { normalizeSlashes } from "./helpers/mock-console"; +import { normalizeString } from "./helpers/normalize"; import { runInTempDir } from "./helpers/run-in-tmp"; import { writeWranglerToml } from "./helpers/write-wrangler-toml"; import type { @@ -301,7 +301,7 @@ describe("normalizeAndValidateConfig()", () => { expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(true); - expect(normalizeSlashes(diagnostics.renderWarnings())) + expect(normalizeString(diagnostics.renderWarnings())) .toMatchInlineSnapshot(` "Processing wrangler configuration: - Deprecation: \\"site.entry-point\\": @@ -332,7 +332,7 @@ describe("normalizeAndValidateConfig()", () => { expect(diagnostics.hasWarnings()).toBe(true); expect(diagnostics.hasErrors()).toBe(true); - expect(normalizeSlashes(diagnostics.renderWarnings())) + expect(normalizeString(diagnostics.renderWarnings())) .toMatchInlineSnapshot(` "Processing wrangler configuration: - Deprecation: \\"site.entry-point\\": @@ -375,7 +375,7 @@ describe("normalizeAndValidateConfig()", () => { - Expected \\"site.entry-point\\" to be of type string but got 111." `); - expect(normalizeSlashes(diagnostics.renderWarnings())) + expect(normalizeString(diagnostics.renderWarnings())) .toMatchInlineSnapshot(` "Processing wrangler configuration: - Deprecation: \\"site.entry-point\\": @@ -409,7 +409,7 @@ describe("normalizeAndValidateConfig()", () => { expect(diagnostics.hasWarnings()).toBe(true); expect(diagnostics.hasErrors()).toBe(false); - expect(normalizeSlashes(diagnostics.renderWarnings())) + expect(normalizeString(diagnostics.renderWarnings())) .toMatchInlineSnapshot(` "Processing wrangler configuration: - Deprecation: \\"site.entry-point\\": @@ -881,7 +881,7 @@ describe("normalizeAndValidateConfig()", () => { expect(diagnostics.hasErrors()).toBe(false); expect(diagnostics.hasWarnings()).toBe(true); - expect(normalizeSlashes(diagnostics.renderWarnings())) + expect(normalizeString(diagnostics.renderWarnings())) .toMatchInlineSnapshot(` "Processing project/wrangler.toml configuration: - \\"unsafe\\" fields are experimental and may change or break at any time. diff --git a/packages/wrangler/src/__tests__/deploy.test.ts b/packages/wrangler/src/__tests__/deploy.test.ts index 5111188eed90..c2abede00275 100644 --- a/packages/wrangler/src/__tests__/deploy.test.ts +++ b/packages/wrangler/src/__tests__/deploy.test.ts @@ -18,11 +18,7 @@ import { logger } from "../logger"; import { writeAuthConfigFile } from "../user"; import { mockAccountId, mockApiToken } from "./helpers/mock-account-id"; import { mockAuthDomain } from "./helpers/mock-auth-domain"; -import { - mockConsoleMethods, - normalizeSlashes, - normalizeTempDirs, -} from "./helpers/mock-console"; +import { mockConsoleMethods } from "./helpers/mock-console"; import { clearDialogs, mockConfirm } from "./helpers/mock-dialogs"; import { mockGetZoneFromHostRequest } from "./helpers/mock-get-zone-from-host"; import { useMockIsTTY } from "./helpers/mock-istty"; @@ -45,6 +41,7 @@ import { mswSuccessUserHandlers, } from "./helpers/msw"; import { mswListNewDeploymentsLatestFull } from "./helpers/msw/handlers/versions"; +import { normalizeString } from "./helpers/normalize"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import { writeWorkerSource } from "./helpers/write-worker-source"; @@ -2053,7 +2050,7 @@ addEventListener('fetch', event => {});` " `); - expect(normalizeSlashes(std.warn)).toMatchInlineSnapshot(` + expect(normalizeString(std.warn)).toMatchInlineSnapshot(` "▲ [WARNING] Processing wrangler.toml configuration: - Because you've defined a [site] configuration, we're defaulting to \\"workers-site\\" for the @@ -2169,7 +2166,7 @@ addEventListener('fetch', event => {});` Current Version ID: Galaxy-Class" `); expect(std.err).toMatchInlineSnapshot(`""`); - expect(normalizeSlashes(std.warn)).toMatchInlineSnapshot(` + expect(normalizeString(std.warn)).toMatchInlineSnapshot(` "▲ [WARNING] Processing my-site/wrangler.toml configuration: - Deprecation: \\"site.entry-point\\": @@ -5679,8 +5676,8 @@ addEventListener('fetch', event => {});` mockUploadWorkerRequest(); await runWrangler("build"); - const outFile = normalizeSlashes( - normalizeTempDirs(fs.readFileSync("dist/index.js", "utf-8")) + const outFile = normalizeString( + fs.readFileSync("dist/index.js", "utf-8") ); // We don't check against the whole file as there is middleware being injected @@ -5715,8 +5712,8 @@ addEventListener('fetch', event => {});` mockUploadWorkerRequest(); await runWrangler("build --env staging"); - const outFile = normalizeSlashes( - normalizeTempDirs(fs.readFileSync("dist/index.js", "utf-8")) + const outFile = normalizeString( + fs.readFileSync("dist/index.js", "utf-8") ); // We don't check against the whole file as there is middleware being injected @@ -9256,27 +9253,139 @@ export default{ `); }); - it("should recommend node compatibility mode when using node builtins and node-compat isn't enabled", async () => { + it("should recommend node compatibility flag when using node builtins and no node compat is enabled", async () => { writeWranglerToml(); - fs.writeFileSync( - "index.js", - ` - import path from 'path'; - console.log(path.join("some/path/to", "a/file.txt")); - export default {} - ` - ); - let err: esbuild.BuildFailure | undefined; - try { - await runWrangler("deploy index.js --dry-run"); // expecting this to throw, as node compatibility isn't enabled - } catch (e) { - err = e as esbuild.BuildFailure; - } - expect( - esbuild.formatMessagesSync(err?.errors ?? [], { kind: "error" }).join() - ).toMatch( - /The package "path" wasn't found on the file system but is built into node\.\s+Add "node_compat = true" to your wrangler\.toml file and make sure to prefix the module name with "node:" to enable Node.js compatibility\./ - ); + fs.writeFileSync("index.js", "import path from 'path';"); + + await expect( + runWrangler("deploy index.js --dry-run").catch((e) => + normalizeString( + esbuild + .formatMessagesSync(e?.errors ?? [], { kind: "error" }) + .join() + .trim() + ) + ) + ).resolves.toMatchInlineSnapshot(` + "X [ERROR] Could not resolve \\"path\\" + + index.js:1:17: + 1 │ import path from 'path'; + ╵ ~~~~~~ + + The package \\"path\\" wasn't found on the file system but is built into node. + - Add the \\"nodejs_compat\\" compatibility flag to your project." + `); + }); + + it("should recommend node compatibility flag when using node builtins and node compat is set only to nodejs_als", async () => { + writeWranglerToml({ + compatibility_flags: ["nodejs_als"], + }); + fs.writeFileSync("index.js", "import path from 'path';"); + + await expect( + runWrangler("deploy index.js --dry-run").catch((e) => + normalizeString( + esbuild + .formatMessagesSync(e?.errors ?? [], { kind: "error" }) + .join() + .trim() + ) + ) + ).resolves.toMatchInlineSnapshot(` + "X [ERROR] Could not resolve \\"path\\" + + index.js:1:17: + 1 │ import path from 'path'; + ╵ ~~~~~~ + + The package \\"path\\" wasn't found on the file system but is built into node. + - Add the \\"nodejs_compat\\" compatibility flag to your project." + `); + }); + + it("should recommend node compatibility flag when using node builtins and `node_compat` is true", async () => { + writeWranglerToml({ + node_compat: true, + }); + fs.writeFileSync("index.js", "import fs from 'diagnostics_channel';"); + + await expect( + runWrangler("deploy index.js --dry-run").catch((e) => + normalizeString( + esbuild + .formatMessagesSync(e?.errors ?? [], { kind: "error" }) + .join() + .trim() + ) + ) + ).resolves.toMatchInlineSnapshot(` + "X [ERROR] Could not resolve \\"diagnostics_channel\\" + + index.js:1:15: + 1 │ import fs from 'diagnostics_channel'; + ╵ ~~~~~~~~~~~~~~~~~~~~~ + + The package \\"diagnostics_channel\\" wasn't found on the file system but is built into node. + - Try removing the legacy \\"node_compat\\" setting and add the \\"nodejs_compat\\" compatibility flag in your project" + `); + }); + + it("should recommend updating the compatibility date when using node builtins and the `nodejs_compat` flag", async () => { + writeWranglerToml({ + compatibility_date: "2024-09-01", // older than Sept 23rd, 2024 + compatibility_flags: ["nodejs_compat"], + }); + fs.writeFileSync("index.js", "import fs from 'path';"); + + await expect( + runWrangler("deploy index.js --dry-run").catch((e) => + normalizeString( + esbuild + .formatMessagesSync(e?.errors ?? [], { kind: "error" }) + .join() + .trim() + ) + ) + ).resolves.toMatchInlineSnapshot(` + "X [ERROR] Could not resolve \\"path\\" + + index.js:1:15: + 1 │ import fs from 'path'; + ╵ ~~~~~~ + + The package \\"path\\" wasn't found on the file system but is built into node. + - Make sure to prefix the module name with \\"node:\\" or update your compatibility_date to 2024-09-23 or later." + `); + }); + + it("should recommend updating the compatibility date flag when using no_nodejs_compat and non-prefixed node builtins", async () => { + writeWranglerToml({ + compatibility_date: "2024-09-23", + compatibility_flags: ["nodejs_compat", "no_nodejs_compat_v2"], + }); + fs.writeFileSync("index.js", "import fs from 'path';"); + + await expect( + runWrangler("deploy index.js --dry-run").catch((e) => + normalizeString( + esbuild + .formatMessagesSync(e?.errors ?? [], { kind: "error" }) + .join() + .trim() + ) + ) + ).resolves.toMatchInlineSnapshot(` + "X [ERROR] Could not resolve \\"path\\" + + index.js:1:15: + 1 │ import fs from 'path'; + ╵ ~~~~~~ + + The package \\"path\\" wasn't found on the file system but is built into node. + - Make sure to prefix the module name with \\"node:\\" or update your compatibility_date to 2024-09-23 or later." + `); }); it("should polyfill node builtins when enabled", async () => { @@ -9330,13 +9439,45 @@ export default{ `); }); - it('when present, should support any "external" `node:*` imports', async () => { + it('when present, should support "external" `node:*` imports', async () => { writeWranglerToml(); fs.writeFileSync( "index.js", ` - import AsyncHooks from 'node:async_hooks'; - console.log(AsyncHooks); + import path from 'node:path'; + console.log(path); + export default {} + ` + ); + + await runWrangler( + "deploy index.js --dry-run --outdir=dist --compatibility-flag=nodejs_compat" + ); + + expect(std).toMatchInlineSnapshot(` + Object { + "debug": "", + "err": "", + "info": "", + "out": "Total Upload: xx KiB / gzip: xx KiB + --dry-run: exiting now.", + "warn": "", + } + `); + expect(fs.readFileSync("dist/index.js", { encoding: "utf-8" })).toContain( + `import path from "node:path";` + ); + }); + + it(`when present, and compat date is on or after 2024-09-23, should support "external" non-prefixed node imports`, async () => { + writeWranglerToml({ + compatibility_date: "2024-09-23", + }); + fs.writeFileSync( + "index.js", + ` + import path from 'path'; + console.log(path); export default {} ` ); @@ -9356,7 +9497,7 @@ export default{ } `); expect(fs.readFileSync("dist/index.js", { encoding: "utf-8" })).toContain( - `import AsyncHooks from "node:async_hooks";` + `import path from "path";` ); }); diff --git a/packages/wrangler/src/__tests__/helpers/mock-console.ts b/packages/wrangler/src/__tests__/helpers/mock-console.ts index fe115f4993a1..3e559f5c0dab 100644 --- a/packages/wrangler/src/__tests__/helpers/mock-console.ts +++ b/packages/wrangler/src/__tests__/helpers/mock-console.ts @@ -1,6 +1,7 @@ import * as util from "node:util"; import { afterEach, beforeEach, vi } from "vitest"; import { logger } from "../../logger"; +import { normalizeString } from "./normalize"; import type { MockInstance } from "vitest"; /** @@ -33,13 +34,7 @@ const std = { }; function normalizeOutput(spy: MockInstance): string { - return normalizeErrorMarkers( - replaceByte( - stripTrailingWhitespace( - normalizeSlashes(normalizeTempDirs(stripTimings(captureCalls(spy)))) - ) - ) - ); + return normalizeString(captureCalls(spy)); } function captureCalls(spy: MockInstance): string { @@ -66,49 +61,3 @@ export function mockConsoleMethods() { }); return std; } - -/** - * Normalize error `X` markers. - * - * Windows gets a different character. - */ -function normalizeErrorMarkers(str: string): string { - return str.replaceAll("✘", "X"); -} - -/** - * Ensure slashes in the `str` are OS file-system agnostic. - * - * Use this in snapshot tests to be resilient to file-system differences. - */ -export function normalizeSlashes(str: string): string { - return str.replace(/\\/g, "/"); -} - -/** - * Strip "timing data" out of the `stdout` string, since this is not always deterministic. - * - * Use this in snapshot tests to be resilient to slight changes in timing of processing. - */ -export function stripTimings(stdout: string): string { - return stdout.replace(/\(\d+\.\d+ sec\)/g, "(TIMINGS)"); -} - -export function stripTrailingWhitespace(str: string): string { - return str.replace(/[^\S\n]+\n/g, "\n"); -} - -/** - * Removing leading kilobit (tenth of a byte) from test output due to - * variation causing every few tests the value to change by Âħ .01 - */ -function replaceByte(stdout: string): string { - return stdout.replaceAll(/\d+\.\d+ KiB/g, "xx KiB"); -} - -/** - * Temp directories are created with random names, so we replace all comments temp dirs in them - */ -export function normalizeTempDirs(stdout: string): string { - return stdout.replaceAll(/\/\/.+\/tmp.+/g, "//tmpdir"); -} diff --git a/packages/wrangler/src/__tests__/helpers/normalize.ts b/packages/wrangler/src/__tests__/helpers/normalize.ts new file mode 100644 index 000000000000..b07664de31b7 --- /dev/null +++ b/packages/wrangler/src/__tests__/helpers/normalize.ts @@ -0,0 +1,58 @@ +/** + * Normalize the input string, to make it reliable to use in tests. + */ +export function normalizeString(input: string): string { + return normalizeErrorMarkers( + replaceByte( + stripTrailingWhitespace( + normalizeSlashes(normalizeTempDirs(stripTimings(input))) + ) + ) + ); +} + +/** + * Normalize error `X` markers. + * + * Windows gets a different character. + */ +function normalizeErrorMarkers(str: string): string { + return str.replaceAll("✘", "X"); +} + +/** + * Ensure slashes in the `str` are OS file-system agnostic. + * + * Use this in snapshot tests to be resilient to file-system differences. + */ +function normalizeSlashes(str: string): string { + return str.replace(/\\/g, "/"); +} + +/** + * Strip "timing data" out of the `stdout` string, since this is not always deterministic. + * + * Use this in snapshot tests to be resilient to slight changes in timing of processing. + */ +function stripTimings(stdout: string): string { + return stdout.replace(/\(\d+\.\d+ sec\)/g, "(TIMINGS)"); +} + +function stripTrailingWhitespace(str: string): string { + return str.replace(/[^\S\n]+\n/g, "\n"); +} + +/** + * Removing leading kilobit (tenth of a byte) from test output due to + * variation causing every few tests the value to change by Âħ .01 + */ +function replaceByte(stdout: string): string { + return stdout.replaceAll(/\d+\.\d+ KiB/g, "xx KiB"); +} + +/** + * Temp directories are created with random names, so we replace all comments temp dirs in them + */ +function normalizeTempDirs(stdout: string): string { + return stdout.replaceAll(/\/\/.+\/tmp.+/g, "//tmpdir"); +} diff --git a/packages/wrangler/src/__tests__/helpers/run-wrangler.ts b/packages/wrangler/src/__tests__/helpers/run-wrangler.ts index d189832401a6..6aa918377380 100644 --- a/packages/wrangler/src/__tests__/helpers/run-wrangler.ts +++ b/packages/wrangler/src/__tests__/helpers/run-wrangler.ts @@ -1,6 +1,6 @@ import { main } from "../../index"; import * as shellquote from "../../utils/shell-quote"; -import { normalizeSlashes, stripTimings } from "./mock-console"; +import { normalizeString } from "./normalize"; /** * A helper to 'run' wrangler commands for tests. @@ -16,7 +16,7 @@ export async function runWrangler( await main(argv); } catch (err) { if (err instanceof Error) { - err.message = normalizeSlashes(stripTimings(err.message)); + err.message = normalizeString(err.message); } throw err; } finally { diff --git a/packages/wrangler/src/__tests__/user.test.ts b/packages/wrangler/src/__tests__/user.test.ts index 8381c909c72a..24d9be6ab064 100644 --- a/packages/wrangler/src/__tests__/user.test.ts +++ b/packages/wrangler/src/__tests__/user.test.ts @@ -9,7 +9,7 @@ import { requireAuth, writeAuthConfigFile, } from "../user"; -import { mockConsoleMethods, normalizeSlashes } from "./helpers/mock-console"; +import { mockConsoleMethods } from "./helpers/mock-console"; import { useMockIsTTY } from "./helpers/mock-istty"; import { mockExchangeRefreshTokenForAccessToken, @@ -20,6 +20,7 @@ import { mswSuccessOauthHandlers, mswSuccessUserHandlers, } from "./helpers/msw"; +import { normalizeString } from "./helpers/normalize"; import { runInTempDir } from "./helpers/run-in-tmp"; import { runWrangler } from "./helpers/run-wrangler"; import type { Config } from "../config"; @@ -109,8 +110,8 @@ describe("User", () => { Successfully logged in." `); - expect(normalizeSlashes(getAuthConfigFilePath())).toBe( - normalizeSlashes(`${getGlobalWranglerConfigPath()}/config/staging.toml`) + expect(normalizeString(getAuthConfigFilePath())).toBe( + normalizeString(`${getGlobalWranglerConfigPath()}/config/staging.toml`) ); expect(readAuthConfigFile()).toEqual({ api_token: undefined, @@ -174,8 +175,8 @@ describe("User", () => { refresh_token: "Order 66", }); - expect(normalizeSlashes(getAuthConfigFilePath())).toBe( - normalizeSlashes(`${getGlobalWranglerConfigPath()}/config/staging.toml`) + expect(normalizeString(getAuthConfigFilePath())).toBe( + normalizeString(`${getGlobalWranglerConfigPath()}/config/staging.toml`) ); }); }); diff --git a/packages/wrangler/src/api/pages/deploy.tsx b/packages/wrangler/src/api/pages/deploy.tsx index b1e270881b95..e8b3333f294f 100644 --- a/packages/wrangler/src/api/pages/deploy.tsx +++ b/packages/wrangler/src/api/pages/deploy.tsx @@ -6,7 +6,7 @@ import { cwd } from "node:process"; import { File, FormData } from "undici"; import { fetchResult } from "../../cfetch"; import { readConfig } from "../../config"; -import { getNodeCompatMode } from "../../deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "../../deployment-bundle/node-compat"; import { FatalError } from "../../errors"; import { logger } from "../../logger"; import { isNavigatorDefined } from "../../navigator-user-agent"; @@ -174,7 +174,8 @@ export async function deploy({ } } - const nodejsCompatMode = getNodeCompatMode( + const nodejsCompatMode = validateNodeCompatMode( + config?.compatibility_date ?? deploymentConfig.compatibility_date, config?.compatibility_flags ?? deploymentConfig.compatibility_flags ?? [], { nodeCompat: false, diff --git a/packages/wrangler/src/api/startDevWorker/types.ts b/packages/wrangler/src/api/startDevWorker/types.ts index 433b4b4f2ba7..db760a95627f 100644 --- a/packages/wrangler/src/api/startDevWorker/types.ts +++ b/packages/wrangler/src/api/startDevWorker/types.ts @@ -7,7 +7,6 @@ import type { ZoneIdRoute, ZoneNameRoute, } from "../../config/environment"; -import type { NodeJSCompatMode } from "../../deployment-bundle/node-compat"; import type { CfAnalyticsEngineDataset, CfD1Database, @@ -35,6 +34,7 @@ import type { DispatchFetch, Json, Miniflare, + NodeJSCompatMode, Request, Response, } from "miniflare"; diff --git a/packages/wrangler/src/deploy/deploy.ts b/packages/wrangler/src/deploy/deploy.ts index 2c3c4bc21b18..4f355826ac7d 100644 --- a/packages/wrangler/src/deploy/deploy.ts +++ b/packages/wrangler/src/deploy/deploy.ts @@ -22,7 +22,7 @@ import { createModuleCollector, getWrangler1xLegacyModuleReferences, } from "../deployment-bundle/module-collection"; -import { getNodeCompatMode } from "../deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "../deployment-bundle/node-compat"; import { loadSourceMaps } from "../deployment-bundle/source-maps"; import { confirm } from "../dialogs"; import { getMigrationsToUpload } from "../durable"; @@ -440,12 +440,18 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m const minify = props.minify ?? config.minify; + const compatibilityDate = + props.compatibilityDate ?? config.compatibility_date; const compatibilityFlags = props.compatibilityFlags ?? config.compatibility_flags; - const nodejsCompatMode = getNodeCompatMode(compatibilityFlags, { - nodeCompat: props.nodeCompat ?? config.node_compat, - noBundle: props.noBundle ?? config.no_bundle, - }); + const nodejsCompatMode = validateNodeCompatMode( + compatibilityDate, + compatibilityFlags, + { + nodeCompat: props.nodeCompat ?? config.node_compat, + noBundle: props.noBundle ?? config.no_bundle, + } + ); // Warn if user tries minify with no-bundle if (props.noBundle && minify) { diff --git a/packages/wrangler/src/deployment-bundle/build-failures.ts b/packages/wrangler/src/deployment-bundle/build-failures.ts index 11a2bd23c2c0..3d1c508a6188 100644 --- a/packages/wrangler/src/deployment-bundle/build-failures.ts +++ b/packages/wrangler/src/deployment-bundle/build-failures.ts @@ -1,5 +1,6 @@ import { builtinModules } from "node:module"; import type * as esbuild from "esbuild"; +import type { NodeJSCompatMode } from "miniflare"; /** * RegExp matching against esbuild's error text when it is unable to resolve @@ -19,23 +20,25 @@ const nodeBuiltinResolveErrorText = new RegExp( */ export function rewriteNodeCompatBuildFailure( errors: esbuild.Message[], - forPages = false + compatMode: NodeJSCompatMode = null ) { for (const error of errors) { const match = nodeBuiltinResolveErrorText.exec(error.text); if (match !== null) { - const issue = `The package "${match[1]}" wasn't found on the file system but is built into node.`; + let text = `The package "${match[1]}" wasn't found on the file system but is built into node.\n`; - const instructionForUser = `${ - forPages - ? 'Add the "nodejs_compat" compatibility flag to your Pages project' - : 'Add "node_compat = true" to your wrangler.toml file' - } and make sure to prefix the module name with "node:" to enable Node.js compatibility.`; + if (compatMode === null || compatMode === "als") { + text += `- Add the "nodejs_compat" compatibility flag to your project.\n`; + } else if (compatMode === "legacy") { + text += `- Try removing the legacy "node_compat" setting and add the "nodejs_compat" compatibility flag in your project\n`; + } else if (compatMode === "v1" && !match[1].startsWith("node:")) { + text += `- Make sure to prefix the module name with "node:" or update your compatibility_date to 2024-09-23 or later.\n`; + } error.notes = [ { location: null, - text: `${issue}\n${instructionForUser}`, + text, }, ]; } diff --git a/packages/wrangler/src/deployment-bundle/bundle.ts b/packages/wrangler/src/deployment-bundle/bundle.ts index 24765fc39379..b676e906d87e 100644 --- a/packages/wrangler/src/deployment-bundle/bundle.ts +++ b/packages/wrangler/src/deployment-bundle/bundle.ts @@ -29,8 +29,8 @@ import type { DurableObjectBindings } from "../config/environment"; import type { MiddlewareLoader } from "./apply-middleware"; import type { Entry } from "./entry"; import type { ModuleCollector } from "./module-collection"; -import type { NodeJSCompatMode } from "./node-compat"; import type { CfModule, CfModuleType } from "./worker"; +import type { NodeJSCompatMode } from "miniflare"; // Taken from https://stackoverflow.com/a/3561711 // which is everything from the tc39 proposal, plus the following two characters: ^/ @@ -133,7 +133,6 @@ export type BundleOptions = { sourcemap?: esbuild.CommonOptions["sourcemap"]; plugins?: esbuild.Plugin[]; isOutfile?: boolean; - forPages?: boolean; local: boolean; projectRoot: string | undefined; defineNavigatorUserAgent: boolean; @@ -172,7 +171,6 @@ export async function bundleWorker( sourcemap, plugins, isOutfile, - forPages, local, projectRoot, defineNavigatorUserAgent, @@ -485,8 +483,8 @@ export async function bundleWorker( }; } } catch (e) { - if (nodejsCompatMode !== "legacy" && isBuildFailure(e)) { - rewriteNodeCompatBuildFailure(e.errors, forPages); + if (isBuildFailure(e)) { + rewriteNodeCompatBuildFailure(e.errors, nodejsCompatMode); } throw e; } diff --git a/packages/wrangler/src/deployment-bundle/esbuild-plugins/log-build-output.ts b/packages/wrangler/src/deployment-bundle/esbuild-plugins/log-build-output.ts index c59497588a9b..783f7cdca6cc 100644 --- a/packages/wrangler/src/deployment-bundle/esbuild-plugins/log-build-output.ts +++ b/packages/wrangler/src/deployment-bundle/esbuild-plugins/log-build-output.ts @@ -1,7 +1,7 @@ import { logBuildFailure, logBuildWarnings } from "../../logger"; import { rewriteNodeCompatBuildFailure } from "../build-failures"; -import type { NodeJSCompatMode } from "../node-compat"; import type { Plugin } from "esbuild"; +import type { NodeJSCompatMode } from "miniflare"; /** * Log esbuild warnings and errors @@ -22,7 +22,7 @@ export function logBuildOutput( build.onEnd(async ({ errors, warnings }) => { if (errors.length > 0) { if (nodejsCompatMode !== "legacy") { - rewriteNodeCompatBuildFailure(errors); + rewriteNodeCompatBuildFailure(errors, nodejsCompatMode); } logBuildFailure(errors, warnings); return; diff --git a/packages/wrangler/src/deployment-bundle/node-compat.ts b/packages/wrangler/src/deployment-bundle/node-compat.ts index d5a0baa04dd8..ed2893e48f9c 100644 --- a/packages/wrangler/src/deployment-bundle/node-compat.ts +++ b/packages/wrangler/src/deployment-bundle/node-compat.ts @@ -1,71 +1,40 @@ +import { getNodeCompat } from "miniflare"; import { UserError } from "../errors"; import { logger } from "../logger"; +import type { NodeJSCompatMode } from "miniflare"; /** - * Wrangler can provide Node.js compatibility in a number of different modes: - * - "legacy" - this mode adds compile-time polyfills that are not well maintained and cannot work with workerd runtime builtins. - * - "als": this mode tells the workerd runtime to enable only the Async Local Storage builtin library (accessible via `node:async_hooks`). - * - "v1" - this mode tells the workerd runtime to enable some Node.js builtin libraries (accessible only via `node:...` imports) but no globals. - * - "v2" - this mode tells the workerd runtime to enable more Node.js builtin libraries (accessible both with and without the `node:` prefix) - * and also some Node.js globals such as `Buffer`; it also turns on additional compile-time polyfills for those that are not provided by the runtime. - */ -export type NodeJSCompatMode = "legacy" | "als" | "v1" | "v2" | null; - -/** - * Computes the Node.js compatibility mode we are running. - * - * NOTE: - * Currently v2 mode is configured via `nodejs_compat_v2` compat flag. - * At a future compatibility date, the use of `nodejs_compat` flag will imply `nodejs_compat_v2`. + * Computes and validates the Node.js compatibility mode we are running. * - * see `EnvironmentInheritable` for `nodeCompat` and `noBundle`. + * NOTES: + * - The v2 mode is configured via `nodejs_compat_v2` compat flag or via `nodejs_compat` plus a compatibility date of Sept 23rd. 2024 or later. + * - See `EnvironmentInheritable` for `nodeCompat` and `noBundle`. * + * @param compatibilityDateStr The compatibility date * @param compatibilityFlags The compatibility flags - * @param validateConfig Whether to validate the config (logs and throws) * @param nodeCompat Whether to add polyfills for node builtin modules and globals * @param noBundle Whether to skip internal build steps and directly deploy script - * @returns one of: - * - "legacy": build-time polyfills, from `node_compat` flag - * - "als": nodejs_als compatibility flag - * - "v1": nodejs_compat compatibility flag - * - "v2": nodejs_compat_v2 compatibility flag - * - null: no Node.js compatibility - */ -export function getNodeCompatMode( + * + */ export function validateNodeCompatMode( + compatibilityDateStr: string = "2000-01-01", // Default to some arbitrary old date compatibilityFlags: string[], { - validateConfig = true, - nodeCompat = undefined, + nodeCompat: legacy = false, noBundle = undefined, }: { - validateConfig?: boolean; nodeCompat?: boolean; noBundle?: boolean; } ): NodeJSCompatMode { const { + mode, hasNodejsAlsFlag, hasNodejsCompatFlag, hasNodejsCompatV2Flag, hasExperimentalNodejsCompatV2Flag, - } = parseNodeCompatibilityFlags(compatibilityFlags); - - const legacy = nodeCompat === true; - let mode: NodeJSCompatMode = null; - if (hasNodejsCompatV2Flag) { - mode = "v2"; - } else if (hasNodejsCompatFlag) { - mode = "v1"; - } else if (hasNodejsAlsFlag) { - mode = "als"; - } else if (legacy) { - mode = "legacy"; - } - - if (validateConfig !== true) { - // Skip the validation. - return mode; - } + } = getNodeCompat(compatibilityDateStr, compatibilityFlags, { + nodeCompat: legacy, + }); if (hasExperimentalNodejsCompatV2Flag) { throw new UserError( @@ -113,14 +82,3 @@ export function getNodeCompatMode( return mode; } - -function parseNodeCompatibilityFlags(compatibilityFlags: string[]) { - return { - hasNodejsAlsFlag: compatibilityFlags.includes("nodejs_als"), - hasNodejsCompatFlag: compatibilityFlags.includes("nodejs_compat"), - hasNodejsCompatV2Flag: compatibilityFlags.includes("nodejs_compat_v2"), - hasExperimentalNodejsCompatV2Flag: compatibilityFlags.includes( - "experimental:nodejs_compat_v2" - ), - }; -} diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 53c98cb7482f..072b80ff4147 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -14,7 +14,7 @@ import { processAssetsArg, validateAssetsArgsAndConfig } from "./assets"; import { findWranglerToml, printBindings, readConfig } from "./config"; import { validateRoutes } from "./deploy/deploy"; import { getEntry } from "./deployment-bundle/entry"; -import { getNodeCompatMode } from "./deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "./deployment-bundle/node-compat"; import { getBoundRegisteredWorkers } from "./dev-registry"; import Dev, { devRegistry } from "./dev/dev"; import { getVarsForDev } from "./dev/dev-vars"; @@ -685,7 +685,8 @@ export async function startDev(args: StartDevOptions) { moduleRoot: args.moduleRoot, moduleRules: args.rules, nodejsCompatMode: (parsedConfig: Config) => - getNodeCompatMode( + validateNodeCompatMode( + args.compatibilityDate ?? parsedConfig.compatibility_date, args.compatibilityFlags ?? parsedConfig.compatibility_flags ?? [], { nodeCompat: args.nodeCompat ?? parsedConfig.node_compat, @@ -889,7 +890,8 @@ export async function startDev(args: StartDevOptions) { additionalModules, } = devServerSettings; - const nodejsCompatMode = getNodeCompatMode( + const nodejsCompatMode = validateNodeCompatMode( + args.compatibilityDate ?? config.compatibility_date, args.compatibilityFlags ?? config.compatibility_flags ?? [], { nodeCompat: args.nodeCompat ?? config.node_compat, @@ -1050,7 +1052,8 @@ export async function startApiDev(args: StartDevOptions) { additionalModules, } = await validateDevServerSettings(args, config); - const nodejsCompatMode = getNodeCompatMode( + const nodejsCompatMode = validateNodeCompatMode( + args.compatibilityDate ?? config.compatibility_date, args.compatibilityFlags ?? config.compatibility_flags, { nodeCompat: args.nodeCompat ?? config.node_compat, diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index 1cde8802c37f..989b7ea70e31 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -48,7 +48,6 @@ import type { AssetsOptions } from "../assets"; import type { Config } from "../config"; import type { Route } from "../config/environment"; import type { Entry } from "../deployment-bundle/entry"; -import type { NodeJSCompatMode } from "../deployment-bundle/node-compat"; import type { CfModule, CfWorkerInit } from "../deployment-bundle/worker"; import type { StartDevOptions } from "../dev"; import type { WorkerRegistry } from "../dev-registry"; @@ -56,6 +55,7 @@ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/ty import type { EphemeralDirectory } from "../paths"; import type { LegacyAssetPaths } from "../sites"; import type { EsbuildBundle } from "./use-esbuild"; +import type { NodeJSCompatMode } from "miniflare"; /** * This hooks establishes a connection with the dev registry, diff --git a/packages/wrangler/src/dev/start-server.ts b/packages/wrangler/src/dev/start-server.ts index 29ef74065922..4ba59da72251 100644 --- a/packages/wrangler/src/dev/start-server.ts +++ b/packages/wrangler/src/dev/start-server.ts @@ -39,12 +39,12 @@ import type { ProxyData, StartDevWorkerInput, Trigger } from "../api"; import type { Config } from "../config"; import type { DurableObjectBindings } from "../config/environment"; import type { Entry } from "../deployment-bundle/entry"; -import type { NodeJSCompatMode } from "../deployment-bundle/node-compat"; import type { CfModule } from "../deployment-bundle/worker"; import type { WorkerRegistry } from "../dev-registry"; import type { DevProps } from "./dev"; import type { LocalProps } from "./local"; import type { EsbuildBundle } from "./use-esbuild"; +import type { NodeJSCompatMode } from "miniflare"; export async function startDevServer( props: DevProps & { diff --git a/packages/wrangler/src/dev/use-esbuild.ts b/packages/wrangler/src/dev/use-esbuild.ts index 2ba0016c9358..7a073e27997b 100644 --- a/packages/wrangler/src/dev/use-esbuild.ts +++ b/packages/wrangler/src/dev/use-esbuild.ts @@ -17,9 +17,9 @@ import { import type { Config } from "../config"; import type { SourceMapMetadata } from "../deployment-bundle/bundle"; import type { Entry } from "../deployment-bundle/entry"; -import type { NodeJSCompatMode } from "../deployment-bundle/node-compat"; import type { CfModule, CfModuleType } from "../deployment-bundle/worker"; import type { Metafile } from "esbuild"; +import type { NodeJSCompatMode } from "miniflare"; export type EsbuildBundle = { id: number; diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index 006d26d83130..35f7c303b970 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -10,7 +10,7 @@ import path, { import { createUploadWorkerBundleContents } from "../api/pages/create-worker-bundle-contents"; import { readConfig } from "../config"; import { writeAdditionalModules } from "../deployment-bundle/find-additional-modules"; -import { getNodeCompatMode } from "../deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "../deployment-bundle/node-compat"; import { FatalError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; @@ -29,11 +29,11 @@ import { } from "./functions/buildWorker"; import type { Config } from "../config"; import type { BundleResult } from "../deployment-bundle/bundle"; -import type { NodeJSCompatMode } from "../deployment-bundle/node-compat"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; +import type { NodeJSCompatMode } from "miniflare"; export type PagesBuildArgs = StrictYargsOptionsToInterface; @@ -438,10 +438,14 @@ const validateArgs = async (args: PagesBuildArgs): Promise => { } const { nodeCompat: node_compat, ...argsExceptNodeCompat } = args; - const nodejsCompatMode = getNodeCompatMode(args.compatibilityFlags ?? [], { - nodeCompat: node_compat, - noBundle: config?.no_bundle, - }); + const nodejsCompatMode = validateNodeCompatMode( + args.compatibilityDate ?? config?.compatibility_date, + args.compatibilityFlags ?? config?.compatibility_flags ?? [], + { + nodeCompat: node_compat, + noBundle: config?.no_bundle, + } + ); const defineNavigatorUserAgent = isNavigatorDefined( args.compatibilityDate, diff --git a/packages/wrangler/src/pages/buildFunctions.ts b/packages/wrangler/src/pages/buildFunctions.ts index 9b43a2f0ad49..4f1a2263b695 100644 --- a/packages/wrangler/src/pages/buildFunctions.ts +++ b/packages/wrangler/src/pages/buildFunctions.ts @@ -10,9 +10,9 @@ import { writeRoutesModule } from "./functions/routes"; import { convertRoutesToRoutesJSONSpec } from "./functions/routes-transformation"; import { getPagesTmpDir, RUNNING_BUILDERS } from "./utils"; import type { BundleResult } from "../deployment-bundle/bundle"; -import type { NodeJSCompatMode } from "../deployment-bundle/node-compat"; import type { PagesBuildArgs } from "./build"; import type { Config } from "./functions/routes"; +import type { NodeJSCompatMode } from "miniflare"; /** * Builds a Functions worker based on the functions directory, with filepath and handler based routing. diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index 6d30ccce5050..630e4bc71a41 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -7,7 +7,7 @@ import { unstable_dev } from "../api"; import { readConfig } from "../config"; import { isBuildFailure } from "../deployment-bundle/build-failures"; import { esbuildAliasExternalPlugin } from "../deployment-bundle/esbuild-plugins/alias-external"; -import { getNodeCompatMode } from "../deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "../deployment-bundle/node-compat"; import { FatalError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; @@ -360,7 +360,8 @@ export const Handler = async (args: PagesDevArguments) => { let scriptPath = ""; - const nodejsCompatMode = getNodeCompatMode( + const nodejsCompatMode = validateNodeCompatMode( + args.compatibilityDate ?? config.compatibility_date, args.compatibilityFlags ?? config.compatibility_flags ?? [], { nodeCompat: args.nodeCompat, diff --git a/packages/wrangler/src/pages/functions/buildPlugin.ts b/packages/wrangler/src/pages/functions/buildPlugin.ts index 4f0d8346e075..867a8dfa3fa9 100644 --- a/packages/wrangler/src/pages/functions/buildPlugin.ts +++ b/packages/wrangler/src/pages/functions/buildPlugin.ts @@ -109,7 +109,6 @@ export function buildPluginFromFunctions({ // TODO: mock AE datasets in Pages functions for dev mockAnalyticsEngineDatasets: [], targetConsumer: local ? "dev" : "deploy", - forPages: true, local, projectRoot: getPagesProjectRoot(), defineNavigatorUserAgent, diff --git a/packages/wrangler/src/pages/functions/buildWorker.ts b/packages/wrangler/src/pages/functions/buildWorker.ts index 9792b4314b39..77a2ab984611 100644 --- a/packages/wrangler/src/pages/functions/buildWorker.ts +++ b/packages/wrangler/src/pages/functions/buildWorker.ts @@ -14,9 +14,9 @@ import { getBasePath } from "../../paths"; import { getPagesProjectRoot, getPagesTmpDir } from "../utils"; import type { BundleResult } from "../../deployment-bundle/bundle"; import type { Entry } from "../../deployment-bundle/entry"; -import type { NodeJSCompatMode } from "../../deployment-bundle/node-compat"; import type { CfModule } from "../../deployment-bundle/worker"; import type { Plugin } from "esbuild"; +import type { NodeJSCompatMode } from "miniflare"; export type Options = { routesModule: string; @@ -85,7 +85,6 @@ export function buildWorkerFromFunctions({ serveLegacyAssetsFromWorker: false, checkFetch: local, targetConsumer: local ? "dev" : "deploy", - forPages: true, local, projectRoot: getPagesProjectRoot(), defineNavigatorUserAgent, @@ -189,7 +188,6 @@ export function buildRawWorker({ serveLegacyAssetsFromWorker: false, checkFetch: local, targetConsumer: local ? "dev" : "deploy", - forPages: true, local, projectRoot: getPagesProjectRoot(), defineNavigatorUserAgent, diff --git a/packages/wrangler/src/type-generation/index.ts b/packages/wrangler/src/type-generation/index.ts index 4a1dd513af05..5ee58a73677a 100644 --- a/packages/wrangler/src/type-generation/index.ts +++ b/packages/wrangler/src/type-generation/index.ts @@ -2,9 +2,9 @@ import * as fs from "node:fs"; import { basename, dirname, extname, join, relative, resolve } from "node:path"; import * as esmLexer from "es-module-lexer"; import { findUpSync } from "find-up"; +import { getNodeCompat } from "miniflare"; import { findWranglerToml, readConfig } from "../config"; import { getEntry } from "../deployment-bundle/entry"; -import { getNodeCompatMode } from "../deployment-bundle/node-compat"; import { getVarsForDev } from "../dev/dev-vars"; import { UserError } from "../errors"; import { CommandLineArgsError } from "../index"; @@ -92,10 +92,13 @@ export async function typesHandler( const tsconfigPath = config.tsconfig ?? join(dirname(configPath), "tsconfig.json"); const tsconfigTypes = readTsconfigTypes(tsconfigPath); - const mode = getNodeCompatMode(config.compatibility_flags, { - validateConfig: false, - nodeCompat: config.node_compat, - }); + const { mode } = getNodeCompat( + config.compatibility_date, + config.compatibility_flags, + { + nodeCompat: config.node_compat, + } + ); logRuntimeTypesMessage(outFile, tsconfigTypes, mode !== null); } diff --git a/packages/wrangler/src/versions/upload.ts b/packages/wrangler/src/versions/upload.ts index 32163ce108cf..4f1dc4971d5c 100644 --- a/packages/wrangler/src/versions/upload.ts +++ b/packages/wrangler/src/versions/upload.ts @@ -19,7 +19,7 @@ import { createModuleCollector, getWrangler1xLegacyModuleReferences, } from "../deployment-bundle/module-collection"; -import { getNodeCompatMode } from "../deployment-bundle/node-compat"; +import { validateNodeCompatMode } from "../deployment-bundle/node-compat"; import { loadSourceMaps } from "../deployment-bundle/source-maps"; import { confirm } from "../dialogs"; import { getMigrationsToUpload } from "../durable"; @@ -185,7 +185,8 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m const minify = props.minify ?? config.minify; - const nodejsCompatMode = getNodeCompatMode( + const nodejsCompatMode = validateNodeCompatMode( + props.compatibilityDate ?? config.compatibility_date, props.compatibilityFlags ?? config.compatibility_flags, { nodeCompat: props.nodeCompat ?? config.node_compat,