From fcbb7bbf8ec5eacd0890a07dd13f522eaed2191d Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 21:03:15 +0000 Subject: [PATCH] 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: : ", 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). --- internal/sync-dropin.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/sync-dropin.mjs b/internal/sync-dropin.mjs index fb3e384fa..853ef3186 100644 --- a/internal/sync-dropin.mjs +++ b/internal/sync-dropin.mjs @@ -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 · 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 [] [--from public] [--only a,b | --from-config] [--prune] [--protect a,b] [--dry-run] ( 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)