feat(diff): --exit-code so olcli diff can gate CI - #57
Conversation
Follow-up to aloth#45, left open when the core diff command shipped in 0.10.0. A unified diff is the right artifact for a developer and the wrong one for a thesis advisor, who expects the revision marked up in the document. --latexdiff runs on the two sides diff has already fetched, so the markup means what the patch output means and costs no extra request. --pdf compiles it with Overleaf's compiler, so a reviewable PDF needs no local TeX installation. Root document detection, argument construction, output naming and failure interpretation are functions over data in src/latexdiff.ts, unit-tested with no Overleaf account and no latexdiff binary. Refs aloth#55
Second follow-up left open by aloth#48, alongside --latexdiff. Reports diff(1)'s statuses: 0 when nothing differs, 1 when something does, 2 when the run itself failed. The 1-vs-2 split is the whole feature. A pipeline that cannot separate "the project differs" from "the run failed" reads an expired session cookie as a content change, and a job that goes red for the wrong reason sends whoever reads the log looking for a diff that was never computed. All ten failure paths in the command move to 2 together, so no failure can be mistaken for a difference. Without the flag they stay 1, as in every other command, so existing scripts are unaffected. The status is set rather than exited on. process.exit discards whatever is still buffered on a non-TTY stdout, and `olcli diff --exit-code > patch.txt` is exactly a large patch going into a pipe: exiting outright truncated a 1.5 MB patch to the 128 KB pipe buffer here, losing 91% of it mid-hunk. The gate covers whatever was compared, so --file narrows it the way a git diff --exit-code pathspec does, and under --latexdiff it reports on the project rather than on the markup - a changed figure is a real difference even though a marked-up root document cannot show one. Refs aloth#48
|
Built and tested locally: The 1-vs-2 split is the part that makes this worth having, and moving all ten failure paths together is what keeps it honest. A gate that reports an expired cookie as a content change is worse than no gate, because the job goes red and the log sends you looking for a diff that was never computed. Setting Stacking this on #56 rather than cutting it from |
The second follow-up left open in #48, alongside
--latexdiff. Makesolcli diffusable as a CI gate.What it does
The 1-vs-2 split is the whole feature
diff(1)'s statuses, not just "non-zero on changes". A pipeline that cannot separate the project differs from the run failed reads an expired session cookie as a content change — and a job that goes red for the wrong reason sends whoever reads the log hunting for a diff that was never computed. That is worse than having no gate.So all ten failure paths in the command move to
2together: bad flag combinations, a missing directory, an unresolvable project, alatexdiffthat is not installed, a root document that is not on the remote yet, a failed remote compile. If any one of them had stayed at1, the flag would be quietly lying in exactly the case it exists for.Without the flag every failure stays
1, which is what every other command exits, so scripts that checkolcli difffor success see no change. Verified in both directions:--exit-code001021SIGINTduring--pdfstill exits130, untouched.The status is set, not exited on
process.exitdiscards whatever is still buffered on a non-TTY stdout, andolcli diff --exit-code > patch.txtis precisely a large patch going into a pipe. Measured rather than assumed:So the command sets
process.exitCodeand returns, letting node flush first. Confirmed against a real project: a 1.5 MB patch redirected to a file arrived complete, trailer line and all, with status1.Scope of the gate
It reports on whatever was compared, which settles the two questions I expected you to ask:
--filenarrows the gate to that file, the way agit diff --exit-codepathspec does. A--filematching nothing is0rather than an error — also git's behaviour.--latexdiffis allowed and reports on the project, not on the markup. A changed figure is a real difference even though a marked-up root document cannot show one; the existingNo .tex file differsline already says so. I considered rejecting the combination, as--name-only/--file/-Uare rejected, but those shape patch output and this does not — keeping it orthogonal is what makes it composable in CI.One consequence worth stating: a remote-only file counts as a difference, even though plain
pushleaves it alone. The two sides genuinely do not match, and a gate that passed would be saying they do.Structure and tests
The decision is a pure function in
src/diff.ts—differencesExitCode(entries)plus the three named statuses — unit-tested with no account and no network, following therename-plan.tsprecedent. Not re-exported from the package root; happy to export it if you would rather it were public.5 unit tests in
test/diff.test.ts(now 84 innpm test, all passing), pinning that unchanged files are not differences — a gate fed an unfilteredcompareTreesresult must not fire on a project that matches its remote exactly — and that failure stays distinguishable from differences.8 e2e cases in
test/e2e.sh: clean tree, a difference, the unchanged-without-the-flag control,--filenarrowing in both directions, the redirected-output case, and both failure statuses. The two failure cases need no network.Also exercised by hand against a real project: clean pull →
0; an edited file →1for plain,--name-only,--fileand--latexdiff;--fileon an unchanged file →0; bad flags and a missing directory →2with the flag and1without.--pdfwas not run against anyone's project.Docs
README.mdgains a CI-gate section with the status table and a workflow snippet,SKILL.mda line in the diff section and a tip, and the--helptext explains the2. The changelog entry records why the status is set rather than exited on, since that is the part most likely to be "simplified" later.Refs #48