doxa-maps/internal/check-tauri-config.mjs
doxa-maps 0138044650
Some checks are pending
build-gate / build (push) Waiting to run
Commit Cargo.lock: it was ignored by association with real build output
deployment/src-tauri/.gitignore listed /target, /gen and Cargo.lock together
with reasoning that fits the first two. /target is 2GB of compiled output and
/gen is emitted by tauri-build on every compile -- both genuinely
regenerated. A lockfile is not build output: it is the RESOLUTION RECORD, the
only artifact that makes cargo build reproducible.

Cargo's rule splits on crate type -- commit for a binary, ignore for a
library -- and cargo metadata reports this crate as kinds=['bin']
(src/main.rs, no lib.rs). Checked with cargo rather than inferred from the
file layout.

The stakes are concrete: Cargo.toml pins nothing tighter than
tauri = { version = "2" }, while the lockfile pins 430 packages. Without it
an inheritor building next month resolves a different dependency tree than
the one that produced the shipped 64 MB binary, and method 5 is already
"built, not shot", so its build must at least be repeatable.

Verified before committing 109KB: cargo metadata --locked --offline succeeds,
so the lockfile is consistent with the manifest, and it contains zero local
paths or credentials.

check:tauri gains a fifth assertion -- Cargo.lock present AND TRACKED.
Tracked-ness is the point: the file sat on disk the whole time and was still
absent from every clone. Negative-tested against the real state, not a
simulation: the gate FAILED naming the file while it was untracked, and
passed once staged.
2026-09-22 06:53:27 +00:00

144 lines
7.2 KiB
JavaScript

#!/usr/bin/env node
/**
* check-tauri-config.mjs — the Tauri analogue of `cap:verify`.
*
* WHY THIS EXISTS: capacitor.config.json documents the failure mode in capitals — "a wrong
* webDir yields an EMPTY assets/public and a BLANK WHITE SCREEN at launch, NOT AN ERROR."
* Tauri has the identical shape: point `build.frontendDist` at the wrong place, or name a
* window `url` that does not exist in the built tree, and the app launches to a blank white
* window with no message. Neither `cargo build` nor any web gate catches it, because the
* path is only resolved by the webview at runtime.
*
* This check runs with NO Rust toolchain and NO Tauri CLI — it is pure config-vs-filesystem
* assertion, so it works in CI (which installs only bun) exactly as `check:mobile` does.
* What it CANNOT do is prove the binary builds or launches; that still needs
* `cargo tauri build` on a machine with the Tauri CLI and system webview libs.
*
* Exit 0 = the config points at files that exist. Exit 1 = a launch-blank-window defect.
*/
import { existsSync, readFileSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { execSync } from 'node:child_process'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
// CANONICAL LOCATION: deployment/src-tauri/tauri.conf.json, NOT the repo root.
// This was found the expensive way (2026-09-21): the config was first written to the repo
// root, this gate PASSED because it only validated the file's CONTENTS, and `cargo check`
// then compiled 228 crates before tauri-build died with "unable to read Tauri config file
// at .../src-tauri/tauri.conf.json because entity not found". A gate that validates a file
// the toolchain never reads is worse than no gate — it certifies a build that cannot work.
const CONF = join(ROOT, 'deployment', 'src-tauri', 'tauri.conf.json')
const STRAY = join(ROOT, 'tauri.conf.json')
const fail = []
const ok = []
if (!existsSync(CONF)) {
console.error('TAURI-CONFIG: FAIL — deployment/src-tauri/tauri.conf.json is missing.')
if (existsSync(STRAY)) {
console.error(' Found tauri.conf.json at the REPO ROOT instead. tauri-build reads it from')
console.error(' deployment/src-tauri/ only; at the root it is invisible to the build. Move it:')
console.error(' git mv tauri.conf.json deployment/src-tauri/tauri.conf.json')
}
process.exit(1)
}
// A stray root copy is its own defect: two configs, and the one a human edits may be the
// one the toolchain ignores.
if (existsSync(STRAY)) {
fail.push('a SECOND tauri.conf.json exists at the repo root — tauri-build ignores it, so edits there silently do nothing')
}
let conf
try {
conf = JSON.parse(readFileSync(CONF, 'utf8'))
} catch (e) {
console.error(`TAURI-CONFIG: FAIL — tauri.conf.json does not parse: ${e.message}`)
process.exit(1)
}
// 1. identifier must match Capacitor's appId — one product, one reverse-DNS identity.
const capPath = join(ROOT, 'capacitor.config.json')
if (existsSync(capPath)) {
const appId = JSON.parse(readFileSync(capPath, 'utf8')).appId
if (conf.identifier !== appId) {
fail.push(`identifier "${conf.identifier}" != capacitor appId "${appId}" — one product should carry one identity`)
} else {
ok.push(`identifier matches capacitor appId (${appId})`)
}
}
// 2. frontendDist must resolve to a real directory. Tauri resolves it RELATIVE TO deployment/src-tauri/,
// which is the single easiest thing to get wrong in this file.
const dist = conf?.build?.frontendDist
if (!dist) {
fail.push('build.frontendDist is unset — the webview would have nothing to load')
} else {
const resolved = resolve(ROOT, 'deployment', 'src-tauri', dist)
if (!existsSync(resolved)) {
fail.push(`build.frontendDist "${dist}" resolves to ${resolved}, which does not exist`)
} else {
ok.push(`frontendDist "${dist}" -> ${resolved.replace(ROOT + '/', '')}/`)
}
}
// 3. EVERY window url must exist inside frontendDist. This is the blank-white-window check.
const windows = conf?.app?.windows || []
if (!windows.length) {
fail.push('app.windows is empty — no window would open')
}
for (const w of windows) {
if (!w.url) { ok.push(`window "${w.title || '(untitled)'}" uses the default entry`); continue }
const page = resolve(ROOT, 'deployment', 'src-tauri', dist || '.', w.url)
if (!existsSync(page)) {
fail.push(`window url "${w.url}" does not exist in the built tree (${page.replace(ROOT + '/', '')}) — THIS LAUNCHES A BLANK WHITE WINDOW`)
} else {
const bytes = readFileSync(page, 'utf8').length
ok.push(`window url "${w.url}" exists (${bytes} bytes)`)
}
}
// 4. The Rust crate the config depends on must be present.
for (const f of ['deployment/src-tauri/Cargo.toml', 'deployment/src-tauri/build.rs', 'deployment/src-tauri/src/main.rs']) {
if (!existsSync(join(ROOT, f))) fail.push(`${f} is missing — \`cargo tauri build\` would fail`)
else ok.push(`${f} present`)
}
// 5. Cargo.lock must be PRESENT AND TRACKED — added 2026-09-22 after finding it gitignored.
//
// It had been ignored by association with /target and /gen in the same .gitignore. Those two are
// genuinely regenerated; a lockfile is not — it is the RESOLUTION RECORD, the only artifact that
// makes `cargo build` reproducible. Cargo's rule splits on crate type (commit for a binary, ignore
// for a library) and `cargo metadata` reports this crate as kinds=['bin'].
//
// THE STAKES, measured: Cargo.toml pins nothing tighter than `tauri = { version = "2" }` while the
// lockfile pins 430 packages. Without it an inheritor resolves a different dependency tree than the
// one that produced the shipped 64 MB binary — and method 5 is already "built, not shot", so its
// build must at least be REPEATABLE. Checking tracked-ness (not just existence) is the point: the
// file was on disk the whole time and still absent from every clone.
{
const rel = 'deployment/src-tauri/Cargo.lock'
if (!existsSync(join(ROOT, rel))) {
fail.push(`${rel} is missing — run \`cargo metadata\` in deployment/src-tauri to generate it`)
} else {
let tracked = false
try {
execSync(`git ls-files --error-unmatch ${JSON.stringify(rel)}`, { cwd: ROOT, stdio: 'pipe' })
tracked = true
} catch { /* not tracked */ }
if (!tracked) fail.push(`${rel} exists but is NOT TRACKED — a clone gets no lockfile, so the build is not reproducible`)
else ok.push(`${rel} present and tracked (reproducible build)`)
}
}
for (const line of ok) console.log(` · ${line}`)
if (fail.length) {
console.error(`\nTAURI-CONFIG: FAIL — ${fail.length} problem(s):`)
for (const f of fail) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log('\nTAURI-CONFIG: PASS — config points at files that exist; no blank-window defect.')
console.log(' This is the CONFIG contract only. Separately verified 2026-09-21 with the real')
console.log(' toolchain: `cargo check` compiles the crate clean (0 errors, 0 warnings), so the')
console.log(' config parses under Tauri\'s STRICT schema and the icon embeds.')
console.log(' STILL NOT PROVEN: that a packaged binary launches and renders. That needs')
console.log(' `cargo install tauri-cli` then `cargo tauri build` — status stays "built, not shot".')