From 5644761bb38dae912ce286c75a706248fdad5236 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Mon, 24 Feb 2025 23:47:14 +0000 Subject: [PATCH 01/17] make sure that instrumentation files work --- .changeset/grumpy-trainers-learn.md | 9 ++ examples/common/apps.ts | 1 + examples/instrumentation-app/.gitignore | 47 ++++++++ .../app/api/hello/route.js | 8 ++ examples/instrumentation-app/app/layout.tsx | 7 ++ examples/instrumentation-app/app/page.jsx | 20 ++++ .../e2e/instrumentation.spec.ts | 36 ++++++ .../e2e/playwright.config.ts | 3 + .../instrumentation-app/instrumentation.js | 15 +++ examples/instrumentation-app/middleware.js | 12 ++ examples/instrumentation-app/next.config.ts | 10 ++ .../instrumentation-app/open-next.config.ts | 26 +++++ examples/instrumentation-app/package.json | 28 +++++ examples/instrumentation-app/wrangler.json | 11 ++ examples/middleware/tsconfig.json | 0 .../cloudflare/src/cli/build/bundle-server.ts | 2 +- .../plugins/load-instrumentation.spec.ts | 83 ++++++++++++- .../patches/plugins/load-instrumentation.ts | 47 ++++++-- pnpm-lock.yaml | 109 +++++++++++------- 19 files changed, 413 insertions(+), 61 deletions(-) create mode 100644 .changeset/grumpy-trainers-learn.md create mode 100644 examples/instrumentation-app/.gitignore create mode 100644 examples/instrumentation-app/app/api/hello/route.js create mode 100644 examples/instrumentation-app/app/layout.tsx create mode 100644 examples/instrumentation-app/app/page.jsx create mode 100644 examples/instrumentation-app/e2e/instrumentation.spec.ts create mode 100644 examples/instrumentation-app/e2e/playwright.config.ts create mode 100644 examples/instrumentation-app/instrumentation.js create mode 100644 examples/instrumentation-app/middleware.js create mode 100644 examples/instrumentation-app/next.config.ts create mode 100644 examples/instrumentation-app/open-next.config.ts create mode 100644 examples/instrumentation-app/package.json create mode 100644 examples/instrumentation-app/wrangler.json mode change 100755 => 100644 examples/middleware/tsconfig.json diff --git a/.changeset/grumpy-trainers-learn.md b/.changeset/grumpy-trainers-learn.md new file mode 100644 index 00000000..512c8e33 --- /dev/null +++ b/.changeset/grumpy-trainers-learn.md @@ -0,0 +1,9 @@ +--- +"@opennextjs/cloudflare": patch +--- + +make sure that instrumentation files work + +currently [instrumentation files](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) +in applications built using the adapter are ignored, the changes here +make sure that those are instead properly included in the applications diff --git a/examples/common/apps.ts b/examples/common/apps.ts index 221ca784..ff96f71e 100644 --- a/examples/common/apps.ts +++ b/examples/common/apps.ts @@ -7,6 +7,7 @@ const apps = [ "vercel-blog-starter", "vercel-commerce", "ssg-app", + "instrumentation-app", // e2e "app-pages-router", "app-router", diff --git a/examples/instrumentation-app/.gitignore b/examples/instrumentation-app/.gitignore new file mode 100644 index 00000000..3f753f29 --- /dev/null +++ b/examples/instrumentation-app/.gitignore @@ -0,0 +1,47 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/examples/instrumentation-app/app/api/hello/route.js b/examples/instrumentation-app/app/api/hello/route.js new file mode 100644 index 00000000..e1f4861e --- /dev/null +++ b/examples/instrumentation-app/app/api/hello/route.js @@ -0,0 +1,8 @@ +import { NextResponse } from "next/server"; + +export function GET() { + return NextResponse.json({ + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + }); +} diff --git a/examples/instrumentation-app/app/layout.tsx b/examples/instrumentation-app/app/layout.tsx new file mode 100644 index 00000000..f3ef34cd --- /dev/null +++ b/examples/instrumentation-app/app/layout.tsx @@ -0,0 +1,7 @@ +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/examples/instrumentation-app/app/page.jsx b/examples/instrumentation-app/app/page.jsx new file mode 100644 index 00000000..f913b332 --- /dev/null +++ b/examples/instrumentation-app/app/page.jsx @@ -0,0 +1,20 @@ +import Link from "next/link"; + +export default function () { + return ( +
+

+ See{" "} + + /api/hello + +

+

+ See{" "} + + /middleware + +

+
+ ); +} diff --git a/examples/instrumentation-app/e2e/instrumentation.spec.ts b/examples/instrumentation-app/e2e/instrumentation.spec.ts new file mode 100644 index 00000000..dfcc4f44 --- /dev/null +++ b/examples/instrumentation-app/e2e/instrumentation.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from "@playwright/test"; +import { describe } from "node:test"; + +test.describe("instrumentation", () => { + test("the instrumentation register hook should work for the nodejs runtime", async ({ page }) => { + const res = await page.request.get("/api/hello"); + const respJson: Record = await res.json(); + expect(respJson["nodejs-instrumentation-setup"]).toEqual( + "this value has been set by calling the instrumentation `register` callback in the nodejs runtime" + ); + }); + + test("the instrumentation register hook should work for the edge runtime", async ({ page }) => { + const res = await page.request.get("/middleware"); + const respJson: Record = await res.json(); + expect(respJson["edge-instrumentation-setup"]).toEqual( + "this value has been set by calling the instrumentation `register` callback in the edge runtime" + ); + }); + + // Note: we cannot test this since currently both runtimes share the same global scope + // (see: https://github.com/opennextjs/opennextjs-cloudflare/issues/408) + describe.skip("isolation", () => { + test("the instrumentation register hook nodejs logic should not effect edge routes", async ({ page }) => { + const res = await page.request.get("/middleware"); + const respJson: Record = await res.json(); + expect(respJson["nodejs-instrumentation-setup"]).toEqual("undefined"); + }); + + test("the instrumentation register hook edge logic should not effect nodejs routes", async ({ page }) => { + const res = await page.request.get("/api/hello"); + const respJson: Record = await res.json(); + expect(respJson["edge-instrumentation-setup"]).toEqual("undefined"); + }); + }); +}); diff --git a/examples/instrumentation-app/e2e/playwright.config.ts b/examples/instrumentation-app/e2e/playwright.config.ts new file mode 100644 index 00000000..947555e2 --- /dev/null +++ b/examples/instrumentation-app/e2e/playwright.config.ts @@ -0,0 +1,3 @@ +import { configurePlaywright } from "../../common/config-e2e"; + +export default configurePlaywright("instrumentation-app", { isCI: !!process.env.CI }); diff --git a/examples/instrumentation-app/instrumentation.js b/examples/instrumentation-app/instrumentation.js new file mode 100644 index 00000000..828854ae --- /dev/null +++ b/examples/instrumentation-app/instrumentation.js @@ -0,0 +1,15 @@ +export function register() { + // Note: we register instrumentation for both the nodejs and edge runtime, we do that using the NEXT_RUNTIME env + // variable as recommended in the official docs: + // https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation#importing-runtime-specific-code + + if (process.env.NEXT_RUNTIME === "nodejs") { + globalThis["__NODEJS_INSTRUMENTATION_SETUP"] = + "this value has been set by calling the instrumentation `register` callback in the nodejs runtime"; + } + + if (process.env.NEXT_RUNTIME === "edge") { + globalThis["__EDGE_INSTRUMENTATION_SETUP"] = + "this value has been set by calling the instrumentation `register` callback in the edge runtime"; + } +} diff --git a/examples/instrumentation-app/middleware.js b/examples/instrumentation-app/middleware.js new file mode 100644 index 00000000..ba945377 --- /dev/null +++ b/examples/instrumentation-app/middleware.js @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server"; + +export function middleware() { + return NextResponse.json({ + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + }); +} + +export const config = { + matcher: ["/middleware"], +}; diff --git a/examples/instrumentation-app/next.config.ts b/examples/instrumentation-app/next.config.ts new file mode 100644 index 00000000..dce7856f --- /dev/null +++ b/examples/instrumentation-app/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from "next"; +import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + +initOpenNextCloudflareForDev(); + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/examples/instrumentation-app/open-next.config.ts b/examples/instrumentation-app/open-next.config.ts new file mode 100644 index 00000000..e860ec20 --- /dev/null +++ b/examples/instrumentation-app/open-next.config.ts @@ -0,0 +1,26 @@ +// default open-next.config.ts file created by @opennextjs/cloudflare + +import cache from "@opennextjs/cloudflare/kv-cache"; + +const config = { + default: { + override: { + wrapper: "cloudflare-node", + converter: "edge", + incrementalCache: async () => cache, + tagCache: "dummy", + queue: "dummy", + }, + }, + + middleware: { + external: true, + override: { + wrapper: "cloudflare-edge", + converter: "edge", + proxyExternalRequest: "fetch", + }, + }, +}; + +export default config; diff --git a/examples/instrumentation-app/package.json b/examples/instrumentation-app/package.json new file mode 100644 index 00000000..23383cac --- /dev/null +++ b/examples/instrumentation-app/package.json @@ -0,0 +1,28 @@ +{ + "name": "instrumentation-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "build:worker": "opennextjs-cloudflare", + "preview": "pnpm build:worker && pnpm wrangler dev", + "e2e": "playwright test -c e2e/playwright.config.ts" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "next": "15.1.7" + }, + "devDependencies": { + "@opennextjs/cloudflare": "workspace:*", + "@playwright/test": "catalog:", + "@types/node": "catalog:", + "@types/react": "^19", + "@types/react-dom": "^19", + "typescript": "catalog:", + "wrangler": "catalog:" + } +} diff --git a/examples/instrumentation-app/wrangler.json b/examples/instrumentation-app/wrangler.json new file mode 100644 index 00000000..13d87d90 --- /dev/null +++ b/examples/instrumentation-app/wrangler.json @@ -0,0 +1,11 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "instrumentation-app", + "compatibility_date": "2025-02-04", + "compatibility_flags": ["nodejs_compat"], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS" + } +} diff --git a/examples/middleware/tsconfig.json b/examples/middleware/tsconfig.json old mode 100755 new mode 100644 diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index ce2d2b68..692e2eda 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -91,7 +91,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { setWranglerExternal(), fixRequire(updater), handleOptionalDependencies(optionalDependencies), - patchLoadInstrumentation(updater), + patchLoadInstrumentation(updater, buildOpts), patchFetchCacheSetMissingWaitUntil(updater), inlineEvalManifest(updater, buildOpts), inlineFindDir(updater, buildOpts), diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts index 4fc1a713..212fb078 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts @@ -1,10 +1,73 @@ -import { describe, expect, test } from "vitest"; +import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { describe, expect, test, vi } from "vitest"; import { patchCode } from "../ast/util.js"; -import { instrumentationRule } from "./load-instrumentation.js"; +import { getRule } from "./load-instrumentation.js"; + +vi.mock(import("node:fs"), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + existsSync(path) { + return `${path}`.includes("_file_exists_"); + }, + }; +}); describe("LoadInstrumentationModule", () => { - test("patch", () => { + test("patch when an instrumentation file is not present", async () => { + const code = ` +export default class NextNodeServer extends BaseServer< + Options, + NodeNextRequest, + NodeNextResponse +> { + protected async loadInstrumentationModule() { + if (!this.serverOptions.dev) { + try { + this.instrumentation = await dynamicRequire( + resolve( + this.serverOptions.dir || '.', + this.serverOptions.conf.distDir!, + 'server', + INSTRUMENTATION_HOOK_FILENAME + ) + ) + } catch (err: any) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw new Error( + 'An error occurred while loading the instrumentation hook', + { cause: err } + ) + } + } + } + return this.instrumentation + } +}`; + + expect( + patchCode( + code, + await getRule({ + appBuildOutputPath: "", + appPath: "", + monorepoRoot: "", + outputDir: "", + } as BuildOptions) + ) + ).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer< + Options, + NodeNextRequest, + NodeNextResponse + > { + async loadInstrumentationModule() { this.instrumentation = null; return this.instrumentation; } + }" + `); + }); + + test("patch when an instrumentation file is present", async () => { const code = ` export default class NextNodeServer extends BaseServer< Options, @@ -35,13 +98,23 @@ export default class NextNodeServer extends BaseServer< } }`; - expect(patchCode(code, instrumentationRule)).toMatchInlineSnapshot(` + expect( + patchCode( + code, + await getRule({ + appBuildOutputPath: "_file_exists_", + appPath: "test", + monorepoRoot: "", + outputDir: "", + } as BuildOptions) + ) + ).toMatchInlineSnapshot(` "export default class NextNodeServer extends BaseServer< Options, NodeNextRequest, NodeNextResponse > { - async loadInstrumentationModule() { } + async loadInstrumentationModule() { this.instrumentation = require('server-functions/default/_file_exists_/.next/server/instrumentation.js'); return this.instrumentation; } }" `); }); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts index 99076715..3c1b4671 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts @@ -2,23 +2,46 @@ * `loadInstrumentationModule` uses a dynamic require which is not supported. */ -import { patchCode } from "../ast/util.js"; -import type { ContentUpdater } from "./content-updater.js"; +import { existsSync } from "node:fs"; +import { join } from "node:path"; -export const instrumentationRule = ` -rule: - kind: method_definition - all: - - has: {field: name, regex: ^loadInstrumentationModule$} - - has: {pattern: dynamicRequire, stopBy: end} +import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; -fix: async loadInstrumentationModule() { } -`; +import { patchCode } from "../ast/util.js"; +import type { ContentUpdater } from "./content-updater.js"; -export function patchLoadInstrumentation(updater: ContentUpdater) { +export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { return updater.updateContent( "patch-load-instrumentation", { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, - ({ contents }) => patchCode(contents, instrumentationRule) + async ({ contents }) => patchCode(contents, await getRule(buildOpts)) ); } + +export async function getRule(buildOpts: BuildOptions) { + const { outputDir } = buildOpts; + + const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); + const dotNextDir = join(baseDir, ".next"); + const builtInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + + return ` + rule: + kind: method_definition + all: + - has: {field: name, regex: ^loadInstrumentationModule$} + - has: {pattern: dynamicRequire, stopBy: end} + + fix: + async loadInstrumentationModule() { + this.instrumentation = ${existsSync(builtInstrumentationPath) ? `require('${builtInstrumentationPath}')` : "null"}; + return this.instrumentation; + } + `; +} + +/** + * Pattern to detect instrumentation hooks file + * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) + */ +const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c5d509c..9830c2c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -582,6 +582,40 @@ importers: specifier: catalog:e2e version: 19.0.0 + examples/instrumentation-app: + dependencies: + next: + specifier: 15.1.7 + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.47.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@opennextjs/cloudflare': + specifier: workspace:* + version: link:../../packages/cloudflare + '@playwright/test': + specifier: 'catalog:' + version: 1.47.0 + '@types/node': + specifier: 'catalog:' + version: 22.2.0 + '@types/react': + specifier: ^19 + version: 19.0.8 + '@types/react-dom': + specifier: ^19 + version: 19.0.3(@types/react@19.0.8) + typescript: + specifier: 'catalog:' + version: 5.7.3 + wrangler: + specifier: 'catalog:' + version: 3.107.3(@cloudflare/workers-types@4.20250109.0) + examples/middleware: dependencies: '@clerk/nextjs': @@ -12977,7 +13011,7 @@ snapshots: '@react-aria/ssr@3.9.5(react@19.0.0-rc-3208e73e-20240730)': dependencies: - '@swc/helpers': 0.5.12 + '@swc/helpers': 0.5.15 react: 19.0.0-rc-3208e73e-20240730 '@react-aria/utils@3.25.2(react@19.0.0-rc-3208e73e-20240730)': @@ -15921,8 +15955,8 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -15941,7 +15975,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.36.1(eslint@8.57.1) @@ -15961,7 +15995,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.11.1(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.11.1(jiti@1.21.6)) @@ -15981,7 +16015,7 @@ snapshots: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-jsx-a11y: 6.10.0(eslint@9.19.0(jiti@1.21.6)) eslint-plugin-react: 7.37.4(eslint@9.19.0(jiti@1.21.6)) @@ -16001,32 +16035,32 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16039,13 +16073,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 9.11.1(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16058,13 +16092,13 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): + eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.3.6 enhanced-resolve: 5.17.1 eslint: 9.19.0(jiti@1.21.6) - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) fast-glob: 3.3.2 get-tsconfig: 4.8.0 is-bun-module: 1.2.1 @@ -16077,84 +16111,73 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): - dependencies: - debug: 3.2.7 - optionalDependencies: - '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) - eslint: 8.57.1 - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) - transitivePeerDependencies: - - supports-color - - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@8.57.1)(typescript@5.7.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3) eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.11.1(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3) eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) + eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@9.19.0(jiti@1.21.6)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -16165,7 +16188,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16193,7 +16216,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@8.57.1)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16222,7 +16245,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.11.1(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)))(eslint@9.11.1(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.11.1(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.11.1(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -16251,7 +16274,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.19.0(jiti@1.21.6) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)))(eslint@9.19.0(jiti@1.21.6)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.7.0(eslint@9.19.0(jiti@1.21.6))(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@9.19.0(jiti@1.21.6)) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 From a06e34558120a9c9c14524e9a4cbea139a6a5f66 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 09:16:11 +0000 Subject: [PATCH 02/17] change getRule params --- .../plugins/load-instrumentation.spec.ts | 27 +++---------------- .../patches/plugins/load-instrumentation.ts | 16 +++++------ 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts index 212fb078..5b950fc5 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts @@ -1,4 +1,3 @@ -import type { BuildOptions } from "@opennextjs/aws/build/helper.js"; import { describe, expect, test, vi } from "vitest"; import { patchCode } from "../ast/util.js"; @@ -46,17 +45,7 @@ export default class NextNodeServer extends BaseServer< } }`; - expect( - patchCode( - code, - await getRule({ - appBuildOutputPath: "", - appPath: "", - monorepoRoot: "", - outputDir: "", - } as BuildOptions) - ) - ).toMatchInlineSnapshot(` + expect(patchCode(code, await getRule("_file_does_not_exist_"))).toMatchInlineSnapshot(` "export default class NextNodeServer extends BaseServer< Options, NodeNextRequest, @@ -98,23 +87,13 @@ export default class NextNodeServer extends BaseServer< } }`; - expect( - patchCode( - code, - await getRule({ - appBuildOutputPath: "_file_exists_", - appPath: "test", - monorepoRoot: "", - outputDir: "", - } as BuildOptions) - ) - ).toMatchInlineSnapshot(` + expect(patchCode(code, await getRule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` "export default class NextNodeServer extends BaseServer< Options, NodeNextRequest, NodeNextResponse > { - async loadInstrumentationModule() { this.instrumentation = require('server-functions/default/_file_exists_/.next/server/instrumentation.js'); return this.instrumentation; } + async loadInstrumentationModule() { this.instrumentation = require('/_file_exists_/instrumentation.js'); return this.instrumentation; } }" `); }); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts index 3c1b4671..a6bbd217 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts @@ -11,20 +11,20 @@ import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { - return updater.updateContent( - "patch-load-instrumentation", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, - async ({ contents }) => patchCode(contents, await getRule(buildOpts)) - ); -} - -export async function getRule(buildOpts: BuildOptions) { const { outputDir } = buildOpts; const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); const dotNextDir = join(baseDir, ".next"); const builtInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + return updater.updateContent( + "patch-load-instrumentation", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, + async ({ contents }) => patchCode(contents, await getRule(builtInstrumentationPath)) + ); +} + +export async function getRule(builtInstrumentationPath: string) { return ` rule: kind: method_definition From 2bb6a62b3d14c03136239f481cb24d87fb443b27 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 09:19:03 +0000 Subject: [PATCH 03/17] include null type in getRule param --- .../build/patches/plugins/load-instrumentation.spec.ts | 2 +- .../cli/build/patches/plugins/load-instrumentation.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts index 5b950fc5..ed2cc2aa 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts @@ -45,7 +45,7 @@ export default class NextNodeServer extends BaseServer< } }`; - expect(patchCode(code, await getRule("_file_does_not_exist_"))).toMatchInlineSnapshot(` + expect(patchCode(code, await getRule(null))).toMatchInlineSnapshot(` "export default class NextNodeServer extends BaseServer< Options, NodeNextRequest, diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts index a6bbd217..d1658f83 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts @@ -15,7 +15,10 @@ export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: Bui const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); const dotNextDir = join(baseDir, ".next"); - const builtInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) + ? maybeBuiltInstrumentationPath + : null; return updater.updateContent( "patch-load-instrumentation", @@ -24,7 +27,7 @@ export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: Bui ); } -export async function getRule(builtInstrumentationPath: string) { +export async function getRule(builtInstrumentationPath: string | null) { return ` rule: kind: method_definition @@ -34,7 +37,7 @@ export async function getRule(builtInstrumentationPath: string) { fix: async loadInstrumentationModule() { - this.instrumentation = ${existsSync(builtInstrumentationPath) ? `require('${builtInstrumentationPath}')` : "null"}; + this.instrumentation = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "null"}; return this.instrumentation; } `; From 1cc62a6118acfaabdb139b66ddfb7ff74c29d0a9 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 09:26:20 +0000 Subject: [PATCH 04/17] fix build issue --- examples/instrumentation-app/tsconfig.json | 27 ++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/instrumentation-app/tsconfig.json diff --git a/examples/instrumentation-app/tsconfig.json b/examples/instrumentation-app/tsconfig.json new file mode 100644 index 00000000..d8b93235 --- /dev/null +++ b/examples/instrumentation-app/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} From 5268738a2493c13f71f6b33e7b9e704f9adb194f Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 09:42:13 +0000 Subject: [PATCH 05/17] playground14 and playground15 --- examples/common/apps.ts | 3 +- examples/instrumentation-app/app/layout.tsx | 7 -- examples/instrumentation-app/app/page.jsx | 20 ----- .../e2e/playwright.config.ts | 3 - examples/instrumentation-app/next.config.ts | 10 --- examples/instrumentation-app/wrangler.json | 11 --- examples/playground/e2e/playwright.config.ts | 3 - .../{playground => playground14}/.dev.vars | 0 .../.env.development | 0 .../{playground => playground14}/.gitignore | 0 .../{playground => playground14}/README.md | 0 .../app/api/buildid/route.ts | 0 .../app/api/env/route.ts | 0 .../app/api/hello/route.ts | 0 .../app/api/instrumentation/route.ts} | 0 .../app/api/request/route.ts | 0 .../app/isr/[id]/dynamic/page.tsx | 0 .../app/isr/[id]/no-dynamic/page.tsx | 0 .../app/layout.js | 0 .../app/og/route.tsx | 0 .../{playground => playground14}/app/page.js | 0 .../e2e/base.spec.ts | 0 .../e2e/cloudflare.spec.ts | 0 .../e2e/instrumentation.spec.ts | 8 +- .../e2e/isr.spec.ts | 0 .../playground14/e2e/playwright.config.ts | 3 + .../e2e/playwright.dev.config.ts | 2 +- .../instrumentation.js | 0 .../middleware.js | 2 +- examples/playground14/next.config.mjs | 14 ++++ .../open-next.config.ts | 0 .../{playground => playground14}/package.json | 2 +- .../public/.gitkeep | 0 .../tsconfig.json | 0 .../worker-configuration.d.ts | 0 .../wrangler.json | 0 examples/playground15/.dev.vars | 1 + examples/playground15/.env.development | 1 + .../.gitignore | 16 ++-- examples/playground15/README.md | 1 + .../playground15/app/api/buildid/route.ts | 9 +++ examples/playground15/app/api/env/route.ts | 8 ++ examples/playground15/app/api/hello/route.ts | 21 ++++++ .../app/api/instrumentation/route.ts | 8 ++ .../playground15/app/api/request/route.ts | 5 ++ .../app/isr/[id]/dynamic/page.tsx | 30 ++++++++ .../app/isr/[id]/no-dynamic/page.tsx | 29 ++++++++ examples/playground15/app/layout.js | 12 +++ examples/playground15/app/og/route.tsx | 65 ++++++++++++++++ examples/playground15/app/page.js | 7 ++ examples/playground15/e2e/base.spec.ts | 61 +++++++++++++++ examples/playground15/e2e/cloudflare.spec.ts | 16 ++++ .../playground15/e2e/instrumentation.spec.ts | 36 +++++++++ examples/playground15/e2e/isr.spec.ts | 32 ++++++++ .../playground15/e2e/playwright.config.ts | 3 + .../playground15/e2e/playwright.dev.config.ts | 6 ++ examples/playground15/instrumentation.js | 15 ++++ examples/playground15/middleware.js | 12 +++ .../next.config.mjs | 0 .../open-next.config.ts | 8 +- .../package.json | 16 ++-- examples/playground15/public/.gitkeep | 0 .../tsconfig.json | 17 ++--- .../playground15/worker-configuration.d.ts | 5 ++ examples/playground15/wrangler.json | 20 +++++ pnpm-lock.yaml | 74 ++++++++----------- 66 files changed, 485 insertions(+), 137 deletions(-) delete mode 100644 examples/instrumentation-app/app/layout.tsx delete mode 100644 examples/instrumentation-app/app/page.jsx delete mode 100644 examples/instrumentation-app/e2e/playwright.config.ts delete mode 100644 examples/instrumentation-app/next.config.ts delete mode 100644 examples/instrumentation-app/wrangler.json delete mode 100644 examples/playground/e2e/playwright.config.ts rename examples/{playground => playground14}/.dev.vars (100%) rename examples/{playground => playground14}/.env.development (100%) rename examples/{playground => playground14}/.gitignore (100%) rename examples/{playground => playground14}/README.md (100%) rename examples/{playground => playground14}/app/api/buildid/route.ts (100%) rename examples/{playground => playground14}/app/api/env/route.ts (100%) rename examples/{playground => playground14}/app/api/hello/route.ts (100%) rename examples/{instrumentation-app/app/api/hello/route.js => playground14/app/api/instrumentation/route.ts} (100%) rename examples/{playground => playground14}/app/api/request/route.ts (100%) rename examples/{playground => playground14}/app/isr/[id]/dynamic/page.tsx (100%) rename examples/{playground => playground14}/app/isr/[id]/no-dynamic/page.tsx (100%) rename examples/{playground => playground14}/app/layout.js (100%) rename examples/{playground => playground14}/app/og/route.tsx (100%) rename examples/{playground => playground14}/app/page.js (100%) rename examples/{playground => playground14}/e2e/base.spec.ts (100%) rename examples/{playground => playground14}/e2e/cloudflare.spec.ts (100%) rename examples/{instrumentation-app => playground14}/e2e/instrumentation.spec.ts (84%) rename examples/{playground => playground14}/e2e/isr.spec.ts (100%) create mode 100644 examples/playground14/e2e/playwright.config.ts rename examples/{playground => playground14}/e2e/playwright.dev.config.ts (68%) rename examples/{instrumentation-app => playground14}/instrumentation.js (100%) rename examples/{instrumentation-app => playground14}/middleware.js (88%) create mode 100644 examples/playground14/next.config.mjs rename examples/{playground => playground14}/open-next.config.ts (100%) rename examples/{playground => playground14}/package.json (96%) rename examples/{playground => playground14}/public/.gitkeep (100%) rename examples/{playground => playground14}/tsconfig.json (100%) rename examples/{playground => playground14}/worker-configuration.d.ts (100%) rename examples/{playground => playground14}/wrangler.json (100%) create mode 100644 examples/playground15/.dev.vars create mode 100644 examples/playground15/.env.development rename examples/{instrumentation-app => playground15}/.gitignore (73%) create mode 100644 examples/playground15/README.md create mode 100644 examples/playground15/app/api/buildid/route.ts create mode 100644 examples/playground15/app/api/env/route.ts create mode 100644 examples/playground15/app/api/hello/route.ts create mode 100644 examples/playground15/app/api/instrumentation/route.ts create mode 100644 examples/playground15/app/api/request/route.ts create mode 100644 examples/playground15/app/isr/[id]/dynamic/page.tsx create mode 100644 examples/playground15/app/isr/[id]/no-dynamic/page.tsx create mode 100644 examples/playground15/app/layout.js create mode 100644 examples/playground15/app/og/route.tsx create mode 100644 examples/playground15/app/page.js create mode 100644 examples/playground15/e2e/base.spec.ts create mode 100644 examples/playground15/e2e/cloudflare.spec.ts create mode 100644 examples/playground15/e2e/instrumentation.spec.ts create mode 100644 examples/playground15/e2e/isr.spec.ts create mode 100644 examples/playground15/e2e/playwright.config.ts create mode 100644 examples/playground15/e2e/playwright.dev.config.ts create mode 100644 examples/playground15/instrumentation.js create mode 100644 examples/playground15/middleware.js rename examples/{playground => playground15}/next.config.mjs (100%) rename examples/{instrumentation-app => playground15}/open-next.config.ts (71%) rename examples/{instrumentation-app => playground15}/package.json (56%) create mode 100644 examples/playground15/public/.gitkeep rename examples/{instrumentation-app => playground15}/tsconfig.json (60%) create mode 100644 examples/playground15/worker-configuration.d.ts create mode 100644 examples/playground15/wrangler.json diff --git a/examples/common/apps.ts b/examples/common/apps.ts index ff96f71e..a9aabccb 100644 --- a/examples/common/apps.ts +++ b/examples/common/apps.ts @@ -3,7 +3,8 @@ const apps = [ // examples "create-next-app", "middleware", - "playground", + "playground14", + "playground15", "vercel-blog-starter", "vercel-commerce", "ssg-app", diff --git a/examples/instrumentation-app/app/layout.tsx b/examples/instrumentation-app/app/layout.tsx deleted file mode 100644 index f3ef34cd..00000000 --- a/examples/instrumentation-app/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} diff --git a/examples/instrumentation-app/app/page.jsx b/examples/instrumentation-app/app/page.jsx deleted file mode 100644 index f913b332..00000000 --- a/examples/instrumentation-app/app/page.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import Link from "next/link"; - -export default function () { - return ( -
-

- See{" "} - - /api/hello - -

-

- See{" "} - - /middleware - -

-
- ); -} diff --git a/examples/instrumentation-app/e2e/playwright.config.ts b/examples/instrumentation-app/e2e/playwright.config.ts deleted file mode 100644 index 947555e2..00000000 --- a/examples/instrumentation-app/e2e/playwright.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { configurePlaywright } from "../../common/config-e2e"; - -export default configurePlaywright("instrumentation-app", { isCI: !!process.env.CI }); diff --git a/examples/instrumentation-app/next.config.ts b/examples/instrumentation-app/next.config.ts deleted file mode 100644 index dce7856f..00000000 --- a/examples/instrumentation-app/next.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { NextConfig } from "next"; -import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; - -initOpenNextCloudflareForDev(); - -const nextConfig: NextConfig = { - /* config options here */ -}; - -export default nextConfig; diff --git a/examples/instrumentation-app/wrangler.json b/examples/instrumentation-app/wrangler.json deleted file mode 100644 index 13d87d90..00000000 --- a/examples/instrumentation-app/wrangler.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "main": ".open-next/worker.js", - "name": "instrumentation-app", - "compatibility_date": "2025-02-04", - "compatibility_flags": ["nodejs_compat"], - "assets": { - "directory": ".open-next/assets", - "binding": "ASSETS" - } -} diff --git a/examples/playground/e2e/playwright.config.ts b/examples/playground/e2e/playwright.config.ts deleted file mode 100644 index 28f57bfc..00000000 --- a/examples/playground/e2e/playwright.config.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { configurePlaywright } from "../../common/config-e2e"; - -export default configurePlaywright("playground", { isCI: !!process.env.CI }); diff --git a/examples/playground/.dev.vars b/examples/playground14/.dev.vars similarity index 100% rename from examples/playground/.dev.vars rename to examples/playground14/.dev.vars diff --git a/examples/playground/.env.development b/examples/playground14/.env.development similarity index 100% rename from examples/playground/.env.development rename to examples/playground14/.env.development diff --git a/examples/playground/.gitignore b/examples/playground14/.gitignore similarity index 100% rename from examples/playground/.gitignore rename to examples/playground14/.gitignore diff --git a/examples/playground/README.md b/examples/playground14/README.md similarity index 100% rename from examples/playground/README.md rename to examples/playground14/README.md diff --git a/examples/playground/app/api/buildid/route.ts b/examples/playground14/app/api/buildid/route.ts similarity index 100% rename from examples/playground/app/api/buildid/route.ts rename to examples/playground14/app/api/buildid/route.ts diff --git a/examples/playground/app/api/env/route.ts b/examples/playground14/app/api/env/route.ts similarity index 100% rename from examples/playground/app/api/env/route.ts rename to examples/playground14/app/api/env/route.ts diff --git a/examples/playground/app/api/hello/route.ts b/examples/playground14/app/api/hello/route.ts similarity index 100% rename from examples/playground/app/api/hello/route.ts rename to examples/playground14/app/api/hello/route.ts diff --git a/examples/instrumentation-app/app/api/hello/route.js b/examples/playground14/app/api/instrumentation/route.ts similarity index 100% rename from examples/instrumentation-app/app/api/hello/route.js rename to examples/playground14/app/api/instrumentation/route.ts diff --git a/examples/playground/app/api/request/route.ts b/examples/playground14/app/api/request/route.ts similarity index 100% rename from examples/playground/app/api/request/route.ts rename to examples/playground14/app/api/request/route.ts diff --git a/examples/playground/app/isr/[id]/dynamic/page.tsx b/examples/playground14/app/isr/[id]/dynamic/page.tsx similarity index 100% rename from examples/playground/app/isr/[id]/dynamic/page.tsx rename to examples/playground14/app/isr/[id]/dynamic/page.tsx diff --git a/examples/playground/app/isr/[id]/no-dynamic/page.tsx b/examples/playground14/app/isr/[id]/no-dynamic/page.tsx similarity index 100% rename from examples/playground/app/isr/[id]/no-dynamic/page.tsx rename to examples/playground14/app/isr/[id]/no-dynamic/page.tsx diff --git a/examples/playground/app/layout.js b/examples/playground14/app/layout.js similarity index 100% rename from examples/playground/app/layout.js rename to examples/playground14/app/layout.js diff --git a/examples/playground/app/og/route.tsx b/examples/playground14/app/og/route.tsx similarity index 100% rename from examples/playground/app/og/route.tsx rename to examples/playground14/app/og/route.tsx diff --git a/examples/playground/app/page.js b/examples/playground14/app/page.js similarity index 100% rename from examples/playground/app/page.js rename to examples/playground14/app/page.js diff --git a/examples/playground/e2e/base.spec.ts b/examples/playground14/e2e/base.spec.ts similarity index 100% rename from examples/playground/e2e/base.spec.ts rename to examples/playground14/e2e/base.spec.ts diff --git a/examples/playground/e2e/cloudflare.spec.ts b/examples/playground14/e2e/cloudflare.spec.ts similarity index 100% rename from examples/playground/e2e/cloudflare.spec.ts rename to examples/playground14/e2e/cloudflare.spec.ts diff --git a/examples/instrumentation-app/e2e/instrumentation.spec.ts b/examples/playground14/e2e/instrumentation.spec.ts similarity index 84% rename from examples/instrumentation-app/e2e/instrumentation.spec.ts rename to examples/playground14/e2e/instrumentation.spec.ts index dfcc4f44..6231a8e9 100644 --- a/examples/instrumentation-app/e2e/instrumentation.spec.ts +++ b/examples/playground14/e2e/instrumentation.spec.ts @@ -3,7 +3,7 @@ import { describe } from "node:test"; test.describe("instrumentation", () => { test("the instrumentation register hook should work for the nodejs runtime", async ({ page }) => { - const res = await page.request.get("/api/hello"); + const res = await page.request.get("/api/instrumentation"); const respJson: Record = await res.json(); expect(respJson["nodejs-instrumentation-setup"]).toEqual( "this value has been set by calling the instrumentation `register` callback in the nodejs runtime" @@ -11,7 +11,7 @@ test.describe("instrumentation", () => { }); test("the instrumentation register hook should work for the edge runtime", async ({ page }) => { - const res = await page.request.get("/middleware"); + const res = await page.request.get("/middleware-instrumentation"); const respJson: Record = await res.json(); expect(respJson["edge-instrumentation-setup"]).toEqual( "this value has been set by calling the instrumentation `register` callback in the edge runtime" @@ -22,13 +22,13 @@ test.describe("instrumentation", () => { // (see: https://github.com/opennextjs/opennextjs-cloudflare/issues/408) describe.skip("isolation", () => { test("the instrumentation register hook nodejs logic should not effect edge routes", async ({ page }) => { - const res = await page.request.get("/middleware"); + const res = await page.request.get("/middleware-instrumentation"); const respJson: Record = await res.json(); expect(respJson["nodejs-instrumentation-setup"]).toEqual("undefined"); }); test("the instrumentation register hook edge logic should not effect nodejs routes", async ({ page }) => { - const res = await page.request.get("/api/hello"); + const res = await page.request.get("/api/instrumentation"); const respJson: Record = await res.json(); expect(respJson["edge-instrumentation-setup"]).toEqual("undefined"); }); diff --git a/examples/playground/e2e/isr.spec.ts b/examples/playground14/e2e/isr.spec.ts similarity index 100% rename from examples/playground/e2e/isr.spec.ts rename to examples/playground14/e2e/isr.spec.ts diff --git a/examples/playground14/e2e/playwright.config.ts b/examples/playground14/e2e/playwright.config.ts new file mode 100644 index 00000000..114012c5 --- /dev/null +++ b/examples/playground14/e2e/playwright.config.ts @@ -0,0 +1,3 @@ +import { configurePlaywright } from "../../common/config-e2e"; + +export default configurePlaywright("playground14", { isCI: !!process.env.CI }); diff --git a/examples/playground/e2e/playwright.dev.config.ts b/examples/playground14/e2e/playwright.dev.config.ts similarity index 68% rename from examples/playground/e2e/playwright.dev.config.ts rename to examples/playground14/e2e/playwright.dev.config.ts index f05725e6..f1b60f3a 100644 --- a/examples/playground/e2e/playwright.dev.config.ts +++ b/examples/playground14/e2e/playwright.dev.config.ts @@ -1,6 +1,6 @@ import { configurePlaywright } from "../../common/config-e2e"; -export default configurePlaywright("playground", { +export default configurePlaywright("playground14", { isCI: !!process.env.CI, isWorker: false, }); diff --git a/examples/instrumentation-app/instrumentation.js b/examples/playground14/instrumentation.js similarity index 100% rename from examples/instrumentation-app/instrumentation.js rename to examples/playground14/instrumentation.js diff --git a/examples/instrumentation-app/middleware.js b/examples/playground14/middleware.js similarity index 88% rename from examples/instrumentation-app/middleware.js rename to examples/playground14/middleware.js index ba945377..4ee953c4 100644 --- a/examples/instrumentation-app/middleware.js +++ b/examples/playground14/middleware.js @@ -8,5 +8,5 @@ export function middleware() { } export const config = { - matcher: ["/middleware"], + matcher: ["/middleware-instrumentation"], }; diff --git a/examples/playground14/next.config.mjs b/examples/playground14/next.config.mjs new file mode 100644 index 00000000..88ff5ae9 --- /dev/null +++ b/examples/playground14/next.config.mjs @@ -0,0 +1,14 @@ +import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare"; + +initOpenNextCloudflareForDev(); + +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + // Generate source map to validate the fix for opennextjs/opennextjs-cloudflare#341 + serverSourceMaps: true, + instrumentationHook: true, + }, +}; + +export default nextConfig; diff --git a/examples/playground/open-next.config.ts b/examples/playground14/open-next.config.ts similarity index 100% rename from examples/playground/open-next.config.ts rename to examples/playground14/open-next.config.ts diff --git a/examples/playground/package.json b/examples/playground14/package.json similarity index 96% rename from examples/playground/package.json rename to examples/playground14/package.json index fcfa6462..6dbec953 100644 --- a/examples/playground/package.json +++ b/examples/playground14/package.json @@ -1,5 +1,5 @@ { - "name": "playground", + "name": "playground14", "version": "0.1.0", "private": true, "type": "module", diff --git a/examples/playground/public/.gitkeep b/examples/playground14/public/.gitkeep similarity index 100% rename from examples/playground/public/.gitkeep rename to examples/playground14/public/.gitkeep diff --git a/examples/playground/tsconfig.json b/examples/playground14/tsconfig.json similarity index 100% rename from examples/playground/tsconfig.json rename to examples/playground14/tsconfig.json diff --git a/examples/playground/worker-configuration.d.ts b/examples/playground14/worker-configuration.d.ts similarity index 100% rename from examples/playground/worker-configuration.d.ts rename to examples/playground14/worker-configuration.d.ts diff --git a/examples/playground/wrangler.json b/examples/playground14/wrangler.json similarity index 100% rename from examples/playground/wrangler.json rename to examples/playground14/wrangler.json diff --git a/examples/playground15/.dev.vars b/examples/playground15/.dev.vars new file mode 100644 index 00000000..17f2dcc2 --- /dev/null +++ b/examples/playground15/.dev.vars @@ -0,0 +1 @@ +NEXTJS_ENV=development \ No newline at end of file diff --git a/examples/playground15/.env.development b/examples/playground15/.env.development new file mode 100644 index 00000000..7eeb777a --- /dev/null +++ b/examples/playground15/.env.development @@ -0,0 +1 @@ +TEST_ENV_VAR=TEST_VALUE \ No newline at end of file diff --git a/examples/instrumentation-app/.gitignore b/examples/playground15/.gitignore similarity index 73% rename from examples/instrumentation-app/.gitignore rename to examples/playground15/.gitignore index 3f753f29..3a282111 100644 --- a/examples/instrumentation-app/.gitignore +++ b/examples/playground15/.gitignore @@ -3,12 +3,8 @@ # dependencies /node_modules /.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions +.pnp.js +.yarn/install-state.gz # testing /coverage @@ -28,10 +24,9 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* -# env files (can opt-in for committing if needed) -.env* +# local env files +.env*.local # vercel .vercel @@ -40,6 +35,9 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts +# wrangler +.wrangler + # playwright /test-results/ /playwright-report/ diff --git a/examples/playground15/README.md b/examples/playground15/README.md new file mode 100644 index 00000000..95904019 --- /dev/null +++ b/examples/playground15/README.md @@ -0,0 +1 @@ +A simple app that only has a single `api/hello` API route (using the `node.js` runtime). diff --git a/examples/playground15/app/api/buildid/route.ts b/examples/playground15/app/api/buildid/route.ts new file mode 100644 index 00000000..0c95b72b --- /dev/null +++ b/examples/playground15/app/api/buildid/route.ts @@ -0,0 +1,9 @@ +// Use headers to force a dynamic response +import { headers } from "next/headers"; + +export async function GET() { + const nextConfig = process.env.__NEXT_PRIVATE_STANDALONE_CONFIG + ? JSON.parse(process.env.__NEXT_PRIVATE_STANDALONE_CONFIG) + : undefined; + return Response.json({ nextConfig, headers: headers() }); +} diff --git a/examples/playground15/app/api/env/route.ts b/examples/playground15/app/api/env/route.ts new file mode 100644 index 00000000..7a49c54d --- /dev/null +++ b/examples/playground15/app/api/env/route.ts @@ -0,0 +1,8 @@ +// This test relies on using `.dev.vars` to set the environment to `development` +// However `next build` is not passed an environment, so we do not want to cache +// the output. +export const dynamic = "force-dynamic"; + +export async function GET() { + return new Response(JSON.stringify(process.env)); +} diff --git a/examples/playground15/app/api/hello/route.ts b/examples/playground15/app/api/hello/route.ts new file mode 100644 index 00000000..35625951 --- /dev/null +++ b/examples/playground15/app/api/hello/route.ts @@ -0,0 +1,21 @@ +import { headers } from "next/headers"; + +import { getCloudflareContext } from "@opennextjs/cloudflare"; + +export async function GET() { + const headersList = await headers(); + + const fromCloudflareContext = headersList.has("from-cloudflare-context"); + + if (!fromCloudflareContext) { + return new Response("Hello World!"); + } + + // Retrieve the bindings defined in wrangler.json + return new Response(getCloudflareContext().env.hello); +} + +export async function POST(request: Request) { + const text = await request.text(); + return new Response(`Hello post-World! body=${text}`); +} diff --git a/examples/playground15/app/api/instrumentation/route.ts b/examples/playground15/app/api/instrumentation/route.ts new file mode 100644 index 00000000..e1f4861e --- /dev/null +++ b/examples/playground15/app/api/instrumentation/route.ts @@ -0,0 +1,8 @@ +import { NextResponse } from "next/server"; + +export function GET() { + return NextResponse.json({ + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + }); +} diff --git a/examples/playground15/app/api/request/route.ts b/examples/playground15/app/api/request/route.ts new file mode 100644 index 00000000..41d73393 --- /dev/null +++ b/examples/playground15/app/api/request/route.ts @@ -0,0 +1,5 @@ +import { NextRequest } from "next/server"; + +export const GET = (request: NextRequest) => { + return new Response(JSON.stringify({ nextUrl: request.nextUrl.href, url: request.url })); +}; diff --git a/examples/playground15/app/isr/[id]/dynamic/page.tsx b/examples/playground15/app/isr/[id]/dynamic/page.tsx new file mode 100644 index 00000000..e7640c65 --- /dev/null +++ b/examples/playground15/app/isr/[id]/dynamic/page.tsx @@ -0,0 +1,30 @@ +// Imported from https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration +interface Post { + id: string; + title: string; + content: string; +} + +// Next.js will invalidate the cache when a +// request comes in, at most once every 1 hour. +export const revalidate = 3600; + +// We'll prerender only the params from `generateStaticParams` at build time. +// If a request comes in for a path that hasn't been generated, +// Next.js will server-render the page on-demand. +export const dynamicParams = true; + +export async function generateStaticParams() { + return [{ id: "1" }, { id: "2" }, { id: "3" }]; +} + +export default async function Page({ params }: { params: Promise<{ id: string }> }) { + const id = (await params).id; + const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) => res.json()); + return ( +
+

{post.title}

+

{post.content}

+
+ ); +} diff --git a/examples/playground15/app/isr/[id]/no-dynamic/page.tsx b/examples/playground15/app/isr/[id]/no-dynamic/page.tsx new file mode 100644 index 00000000..556fb617 --- /dev/null +++ b/examples/playground15/app/isr/[id]/no-dynamic/page.tsx @@ -0,0 +1,29 @@ +// Imported from https://nextjs.org/docs/app/building-your-application/data-fetching/incremental-static-regeneration +interface Post { + id: string; + title: string; + content: string; +} + +// Next.js will invalidate the cache when a +// request comes in, at most once every 1 hour. +export const revalidate = 3600; + +// We'll prerender only the params from `generateStaticParams` at build time. +// If a request comes in for a path that hasn't been generated, it will 404. +export const dynamicParams = false; + +export async function generateStaticParams() { + return [{ id: "1" }, { id: "2" }, { id: "3" }]; +} + +export default async function Page({ params }: { params: Promise<{ id: string }> }) { + const id = (await params).id; + const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then((res) => res.json()); + return ( +
+

{post.title}

+

{post.content}

+
+ ); +} diff --git a/examples/playground15/app/layout.js b/examples/playground15/app/layout.js new file mode 100644 index 00000000..175f2b0a --- /dev/null +++ b/examples/playground15/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: "API hello-world", + description: "a simple api hello-world app", +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/examples/playground15/app/og/route.tsx b/examples/playground15/app/og/route.tsx new file mode 100644 index 00000000..3f48fb0f --- /dev/null +++ b/examples/playground15/app/og/route.tsx @@ -0,0 +1,65 @@ +import { ImageResponse } from "next/og"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + return new ImageResponse( + ( +
+
+ Vercel +
+
+ 'next/og' +
+
+ ), + { + width: 1200, + height: 630, + } + ); + } catch (e: any) { + return new Response("Failed to generate the image", { + status: 500, + }); + } +} diff --git a/examples/playground15/app/page.js b/examples/playground15/app/page.js new file mode 100644 index 00000000..26ca4cad --- /dev/null +++ b/examples/playground15/app/page.js @@ -0,0 +1,7 @@ +export default function Home() { + return ( +
+

Test misc Next features

+
+ ); +} diff --git a/examples/playground15/e2e/base.spec.ts b/examples/playground15/e2e/base.spec.ts new file mode 100644 index 00000000..792e722b --- /dev/null +++ b/examples/playground15/e2e/base.spec.ts @@ -0,0 +1,61 @@ +import { test, expect } from "@playwright/test"; +import type { BinaryLike } from "node:crypto"; +import { createHash } from "node:crypto"; + +const OG_MD5 = "2f7b724d62d8c7739076da211aa62e7b"; + +export function validateMd5(data: Buffer, expectedHash: string) { + return ( + createHash("md5") + .update(data as BinaryLike) + .digest("hex") === expectedHash + ); +} + +test.describe("playground/base", () => { + test("index", async ({ page }) => { + await page.goto("/"); + await expect(page.getByText("Test misc Next features")).toBeVisible(); + }); + + test("the hello-world api GET route works as intended", async ({ page }) => { + const res = await page.request.get("/api/hello"); + expect(res.headers()["content-type"]).toContain("text/plain"); + expect(await res.text()).toEqual("Hello World!"); + }); + + test("returns a hello world string from the cloudflare context env", async ({ page }) => { + const res = await page.request.get("/api/hello", { + headers: { + "from-cloudflare-context": "true", + }, + }); + expect(res.headers()["content-type"]).toContain("text/plain"); + expect(await res.text()).toEqual("Hello World from the cloudflare context!"); + }); + + test("the hello-world api POST route works as intended", async ({ page }) => { + const res = await page.request.post("/api/hello", { data: "some body" }); + expect(res.headers()["content-type"]).toContain("text/plain"); + await expect(res.text()).resolves.toEqual("Hello post-World! body=some body"); + }); + + test("sets environment variables from the Next.js env file", async ({ page }) => { + const res = await page.request.get("/api/env"); + await expect(res.json()).resolves.toEqual(expect.objectContaining({ TEST_ENV_VAR: "TEST_VALUE" })); + }); + + test("returns correct information about the request from a route handler", async ({ page }) => { + const res = await page.request.get("/api/request"); + // Next.js can fall back to `localhost:3000` or `n` if it doesn't get the host - neither of these are expected. + const expectedURL = expect.stringMatching(/https?:\/\/localhost:(?!3000)\d+\/api\/request/); + await expect(res.json()).resolves.toEqual({ nextUrl: expectedURL, url: expectedURL }); + }); + + test.skip("generates an og image successfully", async ({ page }) => { + const res = await page.request.get("/og"); + expect(res.status()).toEqual(200); + expect(res.headers()["content-type"]).toEqual("image/png"); + expect(validateMd5(await res.body(), OG_MD5)).toEqual(true); + }); +}); diff --git a/examples/playground15/e2e/cloudflare.spec.ts b/examples/playground15/e2e/cloudflare.spec.ts new file mode 100644 index 00000000..2b669f99 --- /dev/null +++ b/examples/playground15/e2e/cloudflare.spec.ts @@ -0,0 +1,16 @@ +/** + * Cloudflare specific tests. + * + * The tests in this file do not run on Node (`next dev`). + */ + +import { test, expect } from "@playwright/test"; + +test.describe("playground/cloudflare", () => { + test("NextConfig", async ({ page }) => { + const res = await page.request.get("/api/buildid"); + expect(res.status()).toEqual(200); + const { nextConfig } = await res.json(); + expect(nextConfig.output).toEqual("standalone"); + }); +}); diff --git a/examples/playground15/e2e/instrumentation.spec.ts b/examples/playground15/e2e/instrumentation.spec.ts new file mode 100644 index 00000000..6231a8e9 --- /dev/null +++ b/examples/playground15/e2e/instrumentation.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from "@playwright/test"; +import { describe } from "node:test"; + +test.describe("instrumentation", () => { + test("the instrumentation register hook should work for the nodejs runtime", async ({ page }) => { + const res = await page.request.get("/api/instrumentation"); + const respJson: Record = await res.json(); + expect(respJson["nodejs-instrumentation-setup"]).toEqual( + "this value has been set by calling the instrumentation `register` callback in the nodejs runtime" + ); + }); + + test("the instrumentation register hook should work for the edge runtime", async ({ page }) => { + const res = await page.request.get("/middleware-instrumentation"); + const respJson: Record = await res.json(); + expect(respJson["edge-instrumentation-setup"]).toEqual( + "this value has been set by calling the instrumentation `register` callback in the edge runtime" + ); + }); + + // Note: we cannot test this since currently both runtimes share the same global scope + // (see: https://github.com/opennextjs/opennextjs-cloudflare/issues/408) + describe.skip("isolation", () => { + test("the instrumentation register hook nodejs logic should not effect edge routes", async ({ page }) => { + const res = await page.request.get("/middleware-instrumentation"); + const respJson: Record = await res.json(); + expect(respJson["nodejs-instrumentation-setup"]).toEqual("undefined"); + }); + + test("the instrumentation register hook edge logic should not effect nodejs routes", async ({ page }) => { + const res = await page.request.get("/api/instrumentation"); + const respJson: Record = await res.json(); + expect(respJson["edge-instrumentation-setup"]).toEqual("undefined"); + }); + }); +}); diff --git a/examples/playground15/e2e/isr.spec.ts b/examples/playground15/e2e/isr.spec.ts new file mode 100644 index 00000000..3f4cd869 --- /dev/null +++ b/examples/playground15/e2e/isr.spec.ts @@ -0,0 +1,32 @@ +import { test, expect, type APIResponse } from "@playwright/test"; +import type { BinaryLike } from "node:crypto"; +import { createHash } from "node:crypto"; + +test.describe("playground/isr", () => { + test("Generated pages exist", async ({ page }) => { + const generatedIds = [1, 2, 3]; + let res: APIResponse; + for (const id of generatedIds) { + res = await page.request.get(`/isr/${id}/dynamic`); + expect(res.status()).toBe(200); + res = await page.request.get(`/isr/${id}/no-dynamic`); + expect(res.status()).toBe(200); + } + }); + + test("Non generated pages 404 when dynamic is false", async ({ page }) => { + const generatedIds = [4, 5, 6]; + for (const id of generatedIds) { + const res = await page.request.get(`/isr/${id}/no-dynamic`); + expect(res.status()).toBe(404); + } + }); + + test("Non generated pages are generated when dynamic is true", async ({ page }) => { + const generatedIds = [4, 5, 6]; + for (const id of generatedIds) { + const res = await page.request.get(`/isr/${id}/dynamic`); + expect(res.status()).toBe(200); + } + }); +}); diff --git a/examples/playground15/e2e/playwright.config.ts b/examples/playground15/e2e/playwright.config.ts new file mode 100644 index 00000000..6e19577c --- /dev/null +++ b/examples/playground15/e2e/playwright.config.ts @@ -0,0 +1,3 @@ +import { configurePlaywright } from "../../common/config-e2e"; + +export default configurePlaywright("playground15", { isCI: !!process.env.CI }); diff --git a/examples/playground15/e2e/playwright.dev.config.ts b/examples/playground15/e2e/playwright.dev.config.ts new file mode 100644 index 00000000..ec9cb287 --- /dev/null +++ b/examples/playground15/e2e/playwright.dev.config.ts @@ -0,0 +1,6 @@ +import { configurePlaywright } from "../../common/config-e2e"; + +export default configurePlaywright("playground15", { + isCI: !!process.env.CI, + isWorker: false, +}); diff --git a/examples/playground15/instrumentation.js b/examples/playground15/instrumentation.js new file mode 100644 index 00000000..828854ae --- /dev/null +++ b/examples/playground15/instrumentation.js @@ -0,0 +1,15 @@ +export function register() { + // Note: we register instrumentation for both the nodejs and edge runtime, we do that using the NEXT_RUNTIME env + // variable as recommended in the official docs: + // https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation#importing-runtime-specific-code + + if (process.env.NEXT_RUNTIME === "nodejs") { + globalThis["__NODEJS_INSTRUMENTATION_SETUP"] = + "this value has been set by calling the instrumentation `register` callback in the nodejs runtime"; + } + + if (process.env.NEXT_RUNTIME === "edge") { + globalThis["__EDGE_INSTRUMENTATION_SETUP"] = + "this value has been set by calling the instrumentation `register` callback in the edge runtime"; + } +} diff --git a/examples/playground15/middleware.js b/examples/playground15/middleware.js new file mode 100644 index 00000000..4ee953c4 --- /dev/null +++ b/examples/playground15/middleware.js @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server"; + +export function middleware() { + return NextResponse.json({ + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + }); +} + +export const config = { + matcher: ["/middleware-instrumentation"], +}; diff --git a/examples/playground/next.config.mjs b/examples/playground15/next.config.mjs similarity index 100% rename from examples/playground/next.config.mjs rename to examples/playground15/next.config.mjs diff --git a/examples/instrumentation-app/open-next.config.ts b/examples/playground15/open-next.config.ts similarity index 71% rename from examples/instrumentation-app/open-next.config.ts rename to examples/playground15/open-next.config.ts index e860ec20..ef2e20a9 100644 --- a/examples/instrumentation-app/open-next.config.ts +++ b/examples/playground15/open-next.config.ts @@ -1,15 +1,15 @@ -// default open-next.config.ts file created by @opennextjs/cloudflare - +import type { OpenNextConfig } from "@opennextjs/aws/types/open-next.js"; import cache from "@opennextjs/cloudflare/kv-cache"; -const config = { +const config: OpenNextConfig = { default: { override: { wrapper: "cloudflare-node", converter: "edge", incrementalCache: async () => cache, + queue: "direct", + // Unused implementation tagCache: "dummy", - queue: "dummy", }, }, diff --git a/examples/instrumentation-app/package.json b/examples/playground15/package.json similarity index 56% rename from examples/instrumentation-app/package.json rename to examples/playground15/package.json index 23383cac..29967395 100644 --- a/examples/instrumentation-app/package.json +++ b/examples/playground15/package.json @@ -1,28 +1,28 @@ { - "name": "instrumentation-app", + "name": "playground15", "version": "0.1.0", "private": true, + "type": "module", "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", - "build:worker": "opennextjs-cloudflare", + "build:worker": "pnpm opennextjs-cloudflare", "preview": "pnpm build:worker && pnpm wrangler dev", - "e2e": "playwright test -c e2e/playwright.config.ts" + "e2e": "playwright test -c e2e/playwright.config.ts", + "e2e:dev": "playwright test -c e2e/playwright.dev.config.ts", + "cf-typegen": "wrangler types --env-interface CloudflareEnv" }, "dependencies": { + "next": "^15.1.7", "react": "^19.0.0", - "react-dom": "^19.0.0", - "next": "15.1.7" + "react-dom": "^19.0.0" }, "devDependencies": { "@opennextjs/cloudflare": "workspace:*", "@playwright/test": "catalog:", "@types/node": "catalog:", - "@types/react": "^19", - "@types/react-dom": "^19", - "typescript": "catalog:", "wrangler": "catalog:" } } diff --git a/examples/playground15/public/.gitkeep b/examples/playground15/public/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/examples/instrumentation-app/tsconfig.json b/examples/playground15/tsconfig.json similarity index 60% rename from examples/instrumentation-app/tsconfig.json rename to examples/playground15/tsconfig.json index d8b93235..232e6d61 100644 --- a/examples/instrumentation-app/tsconfig.json +++ b/examples/playground15/tsconfig.json @@ -1,27 +1,24 @@ { "compilerOptions": { - "target": "ES2017", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": true, + "strict": false, "noEmit": true, - "esModuleInterop": true, + "incremental": true, "module": "esnext", - "moduleResolution": "bundler", + "esModuleInterop": true, + "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true, "plugins": [ { "name": "next" } ], - "paths": { - "@/*": ["./*"] - } + "target": "ES2017" }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "worker-configuration.d.ts"], + "exclude": ["node_modules", "open-next.config.ts"] } diff --git a/examples/playground15/worker-configuration.d.ts b/examples/playground15/worker-configuration.d.ts new file mode 100644 index 00000000..7775bd52 --- /dev/null +++ b/examples/playground15/worker-configuration.d.ts @@ -0,0 +1,5 @@ +// Generated by Wrangler by running `wrangler types --env-interface CloudflareEnv` + +interface CloudflareEnv { + hello: "Hello World from the cloudflare context!"; +} diff --git a/examples/playground15/wrangler.json b/examples/playground15/wrangler.json new file mode 100644 index 00000000..53a0fbe6 --- /dev/null +++ b/examples/playground15/wrangler.json @@ -0,0 +1,20 @@ +{ + "$schema": "node_modules/wrangler/config-schema.json", + "main": ".open-next/worker.js", + "name": "api", + "compatibility_date": "2024-12-30", + "compatibility_flags": ["nodejs_compat"], + "assets": { + "directory": ".open-next/assets", + "binding": "ASSETS" + }, + "kv_namespaces": [ + { + "binding": "NEXT_CACHE_WORKERS_KV", + "id": "" + } + ], + "vars": { + "hello": "Hello World from the cloudflare context!" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9830c2c1..17f81e65 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -582,40 +582,6 @@ importers: specifier: catalog:e2e version: 19.0.0 - examples/instrumentation-app: - dependencies: - next: - specifier: 15.1.7 - version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.47.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) - react: - specifier: ^19.0.0 - version: 19.0.0 - react-dom: - specifier: ^19.0.0 - version: 19.0.0(react@19.0.0) - devDependencies: - '@opennextjs/cloudflare': - specifier: workspace:* - version: link:../../packages/cloudflare - '@playwright/test': - specifier: 'catalog:' - version: 1.47.0 - '@types/node': - specifier: 'catalog:' - version: 22.2.0 - '@types/react': - specifier: ^19 - version: 19.0.8 - '@types/react-dom': - specifier: ^19 - version: 19.0.3(@types/react@19.0.8) - typescript: - specifier: 'catalog:' - version: 5.7.3 - wrangler: - specifier: 'catalog:' - version: 3.107.3(@cloudflare/workers-types@4.20250109.0) - examples/middleware: dependencies: '@clerk/nextjs': @@ -717,7 +683,7 @@ importers: specifier: 'catalog:' version: 3.107.3(@cloudflare/workers-types@4.20250109.0) - examples/playground: + examples/playground14: dependencies: next: specifier: 'catalog:' @@ -742,6 +708,31 @@ importers: specifier: 'catalog:' version: 3.107.3(@cloudflare/workers-types@4.20250109.0) + examples/playground15: + dependencies: + next: + specifier: ^15.1.7 + version: 15.1.7(@opentelemetry/api@1.9.0)(@playwright/test@1.47.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: + specifier: ^19.0.0 + version: 19.0.0 + react-dom: + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) + devDependencies: + '@opennextjs/cloudflare': + specifier: workspace:* + version: link:../../packages/cloudflare + '@playwright/test': + specifier: 'catalog:' + version: 1.47.0 + '@types/node': + specifier: 'catalog:' + version: 22.2.0 + wrangler: + specifier: 'catalog:' + version: 3.107.3(@cloudflare/workers-types@4.20250109.0) + examples/ssg-app: dependencies: next: @@ -9073,9 +9064,6 @@ packages: tslib@2.4.1: resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -15371,7 +15359,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 dotenv@16.4.7: {} @@ -17807,7 +17795,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 lru-cache@10.4.3: {} @@ -18451,7 +18439,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.8.1 node-abi@3.73.0: dependencies: @@ -19510,7 +19498,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 snakecase-keys@5.4.4: dependencies: @@ -20165,8 +20153,6 @@ snapshots: tslib@2.4.1: {} - tslib@2.6.3: {} - tslib@2.8.1: {} tsx@4.19.2: From aa4d8fa08c377f3990feb27bcde92b5aa2895042 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 10:31:06 +0000 Subject: [PATCH 06/17] clean up load-instrumentation spec file --- .../plugins/load-instrumentation.spec.ts | 109 ++++++------------ 1 file changed, 35 insertions(+), 74 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts index ed2cc2aa..44028f38 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts @@ -14,87 +14,48 @@ vi.mock(import("node:fs"), async (importOriginal) => { }); describe("LoadInstrumentationModule", () => { - test("patch when an instrumentation file is not present", async () => { - const code = ` -export default class NextNodeServer extends BaseServer< - Options, - NodeNextRequest, - NodeNextResponse -> { - protected async loadInstrumentationModule() { - if (!this.serverOptions.dev) { - try { - this.instrumentation = await dynamicRequire( - resolve( - this.serverOptions.dir || '.', - this.serverOptions.conf.distDir!, - 'server', - INSTRUMENTATION_HOOK_FILENAME - ) - ) - } catch (err: any) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw new Error( - 'An error occurred while loading the instrumentation hook', - { cause: err } - ) + const code = ` + export default class NextNodeServer extends BaseServer { + protected async loadInstrumentationModule() { + if (!this.serverOptions.dev) { + try { + this.instrumentation = await dynamicRequire( + resolve( + this.serverOptions.dir || '.', + this.serverOptions.conf.distDir!, + 'server', + INSTRUMENTATION_HOOK_FILENAME + ) + ) + } catch (err: any) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw new Error( + 'An error occurred while loading the instrumentation hook', + { cause: err } + ) + } + } + } + return this.instrumentation } } - } - return this.instrumentation - } -}`; + `; + test("patch when an instrumentation file is not present", async () => { expect(patchCode(code, await getRule(null))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer< - Options, - NodeNextRequest, - NodeNextResponse - > { - async loadInstrumentationModule() { this.instrumentation = null; return this.instrumentation; } - }" - `); + "export default class NextNodeServer extends BaseServer { + async loadInstrumentationModule() { this.instrumentation = null; return this.instrumentation; } + } + " + `); }); test("patch when an instrumentation file is present", async () => { - const code = ` -export default class NextNodeServer extends BaseServer< - Options, - NodeNextRequest, - NodeNextResponse -> { - protected async loadInstrumentationModule() { - if (!this.serverOptions.dev) { - try { - this.instrumentation = await dynamicRequire( - resolve( - this.serverOptions.dir || '.', - this.serverOptions.conf.distDir!, - 'server', - INSTRUMENTATION_HOOK_FILENAME - ) - ) - } catch (err: any) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw new Error( - 'An error occurred while loading the instrumentation hook', - { cause: err } - ) - } - } - } - return this.instrumentation - } -}`; - expect(patchCode(code, await getRule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer< - Options, - NodeNextRequest, - NodeNextResponse - > { - async loadInstrumentationModule() { this.instrumentation = require('/_file_exists_/instrumentation.js'); return this.instrumentation; } - }" - `); + "export default class NextNodeServer extends BaseServer { + async loadInstrumentationModule() { this.instrumentation = require('/_file_exists_/instrumentation.js'); return this.instrumentation; } + } + " + `); }); }); From 42b43e6e3801d01bc7f74ec641936337ca667545 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 10:47:47 +0000 Subject: [PATCH 07/17] make sure instrumentation works with Next 14 --- .../app/api/instrumentation/route.ts | 2 + .../cloudflare/src/cli/build/bundle-server.ts | 2 + .../patches/plugins/prepare-impl.spec.ts | 61 +++++++++++++++++++ .../cli/build/patches/plugins/prepare-impl.ts | 54 ++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts diff --git a/examples/playground14/app/api/instrumentation/route.ts b/examples/playground14/app/api/instrumentation/route.ts index e1f4861e..a884d5ec 100644 --- a/examples/playground14/app/api/instrumentation/route.ts +++ b/examples/playground14/app/api/instrumentation/route.ts @@ -1,5 +1,7 @@ import { NextResponse } from "next/server"; +export const dynamic = "force-dynamic"; + export function GET() { return NextResponse.json({ "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 692e2eda..de261218 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -17,6 +17,7 @@ import { inlineFindDir } from "./patches/plugins/find-dir.js"; import { patchLoadInstrumentation } from "./patches/plugins/load-instrumentation.js"; import { inlineLoadManifest } from "./patches/plugins/load-manifest.js"; import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js"; +import { patchPrepareImpl } from "./patches/plugins/prepare-impl.js"; import { fixRequire } from "./patches/plugins/require.js"; import { shimRequireHook } from "./patches/plugins/require-hook.js"; import { inlineRequirePage } from "./patches/plugins/require-page.js"; @@ -92,6 +93,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { fixRequire(updater), handleOptionalDependencies(optionalDependencies), patchLoadInstrumentation(updater, buildOpts), + patchPrepareImpl(updater, buildOpts), patchFetchCacheSetMissingWaitUntil(updater), inlineEvalManifest(updater, buildOpts), inlineFindDir(updater, buildOpts), diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts new file mode 100644 index 00000000..89350325 --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts @@ -0,0 +1,61 @@ +import { describe, expect, test, vi } from "vitest"; + +import { patchCode } from "../ast/util.js"; +import { getRule } from "./prepare-impl.js"; + +vi.mock(import("node:fs"), async (importOriginal) => { + const mod = await importOriginal(); + return { + ...mod, + existsSync(path) { + return `${path}`.includes("_file_exists_"); + }, + }; +}); + +describe("prepareImpl", () => { + const code = ` + export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + if (!this.serverOptions.dev && this.nextConfig.experimental.instrumentationHook) { + try { + const instrumentationHook = await dynamicRequire((0, _path.resolve)(this.serverOptions.dir || ".", this.serverOptions.conf.distDir, "server", _constants1.INSTRUMENTATION_HOOK_FILENAME)); + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } catch (err2) { + if (err2.code !== "MODULE_NOT_FOUND") { + err2.message = \`An error occurred while loading instrumentation hook: \${err2.message}\`; + throw err2; + } + } + } + } + } + `; + + test("patch when an instrumentation file is not present", async () => { + expect(patchCode(code, await getRule(null))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + const instrumentationHook = {}; + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + } + " + `); + }); + + test("patch when an instrumentation file is present", async () => { + expect(patchCode(code, await getRule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + const instrumentationHook = require('/_file_exists_/instrumentation.js'); + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + } + " + `); + }); +}); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts new file mode 100644 index 00000000..20ffa1af --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts @@ -0,0 +1,54 @@ +/** + * `prepareImpl` uses a dynamic require which is not supported. + * + * `prepareImpl` is the method that sets up instrumentation in Next 14 (this is `loadInstrumentationModule` in Next 15). + */ + +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; + +import { patchCode } from "../ast/util.js"; +import type { ContentUpdater } from "./content-updater.js"; + +export function patchPrepareImpl(updater: ContentUpdater, buildOpts: BuildOptions) { + const { outputDir } = buildOpts; + + const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); + const dotNextDir = join(baseDir, ".next"); + const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) + ? maybeBuiltInstrumentationPath + : null; + + return updater.updateContent( + "patch-prepareImpl", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, + async ({ contents }) => patchCode(contents, await getRule(builtInstrumentationPath)) + ); +} + +export async function getRule(builtInstrumentationPath: string | null) { + return ` + rule: + kind: method_definition + any: + - has: { field: name, regex: ^prepareImpl$, pattern: $NAME } + all: + - has: { pattern: dynamicRequire, stopBy: end } + - has: { pattern: $_.INSTRUMENTATION_HOOK_FILENAME, stopBy: end } + fix: |- + async $NAME() { + await super.prepareImpl(); + const instrumentationHook = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "{}"}; + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + `; +} + +/** + * Pattern to detect instrumentation hooks file + * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) + */ +const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; From dae0e4677fcef718e431663ae5b05b6628451ccd Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 10:55:19 +0000 Subject: [PATCH 08/17] add `getBuiltInstrumentationPath` utility --- .../patches/plugins/load-instrumentation.ts | 21 ++----------- .../cli/build/patches/plugins/prepare-impl.ts | 21 ++----------- .../utils/get-built-instrumentation-path.ts | 30 +++++++++++++++++++ 3 files changed, 36 insertions(+), 36 deletions(-) create mode 100644 packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts index d1658f83..6776e689 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts @@ -2,23 +2,14 @@ * `loadInstrumentationModule` uses a dynamic require which is not supported. */ -import { existsSync } from "node:fs"; -import { join } from "node:path"; - -import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; +import { type BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { getBuiltInstrumentationPath } from "../../utils/get-built-instrumentation-path.js"; import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { - const { outputDir } = buildOpts; - - const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); - const dotNextDir = join(baseDir, ".next"); - const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); - const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) - ? maybeBuiltInstrumentationPath - : null; + const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); return updater.updateContent( "patch-load-instrumentation", @@ -42,9 +33,3 @@ export async function getRule(builtInstrumentationPath: string | null) { } `; } - -/** - * Pattern to detect instrumentation hooks file - * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) - */ -const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts index 20ffa1af..0bac6431 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts @@ -4,23 +4,14 @@ * `prepareImpl` is the method that sets up instrumentation in Next 14 (this is `loadInstrumentationModule` in Next 15). */ -import { existsSync } from "node:fs"; -import { join } from "node:path"; - -import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; +import { type BuildOptions } from "@opennextjs/aws/build/helper.js"; +import { getBuiltInstrumentationPath } from "../../utils/get-built-instrumentation-path.js"; import { patchCode } from "../ast/util.js"; import type { ContentUpdater } from "./content-updater.js"; export function patchPrepareImpl(updater: ContentUpdater, buildOpts: BuildOptions) { - const { outputDir } = buildOpts; - - const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); - const dotNextDir = join(baseDir, ".next"); - const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); - const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) - ? maybeBuiltInstrumentationPath - : null; + const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); return updater.updateContent( "patch-prepareImpl", @@ -46,9 +37,3 @@ export async function getRule(builtInstrumentationPath: string | null) { } `; } - -/** - * Pattern to detect instrumentation hooks file - * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) - */ -const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; diff --git a/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts b/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts new file mode 100644 index 00000000..0a618940 --- /dev/null +++ b/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts @@ -0,0 +1,30 @@ +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; + +/** + * Gets the instrumentation.js file that the Next.js build process generates when an + * instrumentation hook is provided in the app's source + * + * @param buildOpts the open-next build options + * @returns a string pointing to the instrumentation.js file location, or null if such file is not found + */ +export function getBuiltInstrumentationPath(buildOpts: BuildOptions): string | null { + const { outputDir } = buildOpts; + + const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); + const dotNextDir = join(baseDir, ".next"); + const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) + ? maybeBuiltInstrumentationPath + : null; + + return builtInstrumentationPath; +} + +/** + * Pattern to detect instrumentation hooks file + * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) + */ +const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; From 756b3b1870d2cb7897e70ab8a2822c6b6b071656 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 11:06:33 +0000 Subject: [PATCH 09/17] remove instrumentation-app from the list --- examples/common/apps.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/common/apps.ts b/examples/common/apps.ts index a9aabccb..ee320f23 100644 --- a/examples/common/apps.ts +++ b/examples/common/apps.ts @@ -8,7 +8,6 @@ const apps = [ "vercel-blog-starter", "vercel-commerce", "ssg-app", - "instrumentation-app", // e2e "app-pages-router", "app-router", From a515b1aea3c912fb066657956fc7224bdcf31cef Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 14:55:26 +0000 Subject: [PATCH 10/17] fix accidentally updated tsconfig.json --- examples/middleware/tsconfig.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 examples/middleware/tsconfig.json diff --git a/examples/middleware/tsconfig.json b/examples/middleware/tsconfig.json old mode 100644 new mode 100755 From 7478fed9f3fe0325b70a25a2343c115b3e0c86a4 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 14:57:35 +0000 Subject: [PATCH 11/17] remove playground readmes --- examples/playground14/README.md | 1 - examples/playground15/README.md | 1 - 2 files changed, 2 deletions(-) delete mode 100644 examples/playground14/README.md delete mode 100644 examples/playground15/README.md diff --git a/examples/playground14/README.md b/examples/playground14/README.md deleted file mode 100644 index 95904019..00000000 --- a/examples/playground14/README.md +++ /dev/null @@ -1 +0,0 @@ -A simple app that only has a single `api/hello` API route (using the `node.js` runtime). diff --git a/examples/playground15/README.md b/examples/playground15/README.md deleted file mode 100644 index 95904019..00000000 --- a/examples/playground15/README.md +++ /dev/null @@ -1 +0,0 @@ -A simple app that only has a single `api/hello` API route (using the `node.js` runtime). From edd669392bbe1e2c9733ff431583b6596d07f8cf Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 14:58:59 +0000 Subject: [PATCH 12/17] remove ?? "undefined" --- examples/playground14/app/api/instrumentation/route.ts | 4 ++-- examples/playground14/e2e/instrumentation.spec.ts | 4 ++-- examples/playground14/middleware.js | 4 ++-- examples/playground15/app/api/instrumentation/route.ts | 4 ++-- examples/playground15/e2e/instrumentation.spec.ts | 4 ++-- examples/playground15/middleware.js | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/playground14/app/api/instrumentation/route.ts b/examples/playground14/app/api/instrumentation/route.ts index a884d5ec..f47c1632 100644 --- a/examples/playground14/app/api/instrumentation/route.ts +++ b/examples/playground14/app/api/instrumentation/route.ts @@ -4,7 +4,7 @@ export const dynamic = "force-dynamic"; export function GET() { return NextResponse.json({ - "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", - "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"], + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"], }); } diff --git a/examples/playground14/e2e/instrumentation.spec.ts b/examples/playground14/e2e/instrumentation.spec.ts index 6231a8e9..7dfdf390 100644 --- a/examples/playground14/e2e/instrumentation.spec.ts +++ b/examples/playground14/e2e/instrumentation.spec.ts @@ -24,13 +24,13 @@ test.describe("instrumentation", () => { test("the instrumentation register hook nodejs logic should not effect edge routes", async ({ page }) => { const res = await page.request.get("/middleware-instrumentation"); const respJson: Record = await res.json(); - expect(respJson["nodejs-instrumentation-setup"]).toEqual("undefined"); + expect(respJson["nodejs-instrumentation-setup"]).toBeUndefined(); }); test("the instrumentation register hook edge logic should not effect nodejs routes", async ({ page }) => { const res = await page.request.get("/api/instrumentation"); const respJson: Record = await res.json(); - expect(respJson["edge-instrumentation-setup"]).toEqual("undefined"); + expect(respJson["edge-instrumentation-setup"]).toBeUndefined(); }); }); }); diff --git a/examples/playground14/middleware.js b/examples/playground14/middleware.js index 4ee953c4..eef686e8 100644 --- a/examples/playground14/middleware.js +++ b/examples/playground14/middleware.js @@ -2,8 +2,8 @@ import { NextResponse } from "next/server"; export function middleware() { return NextResponse.json({ - "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", - "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"], + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"], }); } diff --git a/examples/playground15/app/api/instrumentation/route.ts b/examples/playground15/app/api/instrumentation/route.ts index e1f4861e..ddf80b59 100644 --- a/examples/playground15/app/api/instrumentation/route.ts +++ b/examples/playground15/app/api/instrumentation/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export function GET() { return NextResponse.json({ - "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", - "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"], + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"], }); } diff --git a/examples/playground15/e2e/instrumentation.spec.ts b/examples/playground15/e2e/instrumentation.spec.ts index 6231a8e9..7dfdf390 100644 --- a/examples/playground15/e2e/instrumentation.spec.ts +++ b/examples/playground15/e2e/instrumentation.spec.ts @@ -24,13 +24,13 @@ test.describe("instrumentation", () => { test("the instrumentation register hook nodejs logic should not effect edge routes", async ({ page }) => { const res = await page.request.get("/middleware-instrumentation"); const respJson: Record = await res.json(); - expect(respJson["nodejs-instrumentation-setup"]).toEqual("undefined"); + expect(respJson["nodejs-instrumentation-setup"]).toBeUndefined(); }); test("the instrumentation register hook edge logic should not effect nodejs routes", async ({ page }) => { const res = await page.request.get("/api/instrumentation"); const respJson: Record = await res.json(); - expect(respJson["edge-instrumentation-setup"]).toEqual("undefined"); + expect(respJson["edge-instrumentation-setup"]).toBeUndefined(); }); }); }); diff --git a/examples/playground15/middleware.js b/examples/playground15/middleware.js index 4ee953c4..eef686e8 100644 --- a/examples/playground15/middleware.js +++ b/examples/playground15/middleware.js @@ -2,8 +2,8 @@ import { NextResponse } from "next/server"; export function middleware() { return NextResponse.json({ - "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"] ?? "undefined", - "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"] ?? "undefined", + "nodejs-instrumentation-setup": globalThis["__NODEJS_INSTRUMENTATION_SETUP"], + "edge-instrumentation-setup": globalThis["__EDGE_INSTRUMENTATION_SETUP"], }); } From b964ada51b02cc8b3828c14aa1348c912a257778 Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 14:59:45 +0000 Subject: [PATCH 13/17] remove leftover mocking --- .../patches/plugins/load-instrumentation.spec.ts | 12 +----------- .../cli/build/patches/plugins/prepare-impl.spec.ts | 12 +----------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts index 44028f38..56b5c43f 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts @@ -1,18 +1,8 @@ -import { describe, expect, test, vi } from "vitest"; +import { describe, expect, test } from "vitest"; import { patchCode } from "../ast/util.js"; import { getRule } from "./load-instrumentation.js"; -vi.mock(import("node:fs"), async (importOriginal) => { - const mod = await importOriginal(); - return { - ...mod, - existsSync(path) { - return `${path}`.includes("_file_exists_"); - }, - }; -}); - describe("LoadInstrumentationModule", () => { const code = ` export default class NextNodeServer extends BaseServer { diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts index 89350325..3fd9b24d 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts @@ -1,18 +1,8 @@ -import { describe, expect, test, vi } from "vitest"; +import { describe, expect, test } from "vitest"; import { patchCode } from "../ast/util.js"; import { getRule } from "./prepare-impl.js"; -vi.mock(import("node:fs"), async (importOriginal) => { - const mod = await importOriginal(); - return { - ...mod, - existsSync(path) { - return `${path}`.includes("_file_exists_"); - }, - }; -}); - describe("prepareImpl", () => { const code = ` export default class NextNodeServer extends BaseServer { From e759db93537b1f583b956bed2859fdc6710f3fac Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 15:00:31 +0000 Subject: [PATCH 14/17] remove leftover no-longer necessary async await --- .../src/cli/build/patches/plugins/load-instrumentation.ts | 4 ++-- .../cloudflare/src/cli/build/patches/plugins/prepare-impl.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts index 6776e689..301b761c 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts @@ -14,11 +14,11 @@ export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: Bui return updater.updateContent( "patch-load-instrumentation", { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, - async ({ contents }) => patchCode(contents, await getRule(builtInstrumentationPath)) + async ({ contents }) => patchCode(contents, getRule(builtInstrumentationPath)) ); } -export async function getRule(builtInstrumentationPath: string | null) { +export function getRule(builtInstrumentationPath: string | null) { return ` rule: kind: method_definition diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts index 0bac6431..25d55a3c 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts @@ -16,11 +16,11 @@ export function patchPrepareImpl(updater: ContentUpdater, buildOpts: BuildOption return updater.updateContent( "patch-prepareImpl", { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, - async ({ contents }) => patchCode(contents, await getRule(builtInstrumentationPath)) + async ({ contents }) => patchCode(contents, getRule(builtInstrumentationPath)) ); } -export async function getRule(builtInstrumentationPath: string | null) { +export function getRule(builtInstrumentationPath: string | null) { return ` rule: kind: method_definition From f77ff61d8380b080a47fee990e84cbfd3e4a4e3b Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 15:19:41 +0000 Subject: [PATCH 15/17] move instrumentation patches into a single file --- .../cloudflare/src/cli/build/bundle-server.ts | 6 +- .../patches/plugins/instrumentation.spec.ts | 98 +++++++++++++++++++ .../build/patches/plugins/instrumentation.ts | 84 ++++++++++++++++ .../plugins/load-instrumentation.spec.ts | 51 ---------- .../patches/plugins/load-instrumentation.ts | 35 ------- .../patches/plugins/prepare-impl.spec.ts | 51 ---------- .../cli/build/patches/plugins/prepare-impl.ts | 39 -------- .../utils/get-built-instrumentation-path.ts | 30 ------ 8 files changed, 184 insertions(+), 210 deletions(-) create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/instrumentation.spec.ts create mode 100644 packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts delete mode 100644 packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts delete mode 100644 packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts delete mode 100644 packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts delete mode 100644 packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts delete mode 100644 packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index de261218..25bf7462 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -14,10 +14,9 @@ import { ContentUpdater } from "./patches/plugins/content-updater.js"; import { inlineEvalManifest } from "./patches/plugins/eval-manifest.js"; import { patchFetchCacheSetMissingWaitUntil } from "./patches/plugins/fetch-cache-wait-until.js"; import { inlineFindDir } from "./patches/plugins/find-dir.js"; -import { patchLoadInstrumentation } from "./patches/plugins/load-instrumentation.js"; +import { patchInstrumentation } from "./patches/plugins/instrumentation.js"; import { inlineLoadManifest } from "./patches/plugins/load-manifest.js"; import { handleOptionalDependencies } from "./patches/plugins/optional-deps.js"; -import { patchPrepareImpl } from "./patches/plugins/prepare-impl.js"; import { fixRequire } from "./patches/plugins/require.js"; import { shimRequireHook } from "./patches/plugins/require-hook.js"; import { inlineRequirePage } from "./patches/plugins/require-page.js"; @@ -92,8 +91,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { setWranglerExternal(), fixRequire(updater), handleOptionalDependencies(optionalDependencies), - patchLoadInstrumentation(updater, buildOpts), - patchPrepareImpl(updater, buildOpts), + ...patchInstrumentation(updater, buildOpts), patchFetchCacheSetMissingWaitUntil(updater), inlineEvalManifest(updater, buildOpts), inlineFindDir(updater, buildOpts), diff --git a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.spec.ts new file mode 100644 index 00000000..321cbf3b --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.spec.ts @@ -0,0 +1,98 @@ +import { describe, expect, test } from "vitest"; + +import { patchCode } from "../ast/util.js"; +import { getNext14Rule, getNext15Rule } from "./instrumentation.js"; + +describe("LoadInstrumentationModule (Next15)", () => { + const code = ` + export default class NextNodeServer extends BaseServer { + protected async loadInstrumentationModule() { + if (!this.serverOptions.dev) { + try { + this.instrumentation = await dynamicRequire( + resolve( + this.serverOptions.dir || '.', + this.serverOptions.conf.distDir!, + 'server', + INSTRUMENTATION_HOOK_FILENAME + ) + ) + } catch (err: any) { + if (err.code !== 'MODULE_NOT_FOUND') { + throw new Error( + 'An error occurred while loading the instrumentation hook', + { cause: err } + ) + } + } + } + return this.instrumentation + } + } + `; + + test("patch when an instrumentation file is not present", async () => { + expect(patchCode(code, getNext15Rule(null))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async loadInstrumentationModule() { this.instrumentation = null; return this.instrumentation; } + } + " + `); + }); + + test("patch when an instrumentation file is present", async () => { + expect(patchCode(code, getNext15Rule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async loadInstrumentationModule() { this.instrumentation = require('/_file_exists_/instrumentation.js'); return this.instrumentation; } + } + " + `); + }); +}); + +describe("prepareImpl (Next14)", () => { + const code = ` + export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + if (!this.serverOptions.dev && this.nextConfig.experimental.instrumentationHook) { + try { + const instrumentationHook = await dynamicRequire((0, _path.resolve)(this.serverOptions.dir || ".", this.serverOptions.conf.distDir, "server", _constants1.INSTRUMENTATION_HOOK_FILENAME)); + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } catch (err2) { + if (err2.code !== "MODULE_NOT_FOUND") { + err2.message = \`An error occurred while loading instrumentation hook: \${err2.message}\`; + throw err2; + } + } + } + } + } + `; + + test("patch when an instrumentation file is not present", async () => { + expect(patchCode(code, getNext14Rule(null))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + const instrumentationHook = {}; + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + } + " + `); + }); + + test("patch when an instrumentation file is present", async () => { + expect(patchCode(code, getNext14Rule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` + "export default class NextNodeServer extends BaseServer { + async prepareImpl() { + await super.prepareImpl(); + const instrumentationHook = require('/_file_exists_/instrumentation.js'); + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + } + " + `); + }); +}); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts new file mode 100644 index 00000000..4e0ee9df --- /dev/null +++ b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts @@ -0,0 +1,84 @@ +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; + +import { patchCode } from "../ast/util.js"; +import type { ContentUpdater } from "./content-updater.js"; + +export function patchInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { + const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); + + return [ + updater.updateContent( + "patch-load-instrumentation", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, + async ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)) + ), + updater.updateContent( + "patch-prepareImpl", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, + async ({ contents }) => patchCode(contents, getNext14Rule(builtInstrumentationPath)) + ), + ]; +} + +export function getNext15Rule(builtInstrumentationPath: string | null) { + return ` + rule: + kind: method_definition + all: + - has: {field: name, regex: ^loadInstrumentationModule$} + - has: {pattern: dynamicRequire, stopBy: end} + + fix: + async loadInstrumentationModule() { + this.instrumentation = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "null"}; + return this.instrumentation; + } + `; +} + +export function getNext14Rule(builtInstrumentationPath: string | null) { + return ` + rule: + kind: method_definition + any: + - has: { field: name, regex: ^prepareImpl$, pattern: $NAME } + all: + - has: { pattern: dynamicRequire, stopBy: end } + - has: { pattern: $_.INSTRUMENTATION_HOOK_FILENAME, stopBy: end } + fix: |- + async $NAME() { + await super.prepareImpl(); + const instrumentationHook = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "{}"}; + await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); + } + `; +} + +/** + * Gets the instrumentation.js file that the Next.js build process generates when an + * instrumentation hook is provided in the app's source + * + * @param buildOpts the open-next build options + * @returns a string pointing to the instrumentation.js file location, or null if such file is not found + */ +function getBuiltInstrumentationPath(buildOpts: BuildOptions): string | null { + const { outputDir } = buildOpts; + + const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); + const dotNextDir = join(baseDir, ".next"); + const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); + const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) + ? maybeBuiltInstrumentationPath + : null; + + return builtInstrumentationPath; +} + +/** + * Pattern to detect instrumentation hooks file + * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) + */ +const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts deleted file mode 100644 index 56b5c43f..00000000 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { patchCode } from "../ast/util.js"; -import { getRule } from "./load-instrumentation.js"; - -describe("LoadInstrumentationModule", () => { - const code = ` - export default class NextNodeServer extends BaseServer { - protected async loadInstrumentationModule() { - if (!this.serverOptions.dev) { - try { - this.instrumentation = await dynamicRequire( - resolve( - this.serverOptions.dir || '.', - this.serverOptions.conf.distDir!, - 'server', - INSTRUMENTATION_HOOK_FILENAME - ) - ) - } catch (err: any) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw new Error( - 'An error occurred while loading the instrumentation hook', - { cause: err } - ) - } - } - } - return this.instrumentation - } - } - `; - - test("patch when an instrumentation file is not present", async () => { - expect(patchCode(code, await getRule(null))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer { - async loadInstrumentationModule() { this.instrumentation = null; return this.instrumentation; } - } - " - `); - }); - - test("patch when an instrumentation file is present", async () => { - expect(patchCode(code, await getRule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer { - async loadInstrumentationModule() { this.instrumentation = require('/_file_exists_/instrumentation.js'); return this.instrumentation; } - } - " - `); - }); -}); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts deleted file mode 100644 index 301b761c..00000000 --- a/packages/cloudflare/src/cli/build/patches/plugins/load-instrumentation.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * `loadInstrumentationModule` uses a dynamic require which is not supported. - */ - -import { type BuildOptions } from "@opennextjs/aws/build/helper.js"; - -import { getBuiltInstrumentationPath } from "../../utils/get-built-instrumentation-path.js"; -import { patchCode } from "../ast/util.js"; -import type { ContentUpdater } from "./content-updater.js"; - -export function patchLoadInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { - const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); - - return updater.updateContent( - "patch-load-instrumentation", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, - async ({ contents }) => patchCode(contents, getRule(builtInstrumentationPath)) - ); -} - -export function getRule(builtInstrumentationPath: string | null) { - return ` - rule: - kind: method_definition - all: - - has: {field: name, regex: ^loadInstrumentationModule$} - - has: {pattern: dynamicRequire, stopBy: end} - - fix: - async loadInstrumentationModule() { - this.instrumentation = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "null"}; - return this.instrumentation; - } - `; -} diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts deleted file mode 100644 index 3fd9b24d..00000000 --- a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { describe, expect, test } from "vitest"; - -import { patchCode } from "../ast/util.js"; -import { getRule } from "./prepare-impl.js"; - -describe("prepareImpl", () => { - const code = ` - export default class NextNodeServer extends BaseServer { - async prepareImpl() { - await super.prepareImpl(); - if (!this.serverOptions.dev && this.nextConfig.experimental.instrumentationHook) { - try { - const instrumentationHook = await dynamicRequire((0, _path.resolve)(this.serverOptions.dir || ".", this.serverOptions.conf.distDir, "server", _constants1.INSTRUMENTATION_HOOK_FILENAME)); - await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); - } catch (err2) { - if (err2.code !== "MODULE_NOT_FOUND") { - err2.message = \`An error occurred while loading instrumentation hook: \${err2.message}\`; - throw err2; - } - } - } - } - } - `; - - test("patch when an instrumentation file is not present", async () => { - expect(patchCode(code, await getRule(null))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer { - async prepareImpl() { - await super.prepareImpl(); - const instrumentationHook = {}; - await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); - } - } - " - `); - }); - - test("patch when an instrumentation file is present", async () => { - expect(patchCode(code, await getRule("/_file_exists_/instrumentation.js"))).toMatchInlineSnapshot(` - "export default class NextNodeServer extends BaseServer { - async prepareImpl() { - await super.prepareImpl(); - const instrumentationHook = require('/_file_exists_/instrumentation.js'); - await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); - } - } - " - `); - }); -}); diff --git a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts b/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts deleted file mode 100644 index 25d55a3c..00000000 --- a/packages/cloudflare/src/cli/build/patches/plugins/prepare-impl.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * `prepareImpl` uses a dynamic require which is not supported. - * - * `prepareImpl` is the method that sets up instrumentation in Next 14 (this is `loadInstrumentationModule` in Next 15). - */ - -import { type BuildOptions } from "@opennextjs/aws/build/helper.js"; - -import { getBuiltInstrumentationPath } from "../../utils/get-built-instrumentation-path.js"; -import { patchCode } from "../ast/util.js"; -import type { ContentUpdater } from "./content-updater.js"; - -export function patchPrepareImpl(updater: ContentUpdater, buildOpts: BuildOptions) { - const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); - - return updater.updateContent( - "patch-prepareImpl", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, - async ({ contents }) => patchCode(contents, getRule(builtInstrumentationPath)) - ); -} - -export function getRule(builtInstrumentationPath: string | null) { - return ` - rule: - kind: method_definition - any: - - has: { field: name, regex: ^prepareImpl$, pattern: $NAME } - all: - - has: { pattern: dynamicRequire, stopBy: end } - - has: { pattern: $_.INSTRUMENTATION_HOOK_FILENAME, stopBy: end } - fix: |- - async $NAME() { - await super.prepareImpl(); - const instrumentationHook = ${builtInstrumentationPath ? `require('${builtInstrumentationPath}')` : "{}"}; - await (instrumentationHook.register == null ? void 0 : instrumentationHook.register.call(instrumentationHook)); - } - `; -} diff --git a/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts b/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts deleted file mode 100644 index 0a618940..00000000 --- a/packages/cloudflare/src/cli/build/utils/get-built-instrumentation-path.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { existsSync } from "node:fs"; -import { join } from "node:path"; - -import { type BuildOptions, getPackagePath } from "@opennextjs/aws/build/helper.js"; - -/** - * Gets the instrumentation.js file that the Next.js build process generates when an - * instrumentation hook is provided in the app's source - * - * @param buildOpts the open-next build options - * @returns a string pointing to the instrumentation.js file location, or null if such file is not found - */ -export function getBuiltInstrumentationPath(buildOpts: BuildOptions): string | null { - const { outputDir } = buildOpts; - - const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); - const dotNextDir = join(baseDir, ".next"); - const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); - const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) - ? maybeBuiltInstrumentationPath - : null; - - return builtInstrumentationPath; -} - -/** - * Pattern to detect instrumentation hooks file - * (taken from Next.js source: https://github.com/vercel/next.js/blob/1d5820563/packages/next/src/lib/constants.ts#L46-L47) - */ -const INSTRUMENTATION_HOOK_FILENAME = "instrumentation"; From e690889636112300fc78930060412a5db06205cf Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 18:03:26 +0000 Subject: [PATCH 16/17] Apply suggestions from code review Co-authored-by: Victor Berchet --- .../cloudflare/src/cli/build/bundle-server.ts | 2 +- .../build/patches/plugins/instrumentation.ts | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/cloudflare/src/cli/build/bundle-server.ts b/packages/cloudflare/src/cli/build/bundle-server.ts index 25bf7462..577ff957 100644 --- a/packages/cloudflare/src/cli/build/bundle-server.ts +++ b/packages/cloudflare/src/cli/build/bundle-server.ts @@ -91,7 +91,7 @@ export async function bundleServer(buildOpts: BuildOptions): Promise { setWranglerExternal(), fixRequire(updater), handleOptionalDependencies(optionalDependencies), - ...patchInstrumentation(updater, buildOpts), + patchInstrumentation(updater, buildOpts), patchFetchCacheSetMissingWaitUntil(updater), inlineEvalManifest(updater, buildOpts), inlineFindDir(updater, buildOpts), diff --git a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts index 4e0ee9df..51b49703 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts @@ -9,18 +9,22 @@ import type { ContentUpdater } from "./content-updater.js"; export function patchInstrumentation(updater: ContentUpdater, buildOpts: BuildOptions) { const builtInstrumentationPath = getBuiltInstrumentationPath(buildOpts); - return [ - updater.updateContent( - "patch-load-instrumentation", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, - async ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)) - ), - updater.updateContent( - "patch-prepareImpl", - { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, - async ({ contents }) => patchCode(contents, getNext14Rule(builtInstrumentationPath)) - ), - ]; + updater.updateContent( + "patch-instrumentation-next15", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, + async ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)) + ); + + updater.updateContent( + "patch-instrumentation-next14", + { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, + async ({ contents }) => patchCode(contents, getNext14Rule(builtInstrumentationPath)) + ); + + return { + "patch-instrumentation", + setup() {}, + }; } export function getNext15Rule(builtInstrumentationPath: string | null) { @@ -62,19 +66,15 @@ export function getNext14Rule(builtInstrumentationPath: string | null) { * instrumentation hook is provided in the app's source * * @param buildOpts the open-next build options - * @returns a string pointing to the instrumentation.js file location, or null if such file is not found + * @returns the path to instrumentation.js, or null if it doesn't exist */ function getBuiltInstrumentationPath(buildOpts: BuildOptions): string | null { const { outputDir } = buildOpts; - const baseDir = join(outputDir, "server-functions/default", getPackagePath(buildOpts)); - const dotNextDir = join(baseDir, ".next"); - const maybeBuiltInstrumentationPath = join(dotNextDir, "server", `${INSTRUMENTATION_HOOK_FILENAME}.js`); - const builtInstrumentationPath = existsSync(maybeBuiltInstrumentationPath) + const maybeBuiltInstrumentationPath = join(outputDir, "server-functions/default", getPackagePath(buildOpts), `.next/server/${INSTRUMENTATION_HOOK_FILENAME}.js` ); + return existsSync(maybeBuiltInstrumentationPath) ? maybeBuiltInstrumentationPath : null; - - return builtInstrumentationPath; } /** From 4259dfcb8976177d66bd0c21347979b7f52b695a Mon Sep 17 00:00:00 2001 From: Dario Piotrowicz Date: Tue, 25 Feb 2025 18:17:08 +0000 Subject: [PATCH 17/17] add back missing name field --- .../cli/build/patches/plugins/instrumentation.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts index 51b49703..e720d6b1 100644 --- a/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts +++ b/packages/cloudflare/src/cli/build/patches/plugins/instrumentation.ts @@ -14,7 +14,7 @@ export function patchInstrumentation(updater: ContentUpdater, buildOpts: BuildOp { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async loadInstrumentationModule\(/ }, async ({ contents }) => patchCode(contents, getNext15Rule(builtInstrumentationPath)) ); - + updater.updateContent( "patch-instrumentation-next14", { filter: /\.(js|mjs|cjs|jsx|ts|tsx)$/, contentFilter: /async prepareImpl\(/ }, @@ -22,7 +22,7 @@ export function patchInstrumentation(updater: ContentUpdater, buildOpts: BuildOp ); return { - "patch-instrumentation", + name: "patch-instrumentation", setup() {}, }; } @@ -71,10 +71,13 @@ export function getNext14Rule(builtInstrumentationPath: string | null) { function getBuiltInstrumentationPath(buildOpts: BuildOptions): string | null { const { outputDir } = buildOpts; - const maybeBuiltInstrumentationPath = join(outputDir, "server-functions/default", getPackagePath(buildOpts), `.next/server/${INSTRUMENTATION_HOOK_FILENAME}.js` ); - return existsSync(maybeBuiltInstrumentationPath) - ? maybeBuiltInstrumentationPath - : null; + const maybeBuiltInstrumentationPath = join( + outputDir, + "server-functions/default", + getPackagePath(buildOpts), + `.next/server/${INSTRUMENTATION_HOOK_FILENAME}.js` + ); + return existsSync(maybeBuiltInstrumentationPath) ? maybeBuiltInstrumentationPath : null; } /**