Created
August 6, 2026 23:23
-
-
Save hyperknot/9b0256f5c34e29c409fd5a662b2f31bf to your computer and use it in GitHub Desktop.
Minimal Astro 7.2 incrementalBuild image-import cache repro
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| mkdir -p src/pages src/assets | |
| cat > package.json <<'EOF' | |
| { | |
| "private": true, | |
| "type": "module", | |
| "scripts": { "repro": "node repro.mjs" }, | |
| "dependencies": { "astro": "7.2.0" } | |
| } | |
| EOF | |
| cat > astro.config.mjs <<'EOF' | |
| import { defineConfig } from 'astro/config' | |
| export default defineConfig({ | |
| cacheDir: './.cache', | |
| experimental: { incrementalBuild: true }, | |
| }) | |
| EOF | |
| cat > 'src/pages/[slug].astro' <<'EOF' | |
| --- | |
| import red from '../assets/red.png' | |
| import blue from '../assets/blue.png' | |
| export function getStaticPaths() { | |
| return [{ params: { slug: 'post' }, cacheKey: 'unchanged' }] | |
| } | |
| --- | |
| <p>{red.src}</p> | |
| <p>{blue.src}</p> | |
| EOF | |
| printf 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/AAAZ4gk3AAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==' | base64 -d > src/assets/red.png | |
| printf 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAP+KeNJXAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==' | base64 -d > src/assets/blue.png | |
| cat > repro.mjs <<'EOF' | |
| import { spawnSync } from 'node:child_process' | |
| import { createHash } from 'node:crypto' | |
| import { readFileSync, readdirSync, rmSync } from 'node:fs' | |
| import { relative, resolve } from 'node:path' | |
| const route = 'src/pages/[slug].astro' | |
| rmSync('dist', { recursive: true, force: true }) | |
| rmSync('.cache', { recursive: true, force: true }) | |
| let previous = build() | |
| for (let buildNumber = 1; buildNumber <= 50; buildNumber++) { | |
| rmSync('dist', { recursive: true, force: true }) | |
| const current = build() | |
| const before = previous.manifest | |
| const after = current.manifest | |
| if ( | |
| before.configHash === after.configHash && | |
| before.lockfileHash === after.lockfileHash && | |
| JSON.stringify(before.routes[route].paths) === JSON.stringify(after.routes[route].paths) && | |
| before.routes[route].dependencyHash !== after.routes[route].dependencyHash && | |
| JSON.stringify(previous.files) === JSON.stringify(current.files) && | |
| !current.output.includes('(restored)') | |
| ) { | |
| console.log(`Reproduced on build ${buildNumber}`) | |
| console.log(`${before.routes[route].dependencyHash} -> ${after.routes[route].dependencyHash}`) | |
| console.log('Config, lockfile, cache key, and dist hashes are unchanged; route was not restored.') | |
| process.exit(0) | |
| } | |
| previous = current | |
| } | |
| throw new Error('Not reproduced in 50 builds; run pnpm repro again') | |
| function build() { | |
| const result = spawnSync('pnpm', ['exec', 'astro', 'build'], { encoding: 'utf8' }) | |
| if (result.status !== 0) throw new Error(result.stdout + result.stderr) | |
| return { | |
| manifest: JSON.parse(readFileSync('.cache/incremental-build.json')), | |
| files: hashes('dist'), | |
| output: result.stdout + result.stderr, | |
| } | |
| } | |
| function hashes(directory) { | |
| const result = {} | |
| for (const entry of readdirSync(directory, { recursive: true, withFileTypes: true })) { | |
| if (!entry.isFile()) continue | |
| const file = resolve(entry.parentPath, entry.name) | |
| result[relative(directory, file)] = createHash('sha256').update(readFileSync(file)).digest('hex') | |
| } | |
| return Object.fromEntries(Object.entries(result).sort()) | |
| } | |
| EOF | |
| pnpm install | |
| pnpm repro |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment