Skip to content

Instantly share code, notes, and snippets.

@hyperknot
Created August 6, 2026 23:23
Show Gist options
  • Select an option

  • Save hyperknot/9b0256f5c34e29c409fd5a662b2f31bf to your computer and use it in GitHub Desktop.

Select an option

Save hyperknot/9b0256f5c34e29c409fd5a662b2f31bf to your computer and use it in GitHub Desktop.
Minimal Astro 7.2 incrementalBuild image-import cache repro
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