Files
vercel__next.js/scripts/build-native.ts
Tobias Koppers 9a7733ed83 Automatically build and clear native build when running pnpm build (#89819)
## What?

Adds a `maybe-build-native.mjs` script to `packages/next-swc/` that conditionally rebuilds native SWC bindings during `pnpm build`. The script is integrated into the turborepo pipeline as the `build` task for `@next/swc`.

## Why?

Developers often forget to run `pnpm swc-build-native` after pulling Rust changes, leading to confusing runtime errors. Conversely, rebuilding native binaries when nothing changed wastes significant time. This automates the decision so `pnpm build` always does the right thing.

## How?

The `maybe-build-native.mjs` script runs as `@next/swc`'s `build` task and:

1. **Skips in CI** — exits immediately when the `CI` env var is set, since CI uses prebuilt `@next/swc-*` npm packages
2. **Finds the last version bump** — uses `git log -G` to locate the commit that last changed `"version":` in `packages/next/package.json`
3. **Checks for Rust changes** — compares `*.rs` files against the working tree (committed + staged + unstaged) since that version bump commit
4. **Rebuilds if needed** — runs `build-native` and copies/formats the generated TypeScript definitions
5. **Clears stale binaries** — if no Rust changes are detected, removes any leftover `.node` files from `native/` so the prebuilt npm packages are used instead

Turborepo's `turbo.json` is configured with Rust-specific inputs (`crates/`, `Cargo.*`, `rust-toolchain`) and `CI` in `env` so CI and local builds get separate cache keys.

### Files changed
- `packages/next-swc/maybe-build-native.mjs` — new script with the conditional build logic
- `packages/next-swc/package.json` — added `"build": "node maybe-build-native.mjs"`
- `packages/next-swc/turbo.json` — added `build` task with Rust inputs, `CI` env, and `native/*.node` outputs
2026-02-19 13:24:33 +01:00

84 lines
2.4 KiB
JavaScript

#!/usr/bin/env node
import { promises as fs } from '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,
// Without 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 writeTypes()
}
// 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 writeTypes() {
const generatedTypesPath = path.join(
NEXT_DIR,
'packages/next-swc/native/index.d.ts'
)
const vendoredTypesPath = path.join(
NEXT_DIR,
'packages/next/src/build/swc/generated-native.d.ts'
)
const generatedTypesMarker = '// GENERATED-TYPES-BELOW\n'
const generatedNotice =
'// DO NOT MANUALLY EDIT THESE TYPES\n' +
'// You can regenerate this file by running `pnpm swc-build-native` in the root of the repo.\n\n'
const generatedTypes = await fs.readFile(generatedTypesPath, 'utf8')
let vendoredTypes = await fs.readFile(vendoredTypesPath, 'utf8')
const existingContent = vendoredTypes
vendoredTypes = vendoredTypes.split(generatedTypesMarker)[0]
vendoredTypes =
vendoredTypes + generatedTypesMarker + generatedNotice + generatedTypes
const prettifyCommand = ['prettier', '--stdin-filepath', vendoredTypesPath]
logCommand('Prettify generated types', prettifyCommand)
const prettierResult = await execa(
prettifyCommand[0],
prettifyCommand.slice(1),
{
cwd: NEXT_DIR,
input: vendoredTypes,
preferLocal: true,
}
)
vendoredTypes = prettierResult.stdout
if (!vendoredTypes.endsWith('\n')) {
vendoredTypes += '\n'
}
if (vendoredTypes === existingContent) {
return
}
logCommand('Write generated types', `write file`)
await fs.writeFile(vendoredTypesPath, vendoredTypes)
}