The landing precondition rebuilds only the bundles a library change reaches

A change under library/ used to scope freshness to EVERY bundle (~10 min a round). It now checks
each bundle whose source graph contains a changed library file, or that reads a changed i18n
area, found with the same graph walk the i18n tree-shake uses. Only a change to the build itself
(vite config, lockfile, build config, generate-tree, or package.json dependencies) still means all.
This commit is contained in:
Doxa Maps 2026-09-28 20:26:28 +00:00
parent 17516d8b9a
commit b3521c66e1

View file

@ -31,6 +31,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { TREE_NAME } from './_tree-name.mjs'
import { bundleGraph, areasForBundle } from './i18n-areas.mjs'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const refuse = (why, detail = '') => {
@ -106,10 +107,31 @@ if (process.env.DOXA_SKIP_FRESHNESS === '1') {
// every composite, plus each bundle whose own folder the landing touched.
const base = run('git', ['merge-base', 'HEAD', `origin/${process.env.MAIN_CAPTURE_BRANCH || 'main'}`]).stdout.trim()
const changed = base ? run('git', ['diff', '--name-only', base, 'HEAD']).stdout.split('\n').filter(Boolean) : []
const shared = !base || changed.some((f) => /^(library\/|vite\.config\.js$|package\.json$|bun\.lockb?$|vite\.doxa-maps-build-config\.json$)/.test(f))
const own = [...new Set(changed.map((f) => (f.match(/^plugin-designs\/([^/]+)\//) || [])[1]).filter(Boolean))]
const scope = shared ? ['--all'] : (own.length ? ['--bundles', own.join(',')] : [])
console.log(`LANDING-PRECONDITIONS: 4 fresh — scope: ${shared ? 'EVERY bundle (shared code changed' + (base ? '' : '; no merge-base') + ')' : 'composites' + (own.length ? ' + ' + own.join(', ') : '')}`)
// ★ A library/ change no longer means EVERY bundle. It means every bundle whose SOURCE GRAPH contains a
// changed file, or that reads a changed i18n area: measured, a one-file library fix reached 1 of 24
// bundles, and rebuilding all 24 cost ~10 min per landing round. Only the build itself (config,
// dependencies, the tree generator) can reach every bundle, so only that still means --all.
const pkgDepsChanged = () => {
if (!changed.includes('package.json') || !base) return false
const deps = (ref) => { try { const j = JSON.parse(run('git', ['show', `${ref}:package.json`]).stdout); return JSON.stringify([j.dependencies, j.devDependencies]) } catch { return '' } }
return deps(base) !== deps('HEAD')
}
const everything = !base || pkgDepsChanged() || changed.some((f) => /^(vite\.config\.js$|bun\.lockb?$|vite\.doxa-maps-build-config\.json$)/.test(f))
const libChanged = changed.filter((f) => f.startsWith('library/'))
const shared = everything || libChanged.length > 0
const own = new Set(changed.map((f) => (f.match(/^plugin-designs\/([^/]+)\//) || [])[1]).filter(Boolean))
if (!everything && libChanged.length) {
const libSet = new Set(libChanged)
const areas = new Set(libChanged.map((f) => (f.match(/^library\/i18n\/locales\/[^/]+\/([^/]+)\.json$/) || [])[1]).filter(Boolean))
const manifest = JSON.parse(readFileSync(resolve(ROOT, 'public', TREE_NAME, 'manifest.json'), 'utf8'))
for (const { bundle } of manifest.bundles) {
if (!existsSync(resolve(ROOT, 'plugin-designs', bundle, 'index.js'))) continue
const graph = bundleGraph(ROOT, bundle).map((f) => f.slice(ROOT.length + 1))
if (graph.some((f) => libSet.has(f)) || (areas.size && areasForBundle(ROOT, bundle).areas.some((a) => areas.has(a)))) own.add(bundle)
}
}
const scope = everything ? ['--all'] : (own.size ? ['--bundles', [...own].sort().join(',')] : [])
console.log(`LANDING-PRECONDITIONS: 4 fresh — scope: ${everything ? 'EVERY bundle (the build itself changed' + (base ? '' : '; no merge-base') + ')' : (shared ? 'the bundles this library change reaches' : 'composites') + (own.size ? ': ' + [...own].sort().join(', ') : '')}`)
// ★ ONE PASS PER TREE, NOT ONE PER SCAN. This check rebuilds every bundle in its scope (~10 min for
// --all), and it used to run INSIDE the merge-queue lock, so a build-owner landing held main for 30+ min
// while every other landing queued (2026-09-25). The verdict is a function of the committed tree and the