mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
027205875b
The previous detection mechanism special-cased `canary` which lead to large diffs when backport branches ran CI. We stop special casing `canary` reducing complexity and giving us another flake-detecting attempt when the change is merged. That way we can safely enable flake detection on backport branches without having to test large diffs when PRs are merged targetting backport branches. New backport branches will no longer have flake detecton and new deploy test runs disabled.
205 lines
5.8 KiB
JavaScript
205 lines
5.8 KiB
JavaScript
// @ts-check
|
|
import { promisify } from 'util'
|
|
import { exec as execOrig, spawn } from 'child_process'
|
|
import { getDiffRevision, getGitInfo } from './git-info.mjs'
|
|
|
|
const exec = promisify(execOrig)
|
|
|
|
const CHANGE_ITEM_GROUPS = {
|
|
docs: [
|
|
'.agents',
|
|
'.claude',
|
|
'.cursor',
|
|
'bench',
|
|
'docs',
|
|
'errors',
|
|
'examples',
|
|
'UPGRADING.md',
|
|
'contributing.md',
|
|
'contributing',
|
|
'CODE_OF_CONDUCT.md',
|
|
'readme.md',
|
|
'.github/ISSUE_TEMPLATE',
|
|
'.github/actions/pr-auto-label/src/config.json',
|
|
'.github/pull_request_template.md',
|
|
// Agent evals and skills do not affect the framework runtime. Keep them on
|
|
// the same lightweight CI path as documentation changes.
|
|
'evals',
|
|
'skills',
|
|
'run-evals.js',
|
|
'packages/next-plugin-storybook/readme.md',
|
|
'packages/next/license.md',
|
|
'packages/next/README.md',
|
|
'packages/eslint-plugin-next/README.md',
|
|
'packages/next-codemod/license.md',
|
|
'packages/next-codemod/README.md',
|
|
'crates/wasm/README.md',
|
|
'packages/next-swc/README.md',
|
|
'packages/next-bundle-analyzer/readme.md',
|
|
'packages/next-mdx/license.md',
|
|
'packages/next-mdx/readme.md',
|
|
'packages/react-dev-overlay/README.md',
|
|
'packages/react-refresh-utils/README.md',
|
|
'packages/create-next-app/README.md',
|
|
'packages/font/README.md',
|
|
'packages/next-env/README.md',
|
|
'packages/next/src/client/components/react-dev-overlay/README.md',
|
|
],
|
|
cna: [
|
|
'packages/create-next-app',
|
|
'test/production/create-next-app',
|
|
'examples/basic-css',
|
|
'examples/mdx-pages',
|
|
'examples/with-sass',
|
|
'examples/with-eslint',
|
|
],
|
|
'next-codemod': ['packages/next-codemod'],
|
|
'next-swc': [
|
|
'packages/next-swc',
|
|
'scripts/normalize-version-bump.js',
|
|
'test/production/create-next-app',
|
|
'scripts/send-trace-to-jaeger',
|
|
],
|
|
// Changes confined to these paths cannot affect a webpack build.
|
|
turbopack: [
|
|
'turbopack/crates',
|
|
'!turbopack/crates/turbo-bincode',
|
|
'!turbopack/crates/turbo-rcstr', // also covers turbo-rcstr-macros
|
|
'!turbopack/crates/turbo-tasks-hash',
|
|
'!turbopack/crates/turbo-tasks-macros',
|
|
'!turbopack/crates/turbo-unix-path',
|
|
],
|
|
}
|
|
|
|
async function main() {
|
|
const { branchName, remoteUrl } = await getGitInfo()
|
|
const diffRevision = await getDiffRevision()
|
|
|
|
const changesResult = await exec(
|
|
`git diff ${diffRevision} --name-only`
|
|
).catch((err) => {
|
|
console.error(err)
|
|
return { stdout: '' }
|
|
})
|
|
|
|
console.error({ branchName, remoteUrl, changesResult })
|
|
const changedFilesOutput = changesResult.stdout
|
|
|
|
const typeIndex = process.argv.indexOf('--type')
|
|
const type = typeIndex > -1 && process.argv[typeIndex + 1]
|
|
const isNegated = process.argv.indexOf('--not') > -1
|
|
const alwaysCanary = process.argv.indexOf('--always-canary') > -1
|
|
|
|
if (!type) {
|
|
throw new Error(
|
|
`Missing "--type" flag, e.g. "node run-for-change.mjs --type docs"`
|
|
)
|
|
}
|
|
const execArgIndex = process.argv.indexOf('--exec')
|
|
const listChangedDirectories = process.argv.includes(
|
|
'--listChangedDirectories'
|
|
)
|
|
|
|
if (execArgIndex < 0 && !listChangedDirectories) {
|
|
throw new Error(
|
|
'Invalid: must provide either "--exec" or "--listChangedDirectories" flag'
|
|
)
|
|
}
|
|
let hasMatchingChange = false
|
|
const changeItems = CHANGE_ITEM_GROUPS[type]
|
|
const execArgs = process.argv.slice(execArgIndex + 1)
|
|
|
|
if (execArgs.length < 1 && !listChangedDirectories) {
|
|
throw new Error('Missing exec arguments after "--exec"')
|
|
}
|
|
|
|
if (!changeItems) {
|
|
throw new Error(
|
|
`Invalid change type, allowed types are ${Object.keys(
|
|
CHANGE_ITEM_GROUPS
|
|
).join(', ')}`
|
|
)
|
|
}
|
|
// an item prefixed with "!" excludes paths another item matched
|
|
const excludedItems = changeItems
|
|
.filter((item) => item.startsWith('!'))
|
|
.map((item) => item.slice(1))
|
|
const includedItems = changeItems.filter((item) => !item.startsWith('!'))
|
|
|
|
let changedFilesCount = 0
|
|
let changedDirectories = new Set()
|
|
|
|
// always run for canary if flag is enabled
|
|
if (alwaysCanary && branchName === 'canary') {
|
|
changedFilesCount += 1
|
|
hasMatchingChange = true
|
|
}
|
|
|
|
for (let file of changedFilesOutput.split('\n')) {
|
|
file = file.trim().replace(/\\/g, '/')
|
|
|
|
if (file) {
|
|
changedFilesCount += 1
|
|
|
|
// if --not flag is provided we execute for any file changed
|
|
// not included in the change items otherwise we only execute
|
|
// if a change item is changed
|
|
const matchesItem =
|
|
!excludedItems.some((item) => file.startsWith(item)) &&
|
|
includedItems.some((item) => {
|
|
const found = file.startsWith(item)
|
|
if (found) {
|
|
changedDirectories.add(item)
|
|
}
|
|
return found
|
|
})
|
|
|
|
if (!matchesItem && isNegated) {
|
|
hasMatchingChange = true
|
|
break
|
|
}
|
|
|
|
if (matchesItem && !isNegated) {
|
|
hasMatchingChange = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we fail to detect the changes run the command
|
|
if (changedFilesCount < 1) {
|
|
console.error(`No changed files detected:\n${changedFilesOutput}`)
|
|
hasMatchingChange = true
|
|
}
|
|
|
|
if (hasMatchingChange) {
|
|
if (listChangedDirectories) {
|
|
console.log(Array.from(changedDirectories).join('\n'))
|
|
return
|
|
}
|
|
const cmd = spawn(execArgs[0], execArgs.slice(1))
|
|
cmd.stdout.pipe(process.stdout)
|
|
cmd.stderr.pipe(process.stderr)
|
|
|
|
await new Promise((resolve, reject) => {
|
|
cmd.on('exit', (code) => {
|
|
if (code !== 0) {
|
|
return reject(new Error('command failed with code: ' + code))
|
|
}
|
|
resolve()
|
|
})
|
|
cmd.on('error', (err) => reject(err))
|
|
})
|
|
} else if (!listChangedDirectories) {
|
|
console.log(
|
|
`No matching changed files for ${isNegated ? 'not ' : ''}"${type}":\n` +
|
|
changedFilesOutput.trim()
|
|
)
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Failed to detect changes', err)
|
|
process.exit(1)
|
|
})
|