webhook: skip npm ci when dependency files are unchanged
Deploys ran `npm ci` unconditionally, which wipes node_modules and rebuilds native modules (better-sqlite3) on every push — ~20 min even for a frontend-only change with no dependency changes. Gate the clean install on package.json / package-lock.json actually changing between the pre-pull HEAD and the new HEAD. As a safety net, still install whenever the build's required modules (@vitejs/plugin-react, @rollup/rollup-linux-x64-*) are missing from node_modules, so a skipped install can never leave an incomplete tree. Frontend-only deploys now skip straight to the build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+62
-14
@@ -323,7 +323,63 @@ function runCapture(command, args, options = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureBuildDependencies() {
|
// Files that, when changed, require a fresh `npm ci`. package.json is included
|
||||||
|
// because npm ci refuses to run against a package.json/lockfile that are out of
|
||||||
|
// sync, so a change to either should trigger a reinstall.
|
||||||
|
const DEPENDENCY_FILES = ['package.json', 'package-lock.json']
|
||||||
|
|
||||||
|
// Resolve the modules the build relies on. Returns the first missing module's
|
||||||
|
// name, or null if all are present. Used both to validate after an install and
|
||||||
|
// to decide whether a skipped install left an incomplete node_modules.
|
||||||
|
function missingBuildDependency() {
|
||||||
|
try {
|
||||||
|
require.resolve('@vitejs/plugin-react')
|
||||||
|
} catch {
|
||||||
|
return '@vitejs/plugin-react'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'linux' && process.arch === 'x64') {
|
||||||
|
const libc = process.report?.getReport?.().header?.glibcVersionRuntime ? 'gnu' : 'musl'
|
||||||
|
const rollup = `@rollup/rollup-linux-x64-${libc}`
|
||||||
|
try {
|
||||||
|
require.resolve(rollup)
|
||||||
|
} catch {
|
||||||
|
return rollup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when any dependency file changed between previousHead and the current
|
||||||
|
// HEAD. Defaults to true (reinstall) whenever we can't determine the range, so
|
||||||
|
// an uncertain state never skips a needed install.
|
||||||
|
async function dependencyFilesChanged(previousHead) {
|
||||||
|
if (!validGitSha(previousHead)) return true
|
||||||
|
try {
|
||||||
|
const changed = await runCapture('git', [
|
||||||
|
'diff',
|
||||||
|
'--name-only',
|
||||||
|
`${previousHead}..HEAD`,
|
||||||
|
'--',
|
||||||
|
...DEPENDENCY_FILES,
|
||||||
|
])
|
||||||
|
return changed.trim().length > 0
|
||||||
|
} catch {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureBuildDependencies(previousHead) {
|
||||||
|
const depsChanged = await dependencyFilesChanged(previousHead)
|
||||||
|
// Skip the clean reinstall (which wipes node_modules and rebuilds native
|
||||||
|
// modules like better-sqlite3) only when no dependency files changed AND the
|
||||||
|
// existing node_modules already has everything the build needs.
|
||||||
|
const canSkip = !depsChanged && missingBuildDependency() === null
|
||||||
|
|
||||||
|
if (canSkip) {
|
||||||
|
console.log('deploy step skipped: npm ci (no package.json/package-lock.json changes)')
|
||||||
|
} else {
|
||||||
await run('npm', ['ci'], {
|
await run('npm', ['ci'], {
|
||||||
env: {
|
env: {
|
||||||
NODE_ENV: 'development',
|
NODE_ENV: 'development',
|
||||||
@@ -334,18 +390,9 @@ async function ensureBuildDependencies() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
const missing = missingBuildDependency()
|
||||||
require.resolve('@vitejs/plugin-react')
|
if (missing) {
|
||||||
} catch {
|
throw new Error(`${missing} is missing after npm install; dependencies were not installed`)
|
||||||
throw new Error('@vitejs/plugin-react is missing after npm install; dev dependencies were not installed')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'linux' && process.arch === 'x64') {
|
|
||||||
const libc = process.report?.getReport?.().header?.glibcVersionRuntime ? 'gnu' : 'musl'
|
|
||||||
try {
|
|
||||||
require.resolve(`@rollup/rollup-linux-x64-${libc}`)
|
|
||||||
} catch {
|
|
||||||
throw new Error(`@rollup/rollup-linux-x64-${libc} is missing after npm install; optional dependencies were not installed`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -596,9 +643,10 @@ async function deploy(push) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await notifyDeployStarted(push)
|
await notifyDeployStarted(push)
|
||||||
|
const previousHead = await runCapture('git', ['rev-parse', 'HEAD']).catch(() => null)
|
||||||
await run('git', ['pull', '--ff-only'])
|
await run('git', ['pull', '--ff-only'])
|
||||||
diff = await deployDiff(push)
|
diff = await deployDiff(push)
|
||||||
await ensureBuildDependencies()
|
await ensureBuildDependencies(previousHead)
|
||||||
await run('npm', ['run', 'build', '--', '--outDir', '../dist-next'])
|
await run('npm', ['run', 'build', '--', '--outDir', '../dist-next'])
|
||||||
validateBuiltDist()
|
validateBuiltDist()
|
||||||
await run('cargo', ['build', '--manifest-path', 'backend/Cargo.toml', '--release'])
|
await run('cargo', ['build', '--manifest-path', 'backend/Cargo.toml', '--release'])
|
||||||
|
|||||||
Reference in New Issue
Block a user