bo cache API via scripts/turbo-cache.mjs.
// Uses docker export/import (flat filesystem) instead of save/load (layered)
// to avoid including redundant base image layers. Compressed with zstd.
//
// Usage:
//   node scripts/docker-image-cache.js           # restore from cache or build + upload
//   node scripts/docker-image-cache.js --force   # always rebuild and re-upload

const { execSync } = require('child_process')
const crypto = require('crypto')
const { createHash } = crypto
const path = require('path

},
})

const REPO_ROOT = path.resolve(__dirname, '..')
const IMAGE_NAME = 'next-swc-builder:latest'

// docker export/import strips all image metadata. These --change flags
// restore the ENV and WORKDIR that the Dockerfile sets, so that tools
// like carg

')
const fs = require('fs')
const os = require('os')

const { parseArgs } = require('node:util')
const { values: flags } = parseArgs({
  args: process.argv.slice(2),
  options: {
    force: { type: 'boolean', default: false },


entChunks, retry } from 'next-test-utils'

describe('chunk-load-failure', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  async function getNextDynamicChunk() {
    const browserChunks = await listClientChunks(
      path.join(next.testDir, next.distDir)
    )
    let nextDynamicChunks = browserChunks.filter(
      (f) =>
        /\.js$/.test(f) &&
        fs
          .readFileSync(path.join(next.testDir, next.distDir, f), 'utf8')
          .includes('this is a lazy loaded async co

eError: Error | undefined
    const browser = await next.browser('/dynamic', {
      beforePageLoad(page) {
        page.route(`**/${nextDynamicChunk}*`, async (route) => {
          await route.abort('connectionreset')
        })
        page.on('pageerro

mponent')
    )
    expect(nextDynamicChunks).toHaveLength(1)

    return nextDynamicChunks[0]
  }

  it('should report async chunk load failures', async () => {
    let nextDynamicChunk = await getNextDynamicChunk()

    let pag


ction mode', () => {
    const { next, skipped } = nextTestSetup({
      files: __dirname,
      skipStart: true,
      skipDeployment: true,
    })
    if (skipped) return

    afterEach(async () => {
      await next.deleteFile('pages')
    })

    it('should show proper error when two dynamic SSG routes have conflicting paths', async () => {
      await next.patchFile(
        'pages/blog/[slug].js',
        `
      export const getStaticProps = () => {
        return {
          props: {}
        }

on Page() {
        return '/blog/[slug]'
      }
    `
      )

      await next.patchFile(
        'pages/[...catchAll].js',
        `
      export const getStaticProps = () => {
        return {
          props: {}
        }
      }

      export const

  }

      export const getStaticPaths = () => {
        return {
          paths: [
            '/blog/conflicting',
            '/blog/first'
          ],
          fallback: false
        }
      }

      export default functi


('Production Config Usage', () => {
  describe('production mode', () => {
    describe('with generateBuildId', () => {
      const { next, skipped } = nextTestSetup({
        files: __dirname + '/fixture-generateBuildId',
        disableAutoSkewProtection: true,
        skipDeployment: true,
      })
      if (skipped) return

      it('should add the custom buildid', async () => {
        const browser = await next.browser('/')
        const text = await browser.elementByCss('#mounted').text()
        expe

)

    describe('env', () => {
      const { next, skipped } = nextTestSetup({
        files: __dirname,
        skipStart: true,
        skipDeployment: true,
      })
      if (skipped) return

      it('should fail with leading __ in env key', async ()

ct(text).toMatch(/ComponentDidMount executed on client\./)

        const html = await browser.eval('document.documentElement.innerHTML')
        expect(html).toMatch('custom-buildid')
        await browser.close()
      })
    }


tSetup } from 'e2e-utils'
import { renderViaHTTP } from 'next-test-utils'
import path from 'path'

const appDir = path.join(__dirname, 'app')

describe('next/jest', () => {
  const { next } = nextTestSetup({
    files: {
      pages: new FileRef(path.join(appDir, 'pages')),
      'tests/index.test.tsx': `
      import { render, waitFor } from '@testing-library/react'
      import '@testing-library/jest-dom/extend-expect';

      import Page from '@/pages'

      describe('testid', () => {
        it('data-t

      `,
      'jest.config.js': new FileRef(path.join(appDir, 'jest.config.js')),
      'next.config.js': new FileRef(path.join(appDir, 'next.config.js')),
      'tsconfig.json': new FileRef(path.join(appDir, 'tsconfig.json')),
    },
    dependencies:

estid should be available in the test', async () => {
          const { getByTestId } = render(
            <Page />
          )
          expect(getByTestId('main-text')).toHaveTextContent('Hello World')
        })
      })


from 'e2e-utils'

// Skipped in Turbopack as Turbopack doesn't have this error as it can process these imports.
;(process.env.IS_TURBOPACK_TEST ? describe.skip : describe)(
  'Invalid static image import in _document',
  () => {
    const { next } = nextTestSetup({
      files: __dirname,
      skipStart: true,
    })

    it('Should fail to build when no next.config.js', async () => {
      const { exitCode, cliOutput } = await next.build()
      expect(exitCode).not.toBe(0)
      expect(cliOutput).toConta

_document\.js/)
      }
    })

    it('Should fail to build when disableStaticImages in next.config.js', async () => {
      await next.patchFile(
        'next.config.js',
        `
        module.exports = {
          images: {
            disableStatic

in('Failed to compile')
      expect(cliOutput).toMatch(
        /Images.*cannot.*be imported within.*pages[\\/]_document\.js/
      )
      if (!process.env.NEXT_RSPACK) {
        expect(cliOutput).toMatch(/Location:.*pages[\\/]


{ nextTestSetup } from 'e2e-utils'
import { renderViaHTTP } from 'next-test-utils'

describe('PostCSS plugin config as string', () => {
  const { next } = nextTestSetup({
    files: {
      'pages/index.js': `
        export default function Page() {
          return
hello world

        }
      `,
      'global.css': `
        @import "tailwindcss/base";
        @import "tailwindcss/components";
        @import "tailwindcss/utilities";
      `,
      'pages/_app.js': `
        import "../global.css"

 'tailwindcss/nesting': 'postcss-nesting',
            tailwindcss: {},
          },
        }
      `,
      'tailwind.config.js': `
        module.exports = {
          content: ['./pages/**/*'],
        }
      `,
    },
    dependencies: {
      'postc

        export default function MyApp({ Component, pageProps }) {
          return <Component {...pageProps} />
        }
      `,
      'postcss.config.js': `
        module.exports = {
          plugins: {


nv node
/**
 * Analyze a CPU profile to identify hot modules
 */

const fs = require('fs')

const profilePath = process.argv[2]
if (!profilePath) {
  console.error('Usage: node analyze-profile.js <profile.cpuprofile>')
  process.exit(1)
}

const profile = JSON.parse(fs.readFileSync(profilePath, 'utf-8'))

// Extract nodes with their hit counts
const nodes = profile.nodes || []

// Group by file/module
const moduleHits = {}
nodes.forEach((node) => {
  const fn = node.callFrame
  if (fn && fn.url) {
    const

ode_modules/' + url.split('node_modules/').pop()
    }
    if (!moduleHits[moduleName]) {
      moduleHits[moduleName] = { hits: 0 }
    }
    moduleHits[moduleName].hits += node.hitCount || 0
  }
})

// Sort by hits
const sorted = Object.entries(moduleHit

 url = fn.url
    // Extract module name from path
    let moduleName = url
    if (url.includes('next/dist/')) {
      moduleName = url.split('next/dist/')[1]
    } else if (url.includes('node_modules/')) {
      moduleName = 'n


 'node:fs'
import path from 'node:path'
import url from 'node:url'
import execa from 'execa'
import { NEXT_DIR, logCommand } from './pack-util'

const nextSwcDir = path.join(NEXT_DIR, 'packages/next-swc')

export default async function buildNative(
  buildNativeArgs: string[]
): Promise<void> {
  const buildCommand = ['pnpm', 'run', 'build-native', ...buildNativeArgs]
  logCommand('Build native bindings', buildCommand)
  await execa(buildCommand[0], buildCommand.slice(1), {
    cwd: nextSwcDir,
    // Witho

riteTypes()
}

// Check if this file is being run directly
if (import.meta.url === url.pathToFileURL(process.argv[1]).toString()) {
  buildNative(process.argv.slice(2)).catch((err) => {
    console.error(err)
    process.exit(1)
  })
}

async function writ

ut a shell, `pnpm run build-native` returns a 0 exit code on SIGINT?
    shell: true,
    env: {
      NODE_ENV: process.env.NODE_ENV,
      CARGO_TERM_COLOR: 'always',
      TTY: '1',
    },
    stdio: 'inherit',
  })

  await w


t {
  configureGitHubAuth,
  getGitHubToken,
  getGitHubTokenMissingMessage,
  verifyGitHubApiAccess,
} = require('./release-github-auth')

async function main() {
  const args = process.argv
  const branchName = args[args.indexOf('--branch-name') + 1]
  const tagName = args[args.indexOf('--tag-name') + 1]

  if (!branchName) {
    throw new Error('branchName value is missing!')
  }

  if (!tagName || !tagName.startsWith('v')) {
    throw new Error('tagName value is invalid "' + tagName + '"')
  }

  const

t.js/environments/release-stable/deployment-branch-policies?per_page=1',
    'release-stable deployment branch policies'
  )

  await execa(`git checkout -b "${branchName}"`, {
    stdio: 'inherit',
    shell: true,
  })
  await execa(`git fetch origin ${t

githubToken = getGitHubToken()

  if (!githubToken) {
    console.log(getGitHubTokenMissingMessage())
    return
  }

  await configureGitHubAuth(githubToken)
  await verifyGitHubApiAccess(
    githubToken,
    '/repos/vercel/nex


 { nextTestSetup } from 'e2e-utils'
import { renderViaHTTP } from 'next-test-utils'
describe('next/jest', () => {
  const { next } = nextTestSetup({
    files: {
      'pages/index.js': `import capitalize from '@hashicorp/platform-util/text/capitalize'
      export default function Home() {
        return capitalize('test')
      }`,
      'index.test.ts': `import capitalize from '@hashicorp/platform-util/text/capitalize'
      it('should work', () => {
        expect(capitalize('test')).toEqual('Test')

    scripts: {
        // Runs jest and bails if jest fails
        build: 'next build && jest',
      },
    },
    installCommand: 'pnpm i',
    buildCommand: `pnpm build`,
    dependencies: {
      '@hashicorp/platform-util': '0.2.0',
      '@types/reac

   })`,
      'jest.config.js': `module.exports = require('next/jest')({ dir: './' })()`,
      'next.config.js': `module.exports = {
        transpilePackages: ['@hashicorp/platform-util'],
      }`,
    },
    packageJson: {


 { FileRef, nextTestSetup } from 'e2e-utils'
import path from 'path'
import execa from 'execa'

const appDir = path.join(__dirname, 'app')

describe('next/jest', () => {
  const { next } = nextTestSetup({
    skipStart: true,
    files: {
      app: new FileRef(path.join(appDir, 'app')),
      [`tests/index.test.tsx`]: `
      import { render, screen } from '@testing-library/react'
      import Page from '../app/page'

      it('<Page /> renders', () => {
        render(<Page />)
        const logo = screen

.7.0',
      '@testing-library/react': '15.0.2',
      '@testing-library/jest-dom': '5.17.0',
    },
  })

  it('Should not throw preload is undefined error', async () => {
    const { stdout, stderr } = await execa(
      'pnpm',
      ['jest', 'tests/ind

.getByRole('img')
        expect(logo).toBeDefined()
      })
      `,
      'jest.config.js': new FileRef(path.join(appDir, 'jest.config.js')),
    },
    dependencies: {
      jest: '29.7.0',
      'jest-environment-jsdom': '29


ER_PROPS_SSG_CONFLICT } from 'next/dist/lib/constants'

describe('Mixed getStaticProps and getServerSideProps error', () => {
  describe('production mode', () => {
    const { next, isTurbopack, skipped } = nextTestSetup({
      files: __dirname,
      skipStart: true,
      skipDeployment: true,
    })
    if (skipped) {
      return
    }

    // Uses Babel, not supported in Turbopack.
    ;(isTurbopack ? it.skip : it)(
      'should error with getStaticProps but no default export',
      async () => {

tion getStaticProps() {
        return {
          props: {}
        }
      }
    `
        )
        await next.build()
        expect(next.cliOutput).toContain(
          'found page without a React Component as default export in'
        )
        awai

      await next.patchFile('.babelrc', '{ "presets": ["next/babel"] }')
        const originalContent = await next.readFile('pages/index.js')
        await next.patchFile(
          'pages/index.js',
          `
      export func


EZE_TOKEN env`)
}

const codeFreezeRule = {
  context: 'Potentially publish release',
  app_id: 15368,
}

async function updateRules(newRules) {
  const res = await fetch(
    `https://api.github.com/repos/vercel/next.js/branches/canary/protection`,
    {
      method: 'PUT',
      headers: {
        Accept: 'application/vnd.github+json',
        Authorization: `Bearer ${authToken}`,
        'X-GitHub-Api-Version': '2022-11-28',
      },
      body: JSON.stringify(newRules),
    }
  )

  if (!res.ok) {

ction`,
    {
      headers: {
        Accept: 'application/vnd.github+json',
        Authorization: `Bearer ${authToken}`,
        'X-GitHub-Api-Version': '2022-11-28',
      },
    }
  )

  if (!res.ok) {
    throw new Error(
      `Failed to check for r

throw new Error(
      `Failed to check for rule ${res.status} ${await res.text()}`
    )
  }
}

async function getCurrentRules() {
  const res = await fetch(
    `https://api.github.com/repos/vercel/next.js/branches/canary/prote


 from 'e2e-utils'

describe.skip('Invalid CSS Module Usage in node_modules', () => {
  const { next, skipped } = nextTestSetup({
    files: __dirname,
    skipStart: true,
    skipDeployment: true,
  })
  if (skipped) return

  it('should fail to build', async () => {
    const { exitCode, cliOutput } = await next.build()
    expect(exitCode).not.toBe(0)
    expect(cliOutput).toContain('Failed to compile')
    expect(cliOutput).toContain('node_modules/example/index.module.scss')
    expect(cliOutput).toMatc

in.*node_modules/
    )
    // Skip: Rspack loaders cannot access module issuer info for location details
    if (!process.env.NEXT_RSPACK) {
      expect(cliOutput).toMatch(
        /Location:.*node_modules[\\/]example[\\/]index\.mjs/
      )
    }
  })
}

h(
      /CSS Modules.*cannot.*be imported from with


'child_process'

export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'

export function getPkgManager(): PackageManager {
  const userAgent = process.env.npm_config_user_agent || ''

  if (userAgent.startsWith('yarn')) {
    return 'yarn'
  }

  if (userAgent.startsWith('pnpm')) {
    return 'pnpm'
  }

  if (userAgent.startsWith('bun')) {
    return 'bun'
  }

  return 'npm'
}

/**
 * Get the full version string for the given package manager.
 * Returns null if unable to determine the version.
 *
 *

ng | null {
  const userAgent = process.env.npm_config_user_agent || ''
  const userAgentMatch = userAgent.match(
    new RegExp(`${packageManager}/([\\d.]+[\\w.-]*)`)
  )
  if (userAgentMatch) {
    return userAgentMatch[1]
  }

  try {
    const version

 First tries to parse from npm_config_user_agent (e.g., "pnpm/9.13.2 npm/? ..."),
 * then falls back to spawning `<packageManager> --version`.
 */
export function getPackageManagerVersion(
  packageManager: PackageManager
): stri


cSync } from 'node:child_process'
import { join } from 'node:path'
import { rmSync } from 'node:fs'

function isInGitRepository(): boolean {
  try {
    execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
    return true
  } catch (_) {}
  return false
}

function isInMercurialRepository(): boolean {
  try {
    execSync('hg --cwd . root', { stdio: 'ignore' })
    return true
  } catch (_) {}
  return false
}

function isDefaultBranchSet(): boolean {
  try {
    execSync('git config init.de

InGitRepository() || isInMercurialRepository()) {
      return false
    }

    execSync('git init', { stdio: 'ignore' })
    didInit = true

    if (!isDefaultBranchSet()) {
      execSync('git checkout -b main', { stdio: 'ignore' })
    }

    execSync('

faultBranch', { stdio: 'ignore' })
    return true
  } catch (_) {}
  return false
}

export function tryGitInit(root: string): boolean {
  let didInit = false
  try {
    execSync('git --version', { stdio: 'ignore' })
    if (is


sable import/no-extraneous-dependencies */
import { yellow } from 'picocolors'
import spawn from 'cross-spawn'
import type { PackageManager } from './get-pkg-manager'

/**
 * Spawn a package manager installation based on user preference.
 *
 * @returns A Promise that resolves once the installation is finished.
 */
export async function install(
  /** Indicate which package manager to use. */
  packageManager: PackageManager,
  /** Indicate whether there is an active Internet connection.*/
  isOnline: boolea

omise that resolves once the installation is finished.
   */
  return new Promise((resolve, reject) => {
    /**
     * Spawn the installation process.
     */
    const child = spawn(packageManager, args, {
      stdio: 'inherit',
      env: {
        ...

n
): Promise<void> {
  const args: string[] = ['install']
  if (!isOnline) {
    console.log(
      yellow('You appear to be offline.\nFalling back to the local cache.')
    )
    args.push('--offline')
  }
  /**
   * Return a Pr


no-extraneous-dependencies */
import { lstatSync, readdirSync } from 'node:fs'
import { join } from 'node:path'
import { green, blue } from 'picocolors'

export function isFolderEmpty(root: string, name: string): boolean {
  const validFilesOrFolders = [
    '.claude',
    '.cursor',
    '.DS_Store',
    '.git',
    '.gitattributes',
    '.gitignore',
    '.gitlab-ci.yml',
    '.hg',
    '.hgcheck',
    '.hgignore',
    '.idea',
    '.npmignore',
    '.travis.yml',
    '.vscode',
    '.zed',
    'LICENSE',

ilesOrFolders.includes(fileOrFolder) &&
      // Support IntelliJ IDEA-based editors
      !/\.iml$/.test(fileOrFolder)
  )

  if (conflicts.length > 0) {
    console.log(
      `The directory ${green(name)} contains files that could conflict:`
    )
    c

    'Thumbs.db',
    'docs',
    'mkdocs.yml',
    'npm-debug.log',
    'yarn-debug.log',
    'yarn-error.log',
    'yarnrc.yml',
    '.yarn',
  ]

  const conflicts = readdirSync(root).filter(
    (fileOrFolder) =>
      !validF


 { execSync } from 'node:child_process'
import { lookup } from 'node:dns/promises'
import { URL } from 'node:url'

function getProxy(): string | undefined {
  if (process.env.https_proxy) {
    return process.env.https_proxy
  }

  try {
    const httpsProxy = execSync('npm config get https-proxy').toString().trim()
    return httpsProxy !== 'null' ? httpsProxy : undefined
  } catch (e) {
    return
  }
}

export async function getOnline(): Promise<boolean> {
  try {
    await lookup('registry.yarnpkg.com')

    let url: URL
    try {
      url = new URL(proxy)
    } catch {
      // Invalid proxy URL
      return false
    }
    const { hostname } = url
    if (!hostname) {
      // Invalid proxy URL
      return false
    }

    try {
      await lookup(ho

    // If DNS lookup succeeds, we are online
    return true
  } catch {
    // The DNS lookup failed, but we are still fine as long as a proxy has been set
    const proxy = getProxy()
    if (!proxy) {
      return false
    }


no-extraneous-dependencies */
import spawn from 'cross-spawn'
import type { PackageManager } from './get-pkg-manager'

/**
 * Runs `next typegen` using the package manager to execute the locally installed Next.js binary.
 * Assumes the current working directory is the project root where Next is installed.
 */
export async function runTypegen(
  packageManager: PackageManager
): Promise<void> {
  return new Promise((resolve, reject) => {
    // Determine the command and arguments based on the package manager

       args = ['exec', 'next', '--', 'typegen']
        break
      case 'pnpm':
        command = 'pnpm'
        args = ['exec', 'next', '--', 'typegen']
        break
      case 'bun':
        command = 'bun'
        // Bun only has `bun x` which is not

    let command: string
    let args: string[]

    switch (packageManager) {
      case 'npm':
        command = 'npm'
        args = ['exec', 'next', '--', 'typegen']
        break
      case 'yarn':
        command = 'yarn'


from "async-sema";
import pkg from "../package.json";

import { Bundler, GetTemplateFileArgs, InstallTemplateArgs } from "./types";

// Do not rename or format. sync-react script relies on this line.
// prettier-ignore
const nextjsReactPeerVersion = "19.2.6";
function sorted(obj: Record<string, string>) {
  return Object.keys(obj)
    .sort()
    .reduce((acc: Record<string, string>, key) => {
      acc[key] = obj[key];

      return acc;
    }, {});
}

/**
 * Get the file path for a given file in a templat

", "styles"];

/**
 * Install a Next.js internal template to a given `root` directory.
 */
export const installTemplate = async ({
  appName,
  root,
  packageManager,
  isOnline,
  template,
  mode,
  tailwind,
  eslint,
  biome,
  srcDir,
  importAlias,

e, e.g. "next.config.js".
 */
export const getTemplateFile = ({
  template,
  mode,
  file,
}: GetTemplateFileArgs): string => {
  return path.join(__dirname, template, mode, file);
};

export const SRC_DIR_NAMES = ["app", "pages


rt { PackageManager } from "../helpers/get-pkg-manager";

export type TemplateType =
  | "app"
  | "app-api"
  | "app-empty"
  | "app-tw"
  | "app-tw-empty"
  | "default"
  | "default-empty"
  | "default-tw"
  | "default-tw-empty";
export type TemplateMode = "js" | "ts";

export interface GetTemplateFileArgs {
  template: TemplateType;
  mode: TemplateMode;
  file: string;
}

export interface InstallTemplateArgs {
  appName: string;
  root: string;
  packageManager: PackageManager;
  isOnline: boolean;
  te

: boolean;
  biome: boolean;
  tailwind: boolean;
  srcDir: boolean;
  importAlias: string;
  skipInstall: boolean;
  bundler: Bundler;
  reactCompiler: boolean;
}

export enum Bundler {
  Turbopack = "turbopack",
  Webpack = "webpack",
  Rspack = "rspack"

mplate: TemplateType;
  mode: TemplateMode;
  eslint


xt/image";
import { Geist, Geist_Mono } from "next/font/google";
import styles from "@/styles/Home.module.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export default function Home() {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width

main}>
          <Image
            className={styles.logo}
            src="/next.svg"
            alt="Next.js logo"
            width={100}
            height={20}
            priority
          />
          <div className={styles.intro}>
            <h

=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <div
        className={`${styles.page} ${geistSans.variable} ${geistMono.variable}`}
      >
        <main className={styles.


ction Home() {
  return (

        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />
        <div className="flex flex-col items-center gap-6 tex

 <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
            Looking for a starting point or more instructions? Head over to{" "}
            <a
              href="https://vercel.com/templates?framework=next.js&utm_source=creat

t-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the page.js file.
          </h1>


  <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
            Looking for a starting point or more instructions? Head over to{" "}
            <a
              href="https://vercel.com/templates?framework=next.js&utm_source=crea

t-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the page.tsx file.
          </h1>


"./page.module.css";

export default function Home() {
  return (

        <Image
          className={styles.logo}
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />

To get started, edit the page.js file.

            Looking for a starting point or more instructions? Head over to{" "}

rer"
            >
              Templates
            </a>{" "}
            or the{" "}
            <a
              href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              target

       <a
              href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              target="_blank"
              rel="noopener norefer


"./page.module.css";

export default function Home() {
  return (

        <Image
          className={styles.logo}
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />

To get started, edit the page.tsx file.

            Looking for a starting point or more instructions? Head over to{" "}

rrer"
            >
              Templates
            </a>{" "}
            or the{" "}
            <a
              href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              targe

        <a
              href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
              target="_blank"
              rel="noopener norefe


 = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export default function Home() {
  return (
    <div
      className={`${geistSans.className} ${geistMono.className} flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black`}
    >
      <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-star

ter gap-6 text-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the index.js file.
          </h1>
          <p className

t">
        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={100}
          height={20}
          priority
        />
        <div className="flex flex-col items-cen


ter gap-6 text-center sm:items-start sm:text-left">
          <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
            To get started, edit the index.tsx file.
          </h1>
          <p classNam


etadata of the fallback fonts, retrieved with fontkit on system font files
// The average width is calculated with the calcAverageWidth function below
const DEFAULT_SANS_SERIF_FONT = {
  name: 'Arial',
  azAvgWidth: 934.5116279069767,
  unitsPerEm: 2048,
}
const DEFAULT_SERIF_FONT = {
  name: 'Times New Roman',
  azAvgWidth: 854.3953488372093,
  unitsPerEm: 2048,
}

/**
 * Calculate the average character width of a font file.
 * Used to calculate the size-adjust property by comparing the fallback average wi

rage width of all characters, because we have to take letter frequency into account.
     * We also have to take word length into account, because the font's space width usually differ a lot from other characters.
     * The goal is to find a string that'l

th the loaded font average.
 */
function calcAverageWidth(font: Font): number | undefined {
  try {
    /**
     * Finding the right characters to use when calculating the average width is tricky.
     * We can't just use the ave


alFontLoader({
        functionName: '',
        data: [{ src: './my-font.woff2' }],
        emitFontFile: () => '/_next/static/media/my-font.woff2',
        resolve: jest.fn(),
        isDev: false,
        isServer: true,
        variableName: 'myFont',
        loaderContext: {
          fs: {
            readFile: (_: string, cb: any) => cb(null, 'fontdata'),
          },
        } as any,
      })

      expect(css).toMatchInlineSnapshot(`
        "@font-face {
        font-family: myFont;
        src:

ctionName: '',
        data: [{ src: './my-font.woff2', weight: '100 900', style: 'italic' }],
        emitFontFile: () => '/_next/static/media/my-font.woff2',
        resolve: jest.fn(),
        isDev: false,
        isServer: true,
        variableName:

url(/_next/static/media/my-font.woff2) format('woff2');
        font-display: swap;
        }
        "
      `)
    })

    test('Weight and style', async () => {
      const { css } = await nextFontLocalFontLoader({
        fun


equire -- Module created during build.
  const mod = require('../fontkit').default
  fontFromBuffer = mod.default || mod
} catch {}
import type { AdjustFontFallback, FontLoader } from 'next/font'

import { promisify } from 'util'
import { pickFontFileForFallbackGeneration } from './pick-font-file-for-fallback-generation'
import { getFallbackMetricsFromFontFile } from './get-fallback-metrics-from-font-file'
import { validateLocalFontFunctionCall } from './validate-local-font-function-call'

const nextFontLoc

declarations,
    weight: defaultWeight,
    style: defaultStyle,
  } = validateLocalFontFunctionCall(functionName, data[0])

  // Load all font files and emit them to the .next output directory
  // Also generate a @font-face CSS for each font file
  cons

alFontLoader: FontLoader = async ({
  functionName,
  variableName,
  data,
  emitFontFile,
  resolve,
  loaderContext,
}) => {
  const {
    src,
    display,
    fallback,
    preload,
    variable,
    adjustFontFallback,


('pickFontFileForFallbackGeneration', () => {
  it('should pick the weight closest to 400', () => {
    expect(
      pickFontFileForFallbackGeneration([
        {
          weight: '300',
        },
        {
          weight: '600',
        },
      ])
    ).toEqual({
      weight: '300',
    })

    expect(
      pickFontFileForFallbackGeneration([
        { weight: '200' },
        {
          weight: '500',
        },
      ])
    ).toEqual({
      weight: '500',
    })

    expect(
      pickFontFileF

        {
          weight: 'bold',
        },
        {
          weight: '900',
        },
      ])
    ).toEqual({
      weight: 'bold',
    })
  })

  it('should pick the thinner weight if both have the same distance to 400', () => {
    expect(

orFallbackGeneration([
        {
          weight: 'normal',
        },
        {
          weight: '700',
        },
      ])
    ).toEqual({
      weight: 'normal',
    })

    expect(
      pickFontFileForFallbackGeneration([


/**
 * Convert the weight string to a number so it can be used for comparison.
 * Weights can be defined as a number, 'normal' or 'bold'. https://developer.mozilla.org/docs/Web/CSS/@font-face/font-weight
 */
function getWeightNumber(weight: string) {
  return weight === 'normal'
    ? NORMAL_WEIGHT
    : weight === 'bold'
      ? BOLD_WEIGHT
      : Number(weight)
}

/**
 * Get the distance from normal (400) weight for the provided weight.
 * If it's not a variable font we can just return the distance.
 *

", rather than just one "400"
  const [firstWeight, secondWeight] = weight
    .trim()
    .split(/ +/)
    .map(getWeightNumber)

  if (Number.isNaN(firstWeight) || Number.isNaN(secondWeight)) {
    nextFontError(
      `Invalid weight value in src array:

If it's a variable font we need to compare its weight range to 400.
 */
function getDistanceFromNormalWeight(weight?: string) {
  if (!weight) return 0

  // If it's a variable font the weight is defined with two numbers "100 900


tFunctionCall } from './validate-local-font-function-call'

describe('validateLocalFontFunctionCall', () => {
  test('Not using default export', async () => {
    expect(() =>
      validateLocalFontFunctionCall('Named', {})
    ).toThrowErrorMatchingInlineSnapshot(
      `"next/font/local has no named exports"`
    )
  })

  test('Missing src', async () => {
    expect(() =>
      validateLocalFontFunctionCall('', {})
    ).toThrowErrorMatchingInlineSnapshot(`"Missing required \`src\` property"`)
  })

  t

  )
  })

  test('Invalid display value', async () => {
    expect(() =>
      validateLocalFontFunctionCall('', {
        src: './font-file.woff2',
        display: 'invalid',
      })
    ).toThrowErrorMatchingInlineSnapshot(`
      "Invalid display valu

est('Invalid file extension', async () => {
    expect(() =>
      validateLocalFontFunctionCall('', { src: './font/font-file.abc' })
    ).toThrowErrorMatchingInlineSnapshot(
      `"Unexpected file \`./font/font-file.abc\`"`


import { formatAvailableValues } from '../format-available-values'
import { nextFontError } from '../next-font-error'

const extToFormat = {
  woff: 'woff',
  woff2: 'woff2',
  ttf: 'truetype',
  otf: 'opentype',
  eot: 'embedded-opentype',
}

type FontOptions = {
  src: Array<{
    path: string
    weight?: string
    style?: string
    ext: string
    format: string
  }>
  display: string
  weight?: string
  style?: string
  fallback?: string[]
  preload: boolean
  variable?: string
  adjustFontFallback?

nName: string,
  fontData: any
): FontOptions {
  if (functionName) {
    nextFontError(`next/font/local has no named exports`)
  }
  let {
    src,
    display = 'swap',
    weight,
    style,
    fallback,
    preload = true,
    variable,
    adjustFont

: string | false
  declarations?: Array<{ prop: string; value: string }>
}

/**
 * Validate the data received from next-swc next-transform-font on next/font/local calls
 */
export function validateLocalFontFunctionCall(
  functio


 { nextFontError } from '../next-font-error'
import { fetchResource } from './fetch-resource'
import { retry } from './retry'

/**
 * Fetches the CSS containing the @font-face declarations from Google Fonts.
 * The fetch has a user agent header with a modern browser to ensure we'll get .woff2 files.
 *
 * The env variable NEXT_FONT_GOOGLE_MOCKED_RESPONSES may be set containing a path to mocked data.
 * It's used to define mocked data to avoid hitting the Google Fonts API during tests.
 */
export async funct

NSES)
    const mockedResponse = mockFile[url]
    if (!mockedResponse) {
      nextFontError('Missing mocked response for URL: ' + url)
    }
    return mockedResponse
  }

  const buffer = await retry(async () => {
    return fetchResource(
      url,

ion fetchCSSFromGoogleFonts(
  url: string,
  fontFamily: string,
  isDev: boolean
): Promise<string> {
  if (process.env.NEXT_FONT_GOOGLE_MOCKED_RESPONSES) {
    const mockFile = require(process.env.NEXT_FONT_GOOGLE_MOCKED_RESPO


tp'
import https from 'node:https'
import { getProxyAgent } from './get-proxy-agent'

/**
 * Makes a simple GET request and returns the entire response as a Buffer.
 * - Throws if the response status is not 200.
 * - Applies a 3000 ms timeout when `isDev` is `true`.
 */
export function fetchResource(
  url: string,
  isDev: boolean,
  errorMessage?: string
): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const { protocol } = new URL(url)
    const client = protocol === 'https:' ? https :

2 files are fetched
          'User-Agent':
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
            'AppleWebKit/537.36 (KHTML, like Gecko) ' +
            'Chrome/104.0.0.0 Safari/537.36',
        },
      },
      (res) => {

 http
    const timeout = isDev ? 3000 : undefined

    const req = client.request(
      url,
      {
        agent: getProxyAgent(),
        headers: {
          // The file format is based off of the user agent, make sure woff


  it('should find all font files and preload requested subsets', () => {
    const css = `/* latin */
            @font-face {
              font-family: 'Fraunces';
              font-style: normal;
              font-weight: 300;
              src: url(latin1.woff2) format('woff2');
            }

            /* greek */
            @font-face {
              font-family: 'Fraunces';
              font-style: normal;
              font-weight: 300;
              src: url(greek1.woff2) format('woff2');

   }

            /* greek */
            @font-face {
              font-family: 'Fraunces';
              font-style: normal;
              font-weight: 400;
              src: url(greek2.woff2) format('woff2');
            }

            /* cyrilic */

         }

            /* latin */
            @font-face {
              font-family: 'Fraunces';
              font-style: normal;
              font-weight: 400;
              src: url(latin2.woff2) format('woff2');


 Find all font files in the CSS response and determine which files should be preloaded.
 * In Google Fonts responses, the @font-face's subset is above it in a comment.
 * Walk through the CSS from top to bottom, keeping track of the current subset.
 */
export function findFontFilesInCss(css: string, subsetsToPreload?: string[]) {
  // Find font files to download
  const fontFiles: Array<{
    googleFontFileUrl: string
    preloadFontFile: boolean
  }> = []

  // Keep track of the current subset
  let curren

newSubset
    } else {
      const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1]
      if (
        googleFontFileUrl &&
        !fontFiles.some(
          (foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl
        )
      ) {

tSubset = ''
  for (const line of css.split('\n')) {
    const newSubset = /\/\* (.+?) \*\//.exec(line)?.[1]
    if (newSubset) {
      // Found new subset in a comment above the next @font-face declaration
      currentSubset =


gnore
import * as Log from 'next/dist/build/output/log'

/**
 * Get precalculated fallback font metrics for the Google Fonts family.
 *
 * TODO:
 * We might want to calculate these values with fontkit instead (like in next/font/local).
 * That way we don't have to update the precalculated values every time a new font is added to Google Fonts.
 */
export function getFallbackFontOverrideMetrics(fontFamily: string) {
  try {
    const { ascent, descent, lineGap, fallbackFont, sizeAdjust } =
      calculateSize

Font,
      ascentOverride: `${ascent}%`,
      descentOverride: `${descent}%`,
      lineGapOverride: `${lineGap}%`,
      sizeAdjust: `${sizeAdjust}%`,
    }
  } catch {
    Log.error(`Failed to find font override values for font \`${fontFamily}\``)
  }

AdjustValues(fontFamily)
    return {
      fallback


rrors', () => {
  test('Setting axes on font without definable axes', () => {
    expect(() =>
      getFontAxes('Lora', ['variable'], [], [])
    ).toThrowErrorMatchingInlineSnapshot(
      `"Font \`Lora\` has no definable \`axes\`"`
    )
  })

  test('Invalid axes value', async () => {
    expect(() => getFontAxes('Inter', ['variable'], [], true as any))
      .toThrowErrorMatchingInlineSnapshot(`
      "Invalid axes value for font \`Inter\`, expected an array of axes.
      Available axes: \`opsz\`"

c () => {
    expect(() => getFontAxes('Roboto Flex', ['variable'], [], ['INVALID']))
      .toThrowErrorMatchingInlineSnapshot(`
      "Invalid axes value \`INVALID\` for font \`Roboto Flex\`.
      Available axes: \`GRAD\`, \`XOPQ\`, \`XTRA\`, \`YOPQ\`,

 `)
  })

  test('Invalid value in axes array', asyn


ailable-values'
import { nextFontError } from '../next-font-error'
import { googleFontsMetadata } from './google-fonts-metadata'

/**
 * Validates and gets the data for each font axis required to generate the Google Fonts URL.
 */
export function getFontAxes(
  fontFamily: string,
  weights: string[],
  styles: string[],
  selectedVariableAxes?: string[]
): {
  wght?: string[]
  ital?: string[]
  variableAxes?: [string, string][]
} {
  const hasItalic = styles.includes('italic')
  const hasNormal = styles.i

 ? ['0'] : []), '1'] : undefined

  // Weights will always contain one element if it's a variable font
  if (weights[0] === 'variable') {
    // Get all the available axes for the current font from the metadata file
    const allAxes = googleFontsMetadata[

ncludes('normal')
  // Make sure the order is correct, otherwise Google Fonts will return an error
  // If only normal is set, we can skip returning the ital axis as normal is the default
  const ital = hasItalic ? [...(hasNormal


Values } from './sort-fonts-variant-values'

/**
 * Generate the Google Fonts URL given the requested weight(s), style(s) and additional variable axes
 */
export function getGoogleFontsUrl(
  fontFamily: string,
  axes: {
    wght?: string[]
    ital?: string[]
    variableAxes?: [string, string][]
  },
  display: string
) {
  // Variants are all combinations of weight and style, each variant will result in a separate font file
  const variants: Array<[string, string][]> = []
  if (axes.wght) {
    for (con

          ['wght', wght],
            ...(axes.variableAxes ?? []),
          ])
        }
      }
    }
  } else if (axes.variableAxes) {
    // Variable fonts might not have a range of weights, just add optional variable axes in that case
    variants.pu

st wght of axes.wght) {
      if (!axes.ital) {
        variants.push([['wght', wght], ...(axes.variableAxes ?? [])])
      } else {
        for (const ital of axes.ital) {
          variants.push([
            ['ital', ital],


.mock('./fetch-resource')

const mockFetchResource = fetchResource as jest.Mock

describe('next/font/google loader', () => {
  afterEach(() => {
    jest.resetAllMocks()
  })

  describe('URL from options', () => {
    const fixtures: Array<[string, any, string]> = [
      [
        'Inter',
        {},
        'https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap',
      ],
      [
        'Inter',
        { weight: '400' },
        'https://fonts.googleapis.com/css2?family=Inter:wght@

{ weight: '900', display: 'auto' },
        'https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@900&display=auto',
      ],
      [
        'Source_Sans_3',
        { weight: '200', style: 'italic' },
        'https://fonts.googleapis.com/css2?fam

400&display=swap',
      ],
      [
        'Inter',
        { weight: '900', display: 'block' },
        'https://fonts.googleapis.com/css2?family=Inter:wght@900&display=block',
      ],
      [
        'Source_Sans_3',


from 'e2e-utils'

describe('options-request', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it.each(['/app-page/dynamic', '/pages-page/dynamic'])(
    'should return a 400 status code when invoking %s with an OPTIONS request (dynamic rendering)',
    async (path) => {
      const res = await next.fetch(path, { method: 'OPTIONS' })
      expect(res.status).toBe(400)
      // There should be no response body
      expect(await res.text()).toBe('')
    }
  )

  it.each(['/app-page/st

res.status).toBe(405)
      expect(await res.text()).toBe('Method Not Allowed')
    }
  )

  // In app router, OPTIONS is auto-implemented if not provided
  it('should respond with a 204 No Content when invoking an app route handler with an OPTIONS request

atic', '/pages-page/static'])(
    'should return a 405 status code when invoking %s with an OPTIONS request (static rendering)',
    async (path) => {
      const res = await next.fetch(path, { method: 'OPTIONS' })
      expect(


t { diff } from 'jest-diff'
const glob = promisify(globOrig)
import { FILES } from './files'

const IGNORE_CONTENT_NEXT_REGEX = new RegExp(
  [
    // This contains the deployment id, but these changing fields are stripped by the builder
    'routes-manifest\\.json',

    // These contain the build id and deployment id (but are not deployed to the serverless function)
    '.*\\.html',
    '.*\\.rsc',
    // These are not critical, as they aren't deployed to the serverless function
    'client-build-manifest

c files which aren't deployed.
  const IGNORE = /^trace$|^trace-build$/

  const files = (
    (await glob('**/*', {
      cwd: path.join(next.testDir, next.distDir),
      nodir: true,
      dot: true,
    })) as string[]
  )
    .filter((f) => !IGNORE.te

\\.json',
    'fallback-build-manifest\\.json',
  ]
    .map((v) => '(?:\\/|^)' + v + '$')
    .join('|')
)

async function readFilesNext(
  next: NextInstance
): Promise<Map<string, Map<string, string>>> {
  // These are cosmeti


 busting the turbo cache un-necessarily due
 to bumping the version in the repo's package.json files
*/
const path = require('path')
const fs = require('fs/promises')

const cwd = process.cwd()
const NORMALIZED_VERSION = '0.0.0'

const readJson = async (filePath) =>
  JSON.parse(await fs.readFile(filePath, 'utf8'))

const writeJson = async (filePath, data) =>
  fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n')

;(async function () {
  const packages = await fs.readdir(path.join(cwd, 'packages'))

.push(data.name)
      pkgJsonData.set(pkgDir, data)
    })
  )
  const normalizeVersions = async (filePath, data) => {
    data = data || (await readJson(filePath))
    const version = data.version

    if (version) {
      data.version = NORMALIZED_VERSI

  const pkgJsonData = new Map()
  const pkgNames = []
  await Promise.all(
    packages.map(async (pkgDir) => {
      const data = await readJson(
        path.join(cwd, 'packages', pkgDir, 'package.json')
      )
      pkgNames


rom 'yargs/helpers'
import path from 'path'

import { NEXT_DIR, exec, execFn, packageFiles } from './pack-util.js'
import buildNative from './build-native.js'

interface Options {
  project: string
  build: boolean
  'build-native': boolean
  verbose: number
  _: string[]
}

// --- Parse command line arguments ---
const argv = yargs(hideBin(process.argv))
  .scriptName('patch-next')
  .command(
    '$0 <project> [options]',
    'Patch local Next.js packages to the target project directory',
    (yargs) => {

-app --no-build --no-build-native',
          'Patch Next.js packages in the "my-app" directory'
        )
        .example(
          '$0 ../my-app -- --release',
          'Patch using a release-mode native build. `--release` is passed through to the nap

      return yargs
        .positional('project', {
          type: 'string',
          describe: 'Target directory of the Next.js project to patch',
          demandOption: true,
        })
        .example(
          '$0 ../my


ompletedAt) {
  if (!startedAt || !completedAt) return 'N/A'
  const start = new Date(startedAt)
  const end = new Date(completedAt)

  // Validate that both dates are valid (not Invalid Date objects)
  if (isNaN(start.getTime()) || isNaN(end.getTime())) return 'N/A'

  const seconds = Math.floor((end - start) / 1000)

  if (seconds < 60) return `${seconds}s`
  const minutes = Math.floor(seconds / 60)
  const remainingSeconds = seconds % 60
  return `${minutes}m ${remainingSeconds}s`
}

function formatElaps

eturn `${seconds}s`
  const minutes = Math.floor(seconds / 60)
  const remainingSeconds = seconds % 60
  return `${minutes}m ${remainingSeconds}s`
}

function sanitizeFilename(name) {
  return name
    .replace(/[^a-zA-Z0-9._-]/g, '-')
    .replace(/-+/g,

edTime(startedAt) {
  if (!startedAt) return 'N/A'
  const start = new Date(startedAt)
  if (isNaN(start.getTime())) return 'N/A'

  const now = new Date()
  const seconds = Math.floor((now - start) / 1000)

  if (seconds < 60) r


ire('async-sema')
const { readFile, readdir, writeFile, cp } = require('fs/promises')

const cwd = process.cwd()

;(async function () {
  try {
    const publishSema = new Sema(2)

    let version = require('@next/swc/package.json').version

    // Copy binaries to package folders, update version, and publish
    let nativePackagesDir = path.join(cwd, 'crates/next-napi-bindings/npm')
    let platforms = (await readdir(nativePackagesDir)).filter(
      (name) => !name.startsWith('.')
    )

    await Promise

kages/next-swc/native', binaryName),
            path.join(nativePackagesDir, platform, binaryName)
          )
          let pkg = JSON.parse(
            await readFile(
              path.join(nativePackagesDir, platform, 'package.json')
            )

.all(
      platforms.map(async (platform) => {
        await publishSema.acquire()
        let output = ''

        try {
          let binaryName = `next-swc.${platform}.node`
          await cp(
            path.join(cwd, 'pac


ommit,
  githubRequest,
  alignLocalBranchWithSignedCommit,
} = require('./github-utils/signed-commit')

const REPO_API_PATH = '/repos/vercel/next.js'

async function git(args, options = {}) {
  const { captureOutput = false, ...execaOptions } = options
  const { stdout } = await execa('git', args, {
    stdio: captureOutput ? 'pipe' : 'inherit',
    ...execaOptions,
  })

  return typeof stdout === 'string' ? stdout.trim() : stdout
}

/**
 * Verify the local Lerna release commit has the version tag implied

version}`
  const tags = String(
    await git(['tag', '--points-at', commitSha], { captureOutput: true })
  )
    .split('\n')
    .map((tag) => tag.trim())
    .filter(Boolean)

  if (!tags.includes(expectedTagName)) {
    throw new Error(
      `Expecte

 by
 * lerna.json, then return that tag name for GitHub ref creation.
 */
async function getLocalReleaseTagName(commitSha) {
  const { version } = JSON.parse(await fs.readFile('lerna.json', 'utf8'))
  const expectedTagName = `v${


 = require('execa')

const REPO_URL = 'github.com/vercel/next.js.git'

function getGitHubToken() {
  return process.env.RELEASE_GITHUB_TOKEN
}

function getGitHubTokenMissingMessage() {
  return 'Missing RELEASE_GITHUB_TOKEN'
}

function getGitUser() {
  const appSlug = process.env.RELEASE_GITHUB_APP_SLUG
  const appUserId = process.env.RELEASE_GITHUB_APP_USER_ID

  if (appSlug && appUserId) {
    return {
      name: `${appSlug}[bot]`,
      email: `${appUserId}+${appSlug}[bot]@users.noreply.github.com`,

er = getGitUser()
  const remoteUrl = `https://x-access-token:${encodeURIComponent(
    token
  )}@${REPO_URL}`

  await execa('git', ['remote', 'set-url', 'origin', remoteUrl], {
    stdio: 'inherit',
  })
  await execa('git', ['config', 'user.name', gitU

   }
  }

  return {
    name: process.env.RELEASE_GITHUB_USER_NAME || 'nextjs-bot',
    email: process.env.RELEASE_GITHUB_USER_EMAIL || 'it+nextjs-bot@vercel.com',
  }
}

async function configureGitHubAuth(token) {
  const gitUs


 Sync from a React commit (can be a commit on a PR)
//   pnpm run sync-react --version vp:///commit-sha

const repoOwner = 'vercel'
const repoName = 'next.js'
const pullRequestLabels = ['type: react-sync']
const pullRequestReviewers = ['eps1lon']
/**
 * Set to `null` to automatically sync the React version of Pages Router with App Router React version.
 * Set to a specific version to override the Pages Router React version e.g. `^19.0.0`.
 *
 * "Active" just refers to our current development practice. While

hannel = 'canary'
const filesReferencingReactPeerDependencyVersion = [
  'run-tests.js',
  'packages/create-next-app/templates/index.ts',
  'test/lib/next-modes/base.ts',
]
const libraryManifestsSupportingNextjsReact = [
  'packages/third-parties/package.j

 we do support
 * React 18 in pages router, we don't focus our development process on it considering
 * it does not receive new features.
 * @type {string | null}
 */
const activePagesRouterReact = '^19.0.0'

const defaultLatestC


s')
const path = require('path')
const fetch = require('node-fetch')

;(async () => {
  const { familyMetadataList } = await fetch(
    'https://fonts.google.com/metadata/fonts'
  ).then((r) => r.json())

  let fontFunctions = `/**
  * This is an autogenerated file by scripts/update-google-fonts.js
  */
  import type { CssVariable, NextFont, NextFontWithVariable, Display } from '../types'
  `
  const fontData = {}
  const ignoredSubsets = [
    'menu',
    'japanese',
    'korean',
    'chinese-simplified',

ts = subsets.length > 0
    const weights = new Set()
    const styles = new Set()

    for (const variant of Object.keys(fonts)) {
      if (variant.endsWith('i')) {
        styles.add('italic')
        weights.add(variant.slice(0, -1))
        continue

    'chinese-hongkong',
    'chinese-traditional',
  ]
  for (let { family, fonts, axes, subsets } of familyMetadataList) {
    subsets = subsets.filter((subset) => !ignoredSubsets.includes(subset))
    const hasPreloadableSubse


at CLI startup.
 * This helps identify which modules are being loaded eagerly.
 *
 * Usage:
 *   node scripts/trace-cli-startup.js [--command=dev|build|--help]
 */

const inspector = require('inspector')
const fs = require('fs')
const path = require('path')

const args = process.argv.slice(2)
const getArg = (name, defaultValue) => {
  const arg = args.find((a) => a.startsWith(`--${name}=`))
  return arg ? arg.split('=')[1] : defaultValue
}

const command = getArg('command', '--help')
const outputDir = path.

og(`Output directory: ${outputDir}`)
console.log('')

// Start CPU profiling
const session = new inspector.Session()
session.connect()

// Track module loading via require hook
const Module = require('module')
const originalRequire = Module.prototype.requi

join(process.cwd(), 'profiles')

if (!fs.existsSync(outputDir)) {
  fs.mkdirSync(outputDir, { recursive: true })
}

console.log('\x1b[34m=== Next.js CLI Startup Trace ===\x1b[0m')
console.log(`Command: next ${command}`)
console.l


)
const fs = require('node:fs/promises')
const path = require('node:path')

/**
 * Yields one entry per package tarball under `tarballDirectory`. Scoped
 * packages are laid out one level deeper (e.g. `@next/env/<name>.tgz`), so the
 * walk descends into any directory whose name starts with `@`.
 *
 * @param {string} tarballDirectory
 * @returns {AsyncGenerator<{ packageName: string, tarballPath: string }>}
 */
async function* findTarballs(tarballDirectory) {
  const entries = await fs.readdir(tarballDirect

await fs.readdir(entryPath, { withFileTypes: true })
      for (const scopeEntry of scopeEntries) {
        if (!scopeEntry.isDirectory()) continue
        const tarballPath = await findTarballInDir(
          path.join(entryPath, scopeEntry.name)

ory, { withFileTypes: true })
  for (const entry of entries) {
    if (!entry.isDirectory()) continue
    const entryPath = path.join(tarballDirectory, entry.name)
    if (entry.name.startsWith('@')) {
      const scopeEntries =


`,
      'X-GitHub-Api-Version': '2022-11-28',
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  })

  if (!response.ok) {
    const responseText = await response.text()

    throw new Error(
      `GitHub API ${method} ${path} failed (${response.status}): ${responseText}`
    )
  }

  if (response.status === 204) {
    return null
  }

  return response.json()
}

async function git(args, options = {}) {
  const { captureOutput = false

aths changed between two commits, using "\0" delimiters so unusual
 * file names do not affect parsing.
 */
async function getChangedFiles(baseSha, headSha) {
  const stdout = await git(
    ['diff-tree', '-r', '--name-only', '--no-renames', '-z', baseSha,

, ...execaOptions } = options
  const { stdout } = await execa('git', args, {
    stdio: captureOutput ? 'pipe' : 'inherit',
    ...execaOptions,
  })

  return typeof stdout === 'string' ? stdout.trim() : stdout
}

/**
 * List p


h: string = ''

const handleSigTerm = () => process.exit(0)

process.on('SIGINT', handleSigTerm)
process.on('SIGTERM', handleSigTerm)

const onPromptState = (state: {
  value: InitialReturnValue
  aborted: boolean
  exited: boolean
}) => {
  if (state.aborted) {
    // If we don't re-enable the terminal cursor before exiting
    // the program, the cursor will remain hidden
    process.stdout.write('\x1B[?25h')
    process.stdout.write('\n')
    process.exit(1)
  }
}

const program = new Command(packageJson

 message.')
  .option('--ts, --typescript', 'Initialize as a TypeScript project. (default)')
  .option('--js, --javascript', 'Initialize as a JavaScript project.')
  .option('--tailwind', 'Initialize with Tailwind CSS config. (default)')
  .option('--react

.name)
  .version(
    packageJson.version,
    '-v, --version',
    'Output the current version of create-next-app.'
  )
  .argument('[directory]')
  .usage('[directory] [options]')
  .helpOption('-h, --help', 'Display this help


sable import/no-extraneous-dependencies */
import { resolve, dirname, basename, join } from 'node:path'
import { copyFile, mkdir } from 'node:fs/promises'
import { async as glob } from 'fast-glob'

interface CopyOption {
  cwd?: string
  rename?: (basename: string) => string
  parents?: boolean
}

const identity = (x: string) => x

export const copy = async (
  src: string | string[],
  dest: string,
  { cwd, rename = identity, parents = true }: CopyOption = {}
) => {
  const source = typeof src === 'string

  const destRelativeToCwd = cwd ? resolve(cwd, dest) : dest

  return Promise.all(
    sourceFiles.map(async (p) => {
      const dirName = dirname(p)
      const baseName = rename(basename(p))

      const from = cwd ? resolve(cwd, p) : p
      const to =

' ? [src] : src

  if (source.length === 0 || !dest) {
    throw new TypeError('`src` and `dest` are required')
  }

  const sourceFiles = await glob(source, {
    cwd,
    dot: true,
    absolute: false,
    stats: false,
  })


elease-github-auth')

const PUBLISH_RELEASE_JOB_NAME = 'Potentially publish release'
const TRIGGER_RELEASE_WORKFLOW = 'trigger_release.yml'
const RELEASE_COMMIT_REGEX = /^v\d+\.\d+\.\d+(-\w+\.\d+)?$/

// This helper is used by a workflow_run listener on build-and-deploy.
// It decides whether a successful stable backport publish should trigger
// a canary preminor release so canary stays semver-ahead of the backport.

function getArgValue(flag) {
  const index = process.argv.indexOf(flag)
  return index ===

rsionString.slice(1)
    : null
}

async function fetchGitHubJson(url, token) {
  const headers = {
    Accept: 'application/vnd.github+json',
  }

  if (token) {
    headers.Authorization = `Bearer ${token}`
    headers['X-GitHub-Api-Version'] = '2022-11-

 -1 ? undefined : process.argv[index + 1]
}

function parseReleaseVersion(commitMessage) {
  const versionString = commitMessage.split(' ').pop()?.trim()
  return versionString && RELEASE_COMMIT_REGEX.test(versionString)
    ? ve


es the turbo-computed rust fingerprint (target/.rust-fingerprint) as
// the cache key, combined with the target triple. Run `turbo run
// rust-fingerprint` first to compute it.
//
// Usage:
//   node scripts/native-cache.js --restore --target x86_64-unknown-linux-gnu
//   node scripts/native-cache.js --save    --target x86_64-unknown-linux-gnu

const { execSync } = require('child_process')
const { createHash } = require('crypto')
const path = require('path')
const fs = require('fs')
const os = require('os')

arget: { type: 'string', default: '' },
  },
  strict: false,
})

const REPO_ROOT = path.resolve(__dirname, '..')
const NATIVE_DIR = path.join(REPO_ROOT, 'packages/next-swc/native')
const FINGERPRINT = path.join(REPO_ROOT, 'target/.rust-fingerprint')

func

const { parseArgs } = require('node:util')
const { values: flags } = parseArgs({
  args: process.argv.slice(2),
  options: {
    restore: { type: 'boolean', default: false },
    save: { type: 'boolean', default: false },
    t


ss'
import { existsSync } from 'fs'
import globOrig from 'glob'
import { join } from 'path'
import { promisify } from 'util'

export const glob = promisify(globOrig)

export const NEXT_DIR = join(__dirname, '..')

/**
 * @param {string} title
 * @param {string | string[]} command
 * @param {ExecSyncOptions} [opts]
 * @returns {string}
 */
export function exec(title, command, opts?: ExecSyncOptionsWithStringEncoding) {
  if (Array.isArray(command)) {
    logCommand(title, command)
    return execFileSync(com

    })
  }
}

class ExecError extends Error {
  code: number | null
  stdout: Buffer
  stderr: Buffer
}

type ExecOutput = {
  stdout: Buffer
  stderr: Buffer
}

/**
 * @param {string} title
 * @param {string | string[]} command
 * @param {SpawnOptions} [o

mand[0], command.slice(1), {
      stdio: 'inherit',
      cwd: NEXT_DIR,
      ...opts,
    })
  } else {
    logCommand(title, command)
    return execSync(command, {
      stdio: 'inherit',
      cwd: NEXT_DIR,
      ...opts,


default)
 *   --webpack           Use Webpack
 *   --duration=MS       How long to profile after ready (default: 1000)
 *   --cli               Profile just the CLI entry point (runs next --help)
 *
 * Output files:
 *   - dev-turbopack-YYYY-MM-DDTHH-MM-SS.cpuprofile
 *   - cli-turbopack-YYYY-MM-DDTHH-MM-SS.cpuprofile
 *
 * The profile can be loaded in:
 *   - Chrome DevTools (Performance tab -> Load profile)
 *   - VS Code (JavaScript Profile Visualizer extension)
 *   - https://www.speedscope.app/
 *
 * N

 fs = require('fs')

// Parse arguments
const args = process.argv.slice(2)
const getArg = (name, defaultValue) => {
  const arg = args.find((a) => a.startsWith(`--${name}=`))
  return arg ? arg.split('=')[1] : defaultValue
}
const hasFlag = (name) => args.

ote: Currently profiles the parent process only. For child process profiling,
 * additional Next.js changes are needed (see future PRs).
 */

const { spawn, execSync } = require('child_process')
const path = require('path')
const


= require('execa')
const fs = require('node:fs/promises')
const path = require('node:path')

async function main() {
  const [githubSha] = process.argv.slice(2)
  if (!githubSha) {
    throw new Error('Usage: set-preview-version.js <githubSha>')
  }

  const repoRoot = path.resolve(__dirname, '..')

  const [{ stdout: shortSha }, { stdout: dateString }] = await Promise.all([
    execa('git', ['rev-parse', '--short', githubSha]),
    // Source: https://github.com/facebook/react/blob/767f52237cf7892ad07726f21

path.join(repoRoot, 'lerna.json')
  const lernaConfig = JSON.parse(await fs.readFile(lernaConfigPath, 'utf8'))

  // 15.0.0-canary.17 -> 15.0.0
  // 15.0.0 -> 15.0.0
  const [semverStableVersion] = lernaConfig.version.split('-')
  // This can create collid

e3e8bacfc8af839/scripts/release/utils.js#L114
    execa('git', [
      'show',
      '-s',
      '--no-show-signature',
      '--format=%cd',
      '--date=format:%Y%m%d',
      githubSha,
    ]),
  ])

  const lernaConfigPath =


un with tsx

import { NEXT_DIR, exec } from './pack-util'
import fs from 'fs'
import path from 'path'

const TARBALLS = `${NEXT_DIR}/tarballs`

const PROJECT_DIR = path.resolve(process.argv[2])

function realPathIfAny(path: fs.PathLike) {
  try {
    return fs.realpathSync(path)
  } catch {
    return null
  }
}
const packages = {
  next: realPathIfAny(`${PROJECT_DIR}/node_modules/next`),
  'next-swc': realPathIfAny(`${PROJECT_DIR}/node_modules/@next/swc`),
  'next-mdx': realPathIfAny(`${PROJECT_DIR}/node_m

dx`),
  'next-bundle-analyzer': realPathIfAny(
    `${PROJECT_DIR}/node_modules/@next/bundle-anlyzer`
  ),
}

for (const [key, path] of Object.entries(packages)) {
  if (!path) continue
  exec(`Unpack ${key}`, `tar -xf '${TARBALLS}/${key}.tar' -C '${path}'


re('fs/promises')
const prettyBytes = require('pretty-bytes')
const gzipSize = require('next/dist/compiled/gzip-size')
const { nodeFileTrace } = require('next/dist/compiled/@vercel/nft')
const { linkPackages } =
  require('../.github/actions/next-stats-action/src/prepare/repo-setup')()

const MAX_COMPRESSED_SIZE = 250 * 1000
const MAX_UNCOMPRESSED_SIZE = 2.5 * 1000 * 1000

// install next outside the monorepo for clean `node_modules`
// to trace against which helps ensure minimal trace is
// produced.
// re

mp-next-${Date.now()}`)
  const workDir = path.join(tmpdir, `trace-next-${Date.now()}`)
  const origTestDir = path.join(origRepoDir, 'test')
  const dotDir = path.join(origRepoDir, './') + '.'

  await fsp.cp(origRepoDir, repoDir, {
    filter: (item) => {

act and react-dom need to be traced specific to installed
// version so isn't pre-traced
async function main() {
  const tmpdir = os.tmpdir()
  const origRepoDir = path.join(__dirname, '..')
  const repoDir = path.join(tmpdir, `t


path'
import { cyan, green, red } from 'picocolors'
import type { RepoInfo } from './helpers/examples'
import {
  downloadAndExtractExample,
  downloadAndExtractRepo,
  existsInRepo,
  getRepoInfo,
  hasRepo,
} from './helpers/examples'
import type { PackageManager } from './helpers/get-pkg-manager'
import { tryGitInit } from './helpers/git'
import { install } from './helpers/install'
import { isFolderEmpty } from './helpers/is-folder-empty'
import { getOnline } from './helpers/is-online'
import { isWriteab

{ getTemplateFile, installTemplate } from './templates'

export class DownloadError extends Error {}

export async function createApp({
  appPath,
  packageManager,
  example,
  examplePath,
  typescript,
  tailwind,
  eslint,
  biome,
  app,
  srcDir,
  i

le } from './helpers/is-writeable'
import { generateAgentFiles } from './helpers/generate-agent-files'
import { runTypegen } from './helpers/typegen'

import type { Bundler, TemplateMode, TemplateType } from './templates'
import


 sep, posix } from 'node:path'
import { pipeline } from 'node:stream/promises'
import { x } from 'tar'

export type RepoInfo = {
  username: string
  name: string
  branch: string
  filePath: string
}

export async function isUrlOk(url: string): Promise<boolean> {
  try {
    const res = await fetch(url, { method: 'HEAD' })
    return res.status === 200
  } catch {
    return false
  }
}

export async function getRepoInfo(
  url: URL,
  examplePath?: string
): Promise<RepoInfo | undefined> {
  const [, user

 https://github.com/:username/:my-cool-nextjs-example-repo-name.
    t === undefined ||
    // Support GitHub URL that ends with a trailing slash, e.g.
    // https://github.com/:username/:my-cool-nextjs-example-repo-name/
    // In this case "t" will be a

name, name, t, _branch, ...file] = url.pathname.split('/')
  const filePath = examplePath ? examplePath.replace(/^\//, '') : file.join('/')

  if (
    // Support repos whose entire purpose is to be a Next.js example, e.g.
    //


st TARBALLS_DIRNAME = 'tarballs'
const TARBALLS = path.join(NEXT_DIR, TARBALLS_DIRNAME)
const NEXT_PACKAGES = `${NEXT_DIR}/packages`
const NEXT_TARBALL = 'next.tar'
const NEXT_SWC_TARBALL = 'next-swc.tar'
const NEXT_MDX_TARBALL = 'next-mdx.tar'
const NEXT_ENV_TARBALL = 'next-env.tar'
const NEXT_BA_TARBALL = 'next-bundle-analyzer.tar'

type CompressOpt = 'none' | 'strip' | 'objcopy-zlib' | 'objcopy-zstd'

const cliOptions = yargs(hideBin(process.argv))
  .scriptName('pack-next')
  .command('$0', 'Pack Next.j

, {
    alias: 'p',
    type: 'string',
  })
  .option('tar', {
    type: 'boolean',
    describe: 'Create tarballs instead of direct reflinks',
  })
  .option('deployable-tar', {
    type: 'boolean',
    describe:
      'Create tarballs in the target proj

s for testing in external projects')
  .option('js-build', {
    type: 'boolean',
    default: true,
    describe:
      'Build JavaScript code (default). Use `--no-js-build` to skip building JavaScript',
  })
  .option('project'


:\d{2}:\d{2}(?:\.\d+)?Z\s?/

function execGh(args) {
  try {
    return execFileSync('gh', args, {
      encoding: 'utf8',
      maxBuffer: 50 * 1024 * 1024,
    }).trim()
  } catch (error) {
    const command = `gh ${args.join(' ')}`
    console.error(`Command failed: ${command}`)
    console.error(error.stderr || error.message)
    throw error
  }
}

function execGhJson(args) {
  const output = execGh(args)
  return JSON.parse(output)
}

function execGhLines(args) {
  const output = execGh(args)
  if (!ou

', args, {
      stdio: ['ignore', 'pipe', 'pipe'],
    })
    const chunks = []
    let stderr = ''

    child.stdout.on('data', (chunk) => chunks.push(chunk))
    child.stderr.on('data', (chunk) => {
      stderr += chunk
    })
    child.on('close', (co

tput) return []

  return output
    .split('\n')
    .filter((line) => line.trim())
    .map((line) => JSON.parse(line))
}

function execGhTextAsync(args) {
  return new Promise((resolve, reject) => {
    const child = spawn('gh


ca = require('execa')
const resolveFrom = require('resolve-from')
const {
  configureGitHubAuth,
  getGitHubToken,
  getGitHubTokenMissingMessage,
  verifyGitHubApiAccess,
} = require('./release-github-auth')
const { createGitHubReleaseCommit } = require('./release-github-api')

const SEMVER_TYPES = ['patch', 'minor', 'major']

async function main() {
  const args = process.argv
  const releaseType = args[args.indexOf('--release-type') + 1]
  const semverType = args[args.indexOf('--semver-type') + 1]
  cons

e !== 'release-candidate' &&
    releaseType !== 'beta'
  ) {
    console.log(
      `Invalid release type ${releaseType}, must be stable, canary, release-candidate, or beta`
    )
    return
  }
  if (!isCanary && !SEMVER_TYPES.includes(semverType)) {

t isCanary = releaseType === 'canary'
  const isReleaseCandidate = releaseType === 'release-candidate'
  const isBeta = releaseType === 'beta'

  if (
    releaseType !== 'stable' &&
    releaseType !== 'canary' &&
    releaseTyp


 string
  nextEnvTarball: string
  nextBundleAnalyzerTarball: string
  nextSwcTarball: string
}

interface NextPeerDeps {
  react: string
  reactDom: string
  [key: string]: unknown
}

export interface PackageJson {
  dependencies?: Record<string, string>
  peerDependencies?: Record<string, string>
  overrides?: Record<string, string>
  resolutions?: Record<string, string>
  workspaces?: string[] | { packages: string[] }
  [key: string]: unknown
}

/**
 * Main function to patch a project's package.json with

Promise<string> {
  try {
    const packageJsonPath = await findPackageJsonPath(targetProjectPath)
    const packageJsonValue = await readJsonValue(packageJsonPath)
    await patchWorkspacePackageJsonMap(paths, packageJsonValue)
    await writeJsonValue(pa

 Next.js tarball references
 * @param paths Configuration options for patching
 * @returns The path to the patched file
 */
export default async function patchPackageJson(
  targetProjectPath: string,
  paths: DependencyPaths
):


ocess')
const fs = require('fs')
const {
  getGitHubToken,
  getGitHubTokenMissingMessage,
} = require('./release-github-auth')

const cwd = process.cwd()

;(async function () {
  let isCanary = true
  let isReleaseCandidate = false
  let isBeta = false

  try {
    const tagOutput = execSync(
      `node ${path.join(__dirname, 'check-is-release.js')}`
    ).toString()
    console.log(tagOutput)

    if (tagOutput.trim().startsWith('v')) {
      isCanary = tagOutput.includes('-canary')
    }
    isReleaseCa

exiting...')
      return
    }
    throw err
  }

  let tag = isCanary
    ? 'canary'
    : isReleaseCandidate
      ? 'rc'
      : isBeta
        ? 'beta'
        : 'latest'

  try {
    if (!isCanary && !isReleaseCandidate && !isBeta) {
      const vers

ndidate = tagOutput.includes('-rc')
    isBeta = tagOutput.includes('-beta')
  } catch (err) {
    console.log(err)

    if (err.message && err.message.includes('no tag exactly matches')) {
      console.log('Nothing to publish,


*
 * Generate AGENTS.md and CLAUDE.md in the project root.
 * AGENTS.md contains the instructions, CLAUDE.md references it using @ syntax.
 */
export function generateAgentFiles(root: string): void {
  const agentsMdContent = `<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in \`node_modules/next/dist/docs/\` before writing any code. Heed deprecation

 claudeMdContent = `@AGENTS.md
`

  const agentsMdPath = path.join(root, 'AGENTS.md')
  const claudeMdPath = path.join(root, 'CLAUDE.md')

  fs.writeFileSync(agentsMdPath, agentsMdContent, 'utf-8')
  fs.writeFileSync(claudeMdPath, claudeMdContent, 'utf-8')

 notices.
<!-- END:nextjs-agent-rules -->
`

  const


const path = require('path')
const JSON5 = require('next/dist/compiled/json5')

const serverExternals = JSON5.parse(
  fs.readFileSync(
    path.join(
      __dirname,
      '../packages/next/src/lib/server-external-packages.jsonc'
    ),
    'utf8'
  )
)

function validate(docPath) {
  const docContent = fs.readFileSync(
    path.join(__dirname, '..', docPath),
    'utf8'
  )

  const docPkgs = []
  const extraPkgs = []
  const missingPkgs = []

  for (let docPkg of docContent
    .split('opt-ed out:')

sh(docPkg)
    }
  }

  for (const pkg of serverExternals) {
    if (!docPkgs.includes(pkg)) {
      missingPkgs.push(pkg)
    }
  }

  if (extraPkgs.length || missingPkgs.length) {
    console.log(
      'server externals doc out of sync!\n' +
        `Ex

 .pop()
    .split('| Version')
    .shift()
    .split('\n')) {
    docPkg = docPkg.split('`')[1]

    if (!docPkg) {
      continue
    }
    docPkgs.push(docPkg)

    if (!serverExternals.includes(docPkg)) {
      extraPkgs.pu


ion-call'
import { getFontAxes } from './get-font-axes'
import { getGoogleFontsUrl } from './get-google-fonts-url'
import { nextFontError } from '../next-font-error'
import { findFontFilesInCss } from './find-font-files-in-css'
import { getFallbackFontOverrideMetrics } from './get-fallback-font-override-metrics'
import { fetchCSSFromGoogleFonts } from './fetch-css-from-google-fonts'
import { fetchFontFile } from './fetch-font-file'

const cssCache = new Map<string, string | null>()
const fontCache = new Map

ng) {
  // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23
  if (reHasRegExp.test(str)) {
    return str.replace(reReplaceRegExp, '\\$&')
  }
  return str
}

const nextFontGoogleFontLoader: FontL

<string, Buffer | null>()

// regexp is based on https://github.com/sindresorhus/escape-string-regexp
const reHasRegExp = /[|\\{}()[\]^$+*?.-]/
const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g

function escapeStringRegexp(str: stri


disable */
import Image from "next/legacy/image";
import Named, { Blarg } from "next/legacy/image";
import type { ImageProps } from "next/legacy/image";
import { type ImageLoaderProps } from "next/legacy/image";
import Foo from "foo";
import img from "../public/img.jpg";

export default function Home() {
  const myStyle = { color: 'black' };
  return (

Upgrade

      <Image src="/test.jpg" width="100" height="200" />
      <Image src="/test.jpg" width="300" height="400" layout="intri

x' }}>
        <Image
          alt="example alt text"
          src={img}
          layout="fill"
          objectFit="contain"
        />
      </div>
      <Named src="/test.png" width="500" height="400" layout="fixed" />
      <Foo bar="1" />
      <Im

nsic" />
      <Image src="/test.jpg" width="200" height="300" layout="responsive" />
      <Image src="/test.jpg" width="200" height="300" layout="fixed" />
      <div style={{ position: 'relative', width: '300px', height: '500p


om "next/image";
import Named, { Blarg } from "next/image";
import type { ImageProps } from "next/image";
import { type ImageLoaderProps } from "next/image";
import Foo from "foo";
import img from "../public/img.jpg";

export default function Home() {
  const myStyle = { color: 'black' };
  return (
    (

Upgrade

      <Image
        src="/test.jpg"
        width="100"
        height="200"
        style={{
          maxWidth: "100%",
          height: "auto"
        }} />
      <Image

    sizes="100vw"
        style={{
          width: "100%",
          height: "auto"
        }} />
      <Image src="/test.jpg" width="200" height="300" />
      <div style={{ position: 'relative', width: '300px', height: '500px' }}>
        <Image

      src="/test.jpg"
        width="300"
        height="400"
        style={{
          maxWidth: "100%",
          height: "auto"
        }} />
      <Image
        src="/test.jpg"
        width="200"
        height="300"


-nocheck
import { unstable_cacheTag as cacheTagImport, unstable_cacheLife as cacheLifeImport } from 'next/cache'
const { unstable_cacheTag: cacheTagRequire, unstable_cacheLife: cacheLifeRequire } = require('next/cache')
// Alias with same API name, alias should be removed.
import { unstable_cacheTag as cacheTag, unstable_cacheLife as cacheLife } from 'next/cache'
const { unstable_cacheTag: cacheTag, unstable_cacheLife: cacheLife } = require('next/cache')

export function MyComponent() {
  const cacheTagImpo

('redundant')
  const cacheLifeAlias = cacheLife('redundant-time')

  return (

Using cache tag: {cacheTagImportTag}

Using cache life: {cacheLifeImportLife}

Using another tag: {cacheTagRequireTag}

Us

rtTag = cacheTagImport('foo')
  const cacheLifeImportLife = cacheLifeImport('1 hour')
  const cacheTagRequireTag = cacheTagRequire('bar')
  const cacheLifeRequireLife = cacheLifeRequire('2 hours')
  const cacheTagAlias = cacheTag


/ @ts-nocheck
import { cacheTag as cacheTagImport, cacheLife as cacheLifeImport } from 'next/cache'
const { cacheTag: cacheTagRequire, cacheLife: cacheLifeRequire } = require('next/cache')
// Alias with same API name, alias should be removed.
import { cacheTag, cacheLife } from 'next/cache'
const { cacheTag, cacheLife } = require('next/cache')

export function MyComponent() {
  const cacheTagImportTag = cacheTagImport('foo')
  const cacheLifeImportLife = cacheLifeImport('1 hour')
  const cacheTagRequireTag

: {cacheTagImportTag}

Using cache life: {cacheLifeImportLife}

Using another tag: {cacheTagRequireTag}

Using another life: {cacheLifeRequireLife}

Using same alias tag: {cacheTagAlias}

Using sa

= cacheTagRequire('bar')
  const cacheLifeRequireLife = cacheLifeRequire('2 hours')
  const cacheTagAlias = cacheTag('redundant')
  const cacheLifeAlias = cacheLife('redundant-time')

  return (

Using cache tag


ops {
  params: Promise<{
    slug: string;
  }>
}

export function generateMetadata(props: Props, parent: any) {
  console.log({ ...parent })
}

export default async function Page(props: Props) {
  const config = { /* @next-codemod-error 'props' is used with spread syntax (...). Any asynchronous properties of 'props' must be awaited when accessed. */
  ...props }
  return (
    (<Child /* @next-codemod-error 'props' is used with spread syntax (...). Any asynchronous properties of 'props' must be awaited wh

accessed. */
    ...ctx }
  )
}

export function POST(req, ctx) {
  console.log(
    { ...req }
  )
}

export function PATCH(req, ctx) {
  console.log(
    { /* @next-codemod-ignore */ ...ctx }
  )
}

export function PUT(req, ctx) {
  console.log(
    {

en accessed. */
    {...props} {...config} />)
  );
}

export function GET(req, ctx) {
  console.log(
    { /* @next-codemod-error 'ctx' is used with spread syntax (...). Any asynchronous properties of 'ctx' must be awaited when


on getNextjsVersion(cwd: string): NextjsVersionResult {
  try {
    const nextPkgPath = require.resolve('next/package.json', { paths: [cwd] })
    const pkg = JSON.parse(fs.readFileSync(nextPkgPath, 'utf-8'))
    return { version: pkg.version }
  } catch {
    // Not found at root - check for monorepo workspace
    const workspace = detectWorkspace(cwd)
    if (workspace.isMonorepo && workspace.packages.length > 0) {
      const highestVersion = findNextjsInWorkspace(cwd, workspace.packages)

      if (high

      error: 'Next.js is not installed in this project.',
    }
  }
}

function versionToGitHubTag(version: string): string {
  return version.startsWith('v') ? version : `v${version}`
}

interface PullOptions {
  cwd: string
  version?: string
  docsDir?:

estVersion) {
        return { version: highestVersion }
      }

      return {
        version: null,
        error: `No Next.js found in ${workspace.type} workspace packages.`,
      }
    }

    return {
      version: null,


pe PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'

export function getPkgManager(baseDir: string): PackageManager {
  try {
    const lockFile = findUp.sync(
      [
        'package-lock.json',
        'yarn.lock',
        'pnpm-lock.yaml',
        'bun.lock',
        'bun.lockb',
      ],
      { cwd: baseDir }
    )
    if (lockFile) {
      switch (basename(lockFile)) {
        case 'package-lock.json':
          return 'npm'
        case 'yarn.lock':
          return 'yarn'
        case 'pnpm-lock.ya

   return 'npm'
  }
}

export function uninstallPackage(
  packageToUninstall: string,
  pkgManager?: PackageManager
) {
  pkgManager ??= getPkgManager(process.cwd())
  if (!pkgManager) throw new Error('Failed to find package manager')

  let command = 'un

ml':
          return 'pnpm'
        case 'bun.lock':
        case 'bun.lockb':
          return 'bun'
        default:
          return 'npm'
      }
    }
    // No lock file found, default to npm
    return 'npm'
  } catch {


ter/packages/react-dom/src/shared/possibleStandardNames.js
const possibleStandardNames = {
  // HTML
  accept: 'accept',
  acceptcharset: 'acceptCharset',
  'accept-charset': 'acceptCharset',
  accesskey: 'accessKey',
  action: 'action',
  allowfullscreen: 'allowFullScreen',
  alt: 'alt',
  as: 'as',
  async: 'async',
  autocapitalize: 'autoCapitalize',
  autocomplete: 'autoComplete',
  autocorrect: 'autoCorrect',
  autofocus: 'autoFocus',
  autoplay: 'autoPlay',
  autosave: 'autoSave',
  capture: 'capture'

'className',
  cols: 'cols',
  colspan: 'colSpan',
  content: 'content',
  contenteditable: 'contentEditable',
  contextmenu: 'contextMenu',
  controls: 'controls',
  controlslist: 'controlsList',
  coords: 'coords',
  crossorigin: 'crossOrigin',
  dangero

,
  cellpadding: 'cellPadding',
  cellspacing: 'cellSpacing',
  challenge: 'challenge',
  charset: 'charSet',
  checked: 'checked',
  children: 'children',
  cite: 'cite',
  class: 'className',
  classid: 'classID',
  classname:


 */
import { yellow } from 'picocolors'
import spawn from 'cross-spawn'

interface InstallArgs {
  /**
   * Indicate whether to install packages using Yarn.
   */
  useYarn: boolean
  /**
   * Indicate whether there is an active Internet connection.
   */
  isOnline: boolean
  /**
   * Indicate whether the given dependencies are devDependencies.
   */
  devDependencies?: boolean
}

/**
 * Spawn a package manager installation with either Yarn or NPM.
 *
 * @returns A Promise that resolves once the installati

lags: string[] = ['--logLevel', 'error']
  /**
   * Yarn-specific command-line flags.
   */
  const yarnFlags: string[] = []
  /**
   * Return a Promise that resolves once the installation is finished.
   */
  return new Promise((resolve, reject) => {

on is finished.
 */
export function install(
  root: string,
  dependencies: string[] | null,
  { useYarn, isOnline, devDependencies }: InstallArgs
): Promise<void> {
  /**
   * NPM-specific command-line flags.
   */
  const npmF


tus(force) {
  let clean = false
  let errorMessage = 'Unable to determine if git directory is clean'
  try {
    clean = isGitClean.sync(process.cwd())
    errorMessage = 'Git directory is not clean'
  } catch (err) {
    if (err && err.stderr && err.stderr.includes('Not a git repository')) {
      clean = true
    }
  }

  if (!clean) {
    if (force) {
      console.log(`WARNING: ${errorMessage}. Forcibly continuing.`)
    } else {
      console.log('Thank you for using @next/codemod!')
      console.log

xit(1)
    }
  }
}

export function onCancel() {
  process.exit(1)
}

/**
 * When adding a new codemod, ensure to set the target canary version
 * instead of the stable version. This is for `@next/codemod upgrade`
 * to correctly pick up the codemod for th

(
        yellow(
          '\nBut before we continue, please stash or commit your git changes.'
        )
      )
      console.log(
        '\nYou may use the --force flag to override this safety check.'
      )
      process.e


h'
import type { API, FileInfo, Options } from 'jscodeshift'
import { createParserFromPath } from '../parser'

export const globalCssContext = {
  cssImports: new Set<string>(),
  reactSvgImports: new Set<string>(),
}
const globalStylesRegex = /(?<!\.module)\.(css|scss|sass)$/i

export default function transformer(
  file: FileInfo,
  _api: API,
  options: Options
) {
  const j = createParserFromPath(file.path)
  const root = j(file.source)
  let hasModifications = false

  root
    .find(j.ImportDeclaratio

ue

          if (value.startsWith('.')) {
            resolvedPath = nodePath.resolve(nodePath.dirname(file.path), value)
          }
          globalCssContext.cssImports.add(resolvedPath)

          const { start, end } = path.node as any

          if

n)
    .filter((path) => {
      const {
        node: {
          source: { value },
        },
      } = path

      if (typeof value === 'string') {
        if (globalStylesRegex.test(value)) {
          let resolvedPath = val


from 'jscodeshift'
import { createParserFromPath } from '../parser'

export const indexContext = {
  multipleRenderRoots: false,
  nestedRender: false,
}

export default function transformer(
  file: FileInfo,
  _api: API,
  options: Options
) {
  const j = createParserFromPath(file.path)
  const root = j(file.source)
  let hasModifications = false
  let foundReactRender = 0
  let hasRenderImport = false
  let defaultReactDomImport: string | undefined

  root.find(j.ImportDeclaration).forEach((path) => {

 'ImportDefaultSpecifier') {
          defaultReactDomImport = specifier.local.name
        }
      })
    }
    return false
  })

  root
    .find(j.CallExpression)
    .filter((path) => {
      const { node } = path
      let found = false

      if (

  if (path.node.source.value === 'react-dom') {
      return path.node.specifiers.forEach((specifier) => {
        if (specifier.local.name === 'render') {
          hasRenderImport = true
        }
        if (specifier.type ===


.0 --output CLAUDE.md
 */
describe('agents-md e2e (CLI invocation)', () => {
  let testProjectDir
  let originalConsoleLog
  let consoleOutput

  beforeEach(() => {
    // Create isolated test project directory
    const tmpBase = process.env.NEXT_TEST_DIR || os.tmpdir()
    testProjectDir = path.join(
      tmpBase,
      `agents-md-e2e-${Date.now()}-${(Math.random() * 1000) | 0}`
    )
    fs.mkdirSync(testProjectDir, { recursive: true })

    // Mock console.log to capture CLI output
    originalConsoleL

st directory
    if (testProjectDir && fs.existsSync(testProjectDir)) {
      fs.rmSync(testProjectDir, { recursive: true, force: true })
    }
  })

  it('creates CLAUDE.md and .next-docs directory when run with --version and --output', async () => {

og = console.log
    consoleOutput = []
    console.log = (...args) => {
      consoleOutput.push(args.join(' '))
    }
  })

  afterEach(() => {
    // Restore console.log
    console.log = originalConsoleLog

    // Clean up te


rs'
import { BadInput } from './shared'
import {
  getNextjsVersion,
  pullDocs,
  collectDocFiles,
  buildDocTree,
  generateClaudeMdIndex,
  injectIntoClaudeMd,
  ensureGitignoreEntry,
} from '../lib/agents-md'
import { onCancel } from '../lib/utils'

export interface AgentsMdOptions {
  version?: string
  output?: string
}

const DOCS_DIR_NAME = '.next-docs'

function formatSize(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`
  const kb = bytes / 1024
  if (kb < 1024) return `${kb.toFixe

ompts for version + target file)
  // 2. --version provided → --output is REQUIRED (error if missing)
  // 3. --output alone → auto-detect version, error if not found

  let nextjsVersion: string
  let targetFile: string

  if (options.version) {
    // --

d(1)} KB`
  const mb = kb / 1024
  return `${mb.toFixed(1)} MB`
}

export async function runAgentsMd(options: AgentsMdOptions): Promise<void> {
  const cwd = process.cwd()

  // Mode logic:
  // 1. No flags → interactive mode (pr


nder the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */
// Based on https://github.com/reactjs/react-codemod/blob/dd8671c9a470a2c342b221ec903c574cf31e9f57/bin/cli.js
// @next/codemod optional-name-of-transform optional/path/to/src [...options]

import { Command } from 'commander'
import { runUpgrade } from './upgrade'
import { runAgentsMd } from './agents-md'
import { runTransform } from './transform'
import { BadInput } from './shared'

const packageJson = requir

  )
  .argument(
    '[codemod]',
    'Codemod slug to run. See "https://github.com/vercel/next.js/tree/canary/packages/next-codemod".'
  )
  .argument(
    '[source]',
    'Path to source files or directory to transform including glob patterns.'
  )
  .u

e('../package.json')
const program = new Command(packageJson.name)
  .description('Codemods for updating Next.js apps.')
  .version(
    packageJson.version,
    '-v, --version',
    'Output the current version of @next/codemod.'


./lib/handle-package'
import {
  checkGitStatus,
  onCancel,
  TRANSFORMER_INQUIRER_CHOICES,
} from '../lib/utils'

function expandFilePathsIfNeeded(filesBeforeExpansion) {
  const shouldExpandFiles = filesBeforeExpansion.some((file) =>
    file.includes('*')
  )
  return shouldExpandFiles
    ? globby.sync(filesBeforeExpansion)
    : filesBeforeExpansion
}

export const jscodeshiftExecutable = require.resolve('.bin/jscodeshift')
export const transformerDirectory = join(__dirname, '../', 'transforms')

expo

  !TRANSFORMER_INQUIRER_CHOICES.find((x) => x.value === transform)
  ) {
    console.error('Invalid transform choice, pick one of:')
    console.error(
      TRANSFORMER_INQUIRER_CHOICES.map((x) => '- ' + x.value).join('\n')
    )
    process.exit(1)
  }

rt async function runTransform(
  transform: string,
  path: string,
  options: any
) {
  let transformer = transform
  let directory = path

  if (!options.dry) {
    checkGitStatus(options.force)
  }

  if (
    transform &&


e',
  '@next/polyfill-nomodule',
  '@next/swc',
  '@next/react-refresh-utils',
  '@next/third-parties',
]

/**
 * @param query
 * @example loadHighestNPMVersionMatching("react@^18.3.0 || ^19.0.0") === Promise<"19.0.0">
 */
async function loadHighestNPMVersionMatching(query: string) {
  const versionsJSON = execSync(
    `npm --silent view "${query}" --json --field version`,
    { encoding: 'utf-8' }
  )
  const versionOrVersions = JSON.parse(versionsJSON)
  if (versionOrVersions.length < 1) {
    throw new

    // The last entry will be the latest version published.
    // But we want the highest version.
    versionOrVersions.sort(compareVersions)
    return versionOrVersions[versionOrVersions.length - 1]
  }
  return versionOrVersions
}

function endMessage

Error(
      `Found no React versions matching "${query}". This is a bug in the upgrade tool.`
    )
  }
  // npm-view returns an array if there are multiple versions matching the query.
  if (Array.isArray(versionOrVersions)) {


'
import googleFontPreconnect from './rules/google-font-preconnect'
import inlineScriptId from './rules/inline-script-id'
import nextScriptForGa from './rules/next-script-for-ga'
import noAssignModuleVariable from './rules/no-assign-module-variable'
import noAsyncClientComponent from './rules/no-async-client-component'
import noBeforeInteractiveScriptOutsideDocument from './rules/no-before-interactive-script-outside-document'
import noCssTags from './rules/no-css-tags'
import noDocumentImportInPage from './

nkForPages from './rules/no-html-link-for-pages'
import noImgElement from './rules/no-img-element'
import noLocationAssign from './rules/no-location-assign-relative-destination'
import noPageCustomFont from './rules/no-page-custom-font'
import noScriptComp

rules/no-document-import-in-page'
import noDuplicateHead from './rules/no-duplicate-head'
import noHeadElement from './rules/no-head-element'
import noHeadImportInDocument from './rules/no-head-import-in-document'
import noHtmlLi


t { globSync } from 'fast-glob'
import type { Rule } from 'eslint'

/**
 * Process a Next.js root directory glob.
 */
const processRootDir = (rootDir: string): string[] => {
  return globSync(rootDir.replace(/\\/g, '/'), {
    onlyDirectories: true,
  })
}

/**
 * Gets one or more Root, returns an array of root directories.
 */
export const getRootDirs = (context: Rule.RuleContext) => {
  let rootDirs = [context.cwd]

  const nextSettings: { rootDir?: string | string[] } =
    context.settings.next || {}

nextSettings.rootDir

  if (typeof rootDir === 'string') {
    rootDirs = processRootDir(rootDir)
  } else if (Array.isArray(rootDir)) {
    rootDirs = rootDir
      .map((dir) => (typeof dir === 'string' ? processRootDir(dir) : []))
      .flat()
  }

  r


values of a node in a convenient way:
/* example:
    <ExampleElement attr1="15" attr2>
    {   attr1: {
            hasValue: true,
            value: 15
        },
        attr2: {
            hasValue: false
        }
Inclusion of hasValue is in case an eslint rule cares about boolean values
explicitly assigned to attribute vs the attribute being used as a flag
*/
export default class NodeAttributes {
  attributes: Record<
    string,
    | {
        hasValue?: false
      }
    | {
        hasValue: tr

if (!!attribute.value) {
        // hasValue
        const value =
          typeof attribute.value.value === 'string'
            ? attribute.value.value
            : typeof attribute.value.expression.value !== 'undefined'
              ? attribute.value

ue
        value: any
      }
  >

  constructor(ASTnode) {
    this.attributes = {}
    ASTnode.attributes.forEach((attribute) => {
      if (!attribute.type || attribute.type !== 'JSXAttribute') {
        return
      }


 Recursively parse directory for page URLs.
 */
function parseUrlForPages(urlprefix: string, directory: string) {
  fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, {
    withFileTypes: true,
  })
  const res = []
  fsReadDirSyncCache[directory].forEach((dirent) => {
    // TODO: this should account for all page extensions
    // not just js(x) and ts(x)
    if (/(\.(j|t)sx?)$/.test(dirent.name)) {
      if (/^index(\.(j|t)sx?)$/.test(dirent.name)) {
        res.push(
          `${urlprefix}${dir

ectory() && !dirent.isSymbolicLink()) {
        res.push(...parseUrlForPages(urlprefix + dirent.name + '/', dirPath))
      }
    }
  })
  return res
}

/**
 * Recursively parse app directory for URLs.
 */
function parseUrlForAppDir(urlprefix: string, dire

ent.name.replace(/^index(\.(j|t)sx?)$/, '')}`
        )
      }
      res.push(`${urlprefix}${dirent.name.replace(/(\.(j|t)sx?)$/, '')}`)
    } else {
      const dirPath = path.join(directory, dirent.name)
      if (dirent.isDir


m '../utils/define-rule'
import NodeAttributes from '../utils/node-attributes'

const url = 'https://nextjs.org/docs/messages/google-font-display'

export default defineRule({
  meta: {
    docs: {
      description: 'Enforce font-display behavior with Google Fonts.',
      recommended: true,
      url,
    },
    type: 'problem',
    schema: [],
  },
  create(context) {
    return {
      JSXOpeningElement(node) {
        let message: string | undefined

        if (node.name.name !== 'link') {
          r

nst isGoogleFont =
          typeof hrefValue === 'string' &&
          hrefValue.startsWith('https://fonts.googleapis.com/css')

        if (isGoogleFont) {
          const params = new URLSearchParams(hrefValue.split('?', 2)[1])
          const displayVa

eturn
        }

        const attributes = new NodeAttributes(node)
        if (!attributes.has('href') || !attributes.hasValue('href')) {
          return
        }

        const hrefValue = attributes.value('href')
        co


 { defineRule } from '../utils/define-rule'
import NodeAttributes from '../utils/node-attributes'

const url = 'https://nextjs.org/docs/messages/google-font-preconnect'

export default defineRule({
  meta: {
    docs: {
      description: 'Ensure `preconnect` is used with Google Fonts.',
      recommended: true,
      url,
    },
    type: 'problem',
    schema: [],
  },
  create(context) {
    return {
      JSXOpeningElement(node) {
        if (node.name.name !== 'link') {
          return
        }

 =
          !attributes.has('rel') ||
          !attributes.hasValue('rel') ||
          attributes.value('rel') !== 'preconnect'

        if (
          typeof hrefValue === 'string' &&
          hrefValue.startsWith('https://fonts.gstatic.com') &&

    const attributes = new NodeAttributes(node)
        if (!attributes.has('href') || !attributes.hasValue('href')) {
          return
        }

        const hrefValue = attributes.value('href')
        const preconnectMissing


const url = 'https://nextjs.org/docs/messages/inline-script-id'

export default defineRule({
  meta: {
    docs: {
      description:
        'Enforce `id` attribute on `next/script` components with inline content.',
      recommended: true,
      url,
    },
    type: 'problem',
    schema: [],
  },
  create(context) {
    let nextScriptImportName = null

    return {
      ImportDeclaration(node) {
        if (node.source.value === 'next/script') {
          nextScriptImportName = node.specifiers[0].local

extScriptImportName
        ) {
          return
        }

        const attributeNames = new Set()

        let hasNonCheckableSpreadAttribute = false
        node.openingElement.attributes.forEach((attribute) => {
          // Early return if we already

.name
        }
      },
      JSXElement(node) {
        if (nextScriptImportName == null) return

        if (
          node.openingElement &&
          node.openingElement.name &&
          node.openingElement.name.name !== n


'

const GOOGLE_ANALYTICS_URL = 'www.google-analytics.com/analytics.js'
const GOOGLE_TAG_MANAGER_URL = 'www.googletagmanager.com/gtag/js'

const GOOGLE_ANALYTICS_SRC = GOOGLE_ANALYTICS_URL
const GOOGLE_TAG_MANAGER_SRC = 'www.googletagmanager.com/gtm.js'

const description =
  'Prefer `@next/third-parties/google` when using the inline script for Google Analytics and Tag Manager.'
const url = 'https://nextjs.org/docs/messages/next-script-for-ga'
const ERROR_MSG_GOOGLE_ANALYTICS = `Prefer \`GoogleAnalytics\` c

ng the inline script for Google Tag Manager. See: ${url}`

export default defineRule({
  meta: {
    docs: {
      description,
      recommended: true,
      url,
    },
    type: 'problem',
    schema: [],
  },
  create(context) {
    return {
      JSXO

omponent from \`@next/third-parties/google\` when using the inline script for Google Analytics. See: ${url}`
const ERROR_MSG_GOOGLE_TAG_MANAGER = `Prefer \`GoogleTagManager\` component from \`@next/third-parties/google\` when usi