diff --git a/jest.config.js b/jest.config.js index b1219cbd5819c..9f81261a7eff8 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,6 +7,7 @@ const createJestConfig = nextJest() const customJestConfig = { displayName: process.env.IS_WEBPACK_TEST ? 'webpack' : 'Turbopack', testMatch: ['**/*.test.js', '**/*.test.ts', '**/*.test.jsx', '**/*.test.tsx'], + globalSetup: '/jest-global-setup.ts', setupFilesAfterEnv: ['/jest-setup-after-env.ts'], verbose: true, rootDir: 'test', diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 225fb3b1f69ae..01ac089ed4604 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -2684,6 +2684,16 @@ export async function build(task, opts) { ['precompile', 'compile', 'check_error_codes', 'generate_types'], opts ) + // Write git commit hash to dist for stale build detection during tests + try { + const { stdout: commitHash } = await execa('git', ['rev-parse', 'HEAD']) + await fs.writeFile( + join(__dirname, 'dist', '.build-commit'), + commitHash.trim() + ) + } catch (err) { + console.warn(`Warning: Could not write build commit hash: ${err.message}`) + } } export async function generate_types(task, opts) { diff --git a/run-tests.js b/run-tests.js index 6c776d119ffcb..da96dd179fbd1 100644 --- a/run-tests.js +++ b/run-tests.js @@ -13,6 +13,7 @@ const glob = promisify(_glob) const exec = promisify(execOrig) const core = require('@actions/core') const { getTestFilter } = require('./test/get-test-filter') +const { checkBuildFreshness } = require('./test/lib/check-build-freshness') // Do not rename or format. sync-react script relies on this line. // prettier-ignore @@ -219,6 +220,9 @@ async function main() { // Ensure we have the arguments awaited from yargs. argv = await argv + // Check for stale or missing build + await checkBuildFreshness() + // `.github/workflows/build_reusable.yml` sets this, we should use it unless // it's overridden by an explicit `--concurrency` argument. const envConcurrency = diff --git a/test/jest-global-setup.ts b/test/jest-global-setup.ts new file mode 100644 index 0000000000000..5b668053cca28 --- /dev/null +++ b/test/jest-global-setup.ts @@ -0,0 +1,5 @@ +import { checkBuildFreshness } from './lib/check-build-freshness' + +export default async function globalSetup() { + await checkBuildFreshness() +} diff --git a/test/lib/check-build-freshness.js b/test/lib/check-build-freshness.js new file mode 100644 index 0000000000000..6b100e4d64c4b --- /dev/null +++ b/test/lib/check-build-freshness.js @@ -0,0 +1,59 @@ +const { existsSync } = require('fs') +const fsp = require('fs/promises') +const path = require('path') +const { execSync } = require('child_process') + +const YELLOW = '\x1b[33m' +const RESET = '\x1b[0m' + +/** + * Check if the Next.js build is fresh (matches current git HEAD). + * Prints warnings to console if build is missing or stale. + * @returns {Promise} + */ +async function checkBuildFreshness() { + const distPath = path.join(__dirname, '../../packages/next/dist') + const buildCommitPath = path.join(distPath, '.build-commit') + + if (!existsSync(distPath)) { + console.warn(`${YELLOW}⚠️ WARNING: No build found!${RESET}`) + console.warn( + `${YELLOW} The packages/next/dist directory does not exist.${RESET}` + ) + console.warn( + `${YELLOW} Run \`pnpm build\` before running tests.\n${RESET}` + ) + return + } + + if (!existsSync(buildCommitPath)) { + console.warn(`${YELLOW}⚠️ WARNING: Build may be stale!${RESET}`) + console.warn( + `${YELLOW} Unable to verify build freshness (no .build-commit marker).${RESET}` + ) + console.warn(`${YELLOW} Run \`pnpm build\` to rebuild.\n${RESET}`) + return + } + + try { + const buildCommit = (await fsp.readFile(buildCommitPath, 'utf8')).trim() + const currentCommit = execSync('git rev-parse HEAD', { + encoding: 'utf8', + }).trim() + + if (buildCommit !== currentCommit) { + console.warn(`${YELLOW}⚠️ WARNING: Build is stale!${RESET}`) + console.warn( + `${YELLOW} Build was compiled at commit: ${buildCommit.slice(0, 8)}${RESET}` + ) + console.warn( + `${YELLOW} Current HEAD is at commit: ${currentCommit.slice(0, 8)}${RESET}` + ) + console.warn(`${YELLOW} Run \`pnpm build\` to rebuild.\n${RESET}`) + } + } catch (err) { + // Ignore errors (e.g., git not available) + } +} + +module.exports = { checkBuildFreshness }