sync-dropin: a build config that won't parse fails loudly, even under --if-present

From studio's review of 87f10ceda. configDest swallowed a JSON parse error, so with a malformed
vite.doxa-maps-build-config.json (one trailing comma from a hand edit), --if-present read it as "no
destination configured" and exited 0. The build went green and never refreshed the site. The parse
error is now recorded when the file EXISTS. Under --if-present or --from-config the command exits 1
with "build config unreadable: <file>: <error>", even with DOXA_DROPIN=off. A config file that is
absent, or one with no dropin.dest, still skips.
Tested 7 ways:
1. dest configured, host absent: skip, exit 0.
2. host present: 1,208 files copied, exit 0.
3. no dest key: skip, exit 0.
4. no dest key, plain --from-config: exit 1.
5. trailing comma: exit 1, "build config unreadable".
6. trailing comma with DOXA_DROPIN=off: exit 1.
7. no config file: skip, exit 0.
The config was restored byte for byte (cmp).
This commit is contained in:
Doxa Maps 2026-09-29 21:03:15 +00:00
parent 225eadef11
commit fcbb7bbf8e

View file

@ -62,7 +62,10 @@ const VALUE_FLAGS = new Set(['--from', '--tree', '--protect', '--only'])
const argOf = (k, d) => { const i = args.indexOf(k); const v = args[i + 1]; return i >= 0 && v !== undefined && !v.startsWith('--') ? v : d }
// Destination: positional <dest> · else DOXA_DROPIN_DEST · else "dropin.dest" in the build-config
// (the ONE place the host folder is declared; moving the drop-in = changing that line).
const configDest = (() => { try { const d = JSON.parse(readFileSync(CONFIG, 'utf8')).dropin?.dest; return typeof d === 'string' && d.trim() ? resolve(ROOT, d.trim()) : null } catch { return null } })()
// A config that EXISTS but does not parse is recorded, never swallowed: with --if-present a silent null would read
// as "no destination configured" and a green build would never refresh the site.
let configBroken = ''
const configDest = (() => { try { const d = JSON.parse(readFileSync(CONFIG, 'utf8')).dropin?.dest; return typeof d === 'string' && d.trim() ? resolve(ROOT, d.trim()) : null } catch (e) { if (existsSync(CONFIG)) configBroken = e.message; return null } })()
const DEST = args.find((a, i) => !a.startsWith('--') && !VALUE_FLAGS.has(args[i - 1])) || process.env.DOXA_DROPIN_DEST || configDest
const FROM = resolve(ROOT, argOf('--from', 'public'))
const DRY = args.includes('--dry-run')
@ -86,6 +89,10 @@ const excludedInternal = (f) => EXCLUDE.has(f) || isDoc(f) || isStaging(f)
const excluded = (f) => excludedInternal(f) || isNotShipped(f)
const usage = 'usage: node internal/sync-dropin.mjs [<dest>] [--from public] [--only a,b | --from-config] [--prune] [--protect a,b] [--dry-run] (<dest> defaults to $DOXA_DROPIN_DEST, then dropin.dest in vite.doxa-maps-build-config.json)'
if (configBroken && (args.includes('--if-present') || args.includes('--from-config'))) {
console.error(`[sync-dropin] build config unreadable: ${norm(relative(ROOT, CONFIG))}: ${configBroken}`)
process.exit(1)
}
if (!DEST && args.includes('--if-present')) { console.log('[sync-dropin] skipped: no drop-in destination configured (dropin.dest)'); process.exit(0) }
if (!DEST) { console.error(`[sync-dropin] ${usage}`); process.exit(1) }
const dest = resolve(DEST)