UNPKG

next

Version:

The React Framework

216 lines (215 loc) • 8.43 kB
import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; import { execSync } from 'child_process'; import { getPageFromPath } from '../entries'; import originalDebug from 'next/dist/compiled/debug'; import { Sema } from 'next/dist/compiled/async-sema'; import { generateShuttleManifest } from './store-shuttle'; import { updateIncrementalBuildMetrics } from '../../diagnostics/build-diagnostics'; const debug = originalDebug('next:build:flying-shuttle'); function deepEqual(obj1, obj2) { if (obj1 === obj2) return true; if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { return false; } let keys1 = Object.keys(obj1); let keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (let key of keys1){ if (!keys2.includes(key) || !deepEqual(obj1[key], obj2[key])) { return false; } } return true; } let _hasShuttle = undefined; export async function hasShuttle(config, shuttleDir) { if (typeof _hasShuttle === 'boolean') { return _hasShuttle; } let foundShuttleManifest; async function pruneCache() { await fs.promises.rm(shuttleDir, { force: true, recursive: true }); await fs.promises.mkdir(shuttleDir, { recursive: true }); } try { foundShuttleManifest = JSON.parse(await fs.promises.readFile(path.join(shuttleDir, 'shuttle-manifest.json'), 'utf8')); _hasShuttle = true; } catch (err) { _hasShuttle = false; console.log(`Failed to read shuttle manifest`); // prune potentially corrupted cache await pruneCache(); return _hasShuttle; } const currentShuttleManifest = JSON.parse(generateShuttleManifest(config)); if (currentShuttleManifest.nextVersion !== foundShuttleManifest.nextVersion) { // we don't allow using shuttle from differing Next.js version // as it could have internal changes console.log(`shuttle has differing Next.js version ${foundShuttleManifest.nextVersion} versus current ${currentShuttleManifest.nextVersion}, skipping.`); _hasShuttle = false; } if (!deepEqual(currentShuttleManifest.config, foundShuttleManifest.config)) { _hasShuttle = false; console.log(`Mismatching shuttle configs`, currentShuttleManifest.config, foundShuttleManifest.config); } if (_hasShuttle) { let currentGitSha = ''; try { currentGitSha = execSync('git rev-parse HEAD').toString().trim(); } catch (_) {} await updateIncrementalBuildMetrics({ currentGitSha, shuttleGitSha: currentShuttleManifest.gitSha }); } else { // prune mis-matching cache await pruneCache(); } return _hasShuttle; } export async function detectChangedEntries({ appPaths, pagesPaths, pageExtensions, distDir, shuttleDir, config }) { const changedEntries = { app: [], pages: [] }; const unchangedEntries = { app: [], pages: [] }; if (!await hasShuttle(config, shuttleDir)) { // no shuttle so consider everything changed console.log(`no shuttle. can't detect changes`); return { changed: { pages: pagesPaths || [], app: appPaths || [] }, unchanged: { pages: [], app: [] } }; } const hashCache = new Map(); async function computeHash(p) { let hash = hashCache.get(p); if (hash) { return hash; } return new Promise((resolve, reject)=>{ const hashInst = crypto.createHash('sha1'); const stream = fs.createReadStream(p); stream.on('error', (err)=>reject(err)); stream.on('data', (chunk)=>hashInst.update(chunk)); stream.on('end', ()=>{ const digest = hashInst.digest('hex'); resolve(digest); hashCache.set(p, digest); }); }); } const hashSema = new Sema(16); let globalEntryChanged = false; const changedDependencies = {}; async function detectChange({ normalizedEntry, entry, type }) { const traceFile = path.join(shuttleDir, 'server', type, `${normalizedEntry}.js.nft.json`); let changed = true; // we don't need to check any further entry's dependencies if // a global entry changed since that invalidates everything if (!globalEntryChanged) { try { const traceData = JSON.parse(await fs.promises.readFile(traceFile, 'utf8')); if (traceData) { let changedDependency = false; await Promise.all(Object.keys(traceData.fileHashes).map(async (file)=>{ try { if (changedDependency) return; await hashSema.acquire(); const relativeTraceFile = path.relative(path.join(shuttleDir, 'server', type), traceFile); const originalTraceFile = path.join(distDir, 'server', type, relativeTraceFile); const absoluteFile = path.join(path.dirname(originalTraceFile), file); if (absoluteFile.startsWith(distDir)) { return; } const prevHash = traceData.fileHashes[file]; const curHash = await computeHash(absoluteFile); if (prevHash !== curHash) { changedDependencies[normalizedEntry] = file; debug('detected change on', { prevHash, curHash, file, entry: normalizedEntry }); changedDependency = true; } } finally{ hashSema.release(); } })); if (!changedDependency) { changed = false; } } else { console.error('missing trace data', traceFile, normalizedEntry); } } catch (err) { console.error(`Failed to detect change for ${entry} ${normalizedEntry}`, err); } } // we always rebuild global entries so we have a version // that matches the newest build/runtime const isGlobalEntry = /(_app|_document|_error)/.test(entry); if (changed || isGlobalEntry) { // if a global entry changed all entries are changed if (changed && !globalEntryChanged && isGlobalEntry) { console.log(`global entry ${entry} changed invalidating all entries`); globalEntryChanged = true; // move unchanged to changed changedEntries[type].push(...unchangedEntries[type]); } changedEntries[type].push(entry); } else { unchangedEntries[type].push(entry); } } // loop over entries and their dependency's hashes // to detect which changed for (const entry of pagesPaths || []){ let normalizedEntry = getPageFromPath(entry, pageExtensions); if (normalizedEntry === '/') { normalizedEntry = '/index'; } await detectChange({ entry, normalizedEntry, type: 'pages' }); } for (const entry of appPaths || []){ let normalizedEntry = getPageFromPath(entry, pageExtensions); if (normalizedEntry === '/not-found') { normalizedEntry = '/_not-found/page'; } await detectChange({ entry, normalizedEntry, type: 'app' }); } await updateIncrementalBuildMetrics({ changedDependencies }); return { changed: changedEntries, unchanged: unchangedEntries }; } //# sourceMappingURL=detect-changed-entries.js.map