fix: four second-tier correctness defects (--json truncation, badge NaN, score buckets)#20
Open
PunGrumpy wants to merge 5 commits into
Open
fix: four second-tier correctness defects (--json truncation, badge NaN, score buckets)#20PunGrumpy wants to merge 5 commits into
PunGrumpy wants to merge 5 commits into
Conversation
Establishes a verification baseline for @docker-doctor/cli by spawning the built dist/cli.mjs against small Dockerfile fixtures and asserting on the three documented exit-code paths and the --json report contract.
process.exit() in the scan action's output paths aborted the process before async stdout writes to a pipe/file finished flushing, silently truncating --json/--score output when redirected. Replace the three output-path exit calls with process.exitCode + return so Node exits naturally once the event loop drains and stdout is fully flushed. The six interrupt/signal/error-handler exit calls are untouched. Adds a fixture-driven regression test (200-service fixture, ~475KB of JSON) that reproduces the truncation against the old code and asserts the fix produces byte-identical, fully parseable output whether piped or redirected to a file.
parseQuery only fell back on null, so a non-numeric ?s= value (e.g.
"abc") produced Number("abc") = NaN, which survives Math.min/Math.max
clamping and gets served (and cached for an hour) as "NaN/100" on both
the badge SVG and the OG image.
Extract a single shared helper, parseScoreQuery, in apps/web/lib/score.ts
(fallback applies to null/absent and to any non-finite parse) and use it
from both apps/web/app/share/badge/route.ts and apps/web/app/share/og/route.tsx
instead of two separate ad hoc implementations.
Note: an empty ?s= is a *present* value (Number("") is 0), so it clamps
to 0 rather than falling back -- deliberate, not an oversight.
The score-to-label/color threshold table (90/75/50/0) was duplicated three times: packages/core/src/scoring.ts (4 buckets, inline if/else), apps/web/lib/score.ts (3 buckets, missing Critical), and apps/web/app/share/badge/route.ts (a third, color-only set, also missing Critical). Extract SCORE_BUCKETS + getScoreBucket in packages/core/src/scoring.ts as the single source of truth and export them from packages/core/src/index.ts. calculateScore's returned label strings are byte-identical to before (verified against the exact UTF-8 bytes, including the "Needs Work⚠️ " variation selector) -- covered by a new packages/core/test/scoring.test.ts, including the previously untested "Critical \ud83d\ude a8" bucket. apps/web cannot cleanly depend on @docker-doctor/core: core's raw-TS sources use regex named capturing groups (dockerfile-parser.ts, rules/security.ts) that require `target >= ES2018`, but apps/web's tsconfig targets ES2017, so tsc --noEmit fails across the whole app when core is imported. Per the plan, no fourth copy was created; apps/web/lib/score.ts and the badge route instead gained a completed, NaN-safe, single *local* bucket table (adding the missing Critical bucket, mirroring core's thresholds) so the badge's color mapping and label mapping share one source within apps/web, even though it can't yet be unified with core. See the executor report for the exact error and the maintainer decision this needs (bump apps/web's target, or drop named capture groups from core).
… SCORE_BUCKETS export Covers defect A (process.exitCode instead of process.exit on the scan action's output paths) and defect C (SCORE_BUCKETS/getScoreBucket now exported from @docker-doctor/core, bundled into the CLI).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
React Doctor found no new issues. 🎉 Reviewed by React Doctor for commit |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Fixes four second-tier correctness defects, grouped because each is small and independently verifiable. Implements plan 007. Depends on 002 (CLI integration tests).
Defects
A — piped
--jsonoutput was silently truncated ✅The CLI wrote the report with
console.logthen immediately calledprocess.exit(), which does not flush an async stdout pipe — sodocker-doctor . --json > report.json(or piping tojqin CI) produced truncated, invalid JSON at exactly the sizes that matter. Fixed by switching the three scan output paths (--score,--json, default) toprocess.exitCode = n; return;so Node drains stdout before exiting. The six immediate-abortprocess.exit()calls (two prompt Ctrl-C handlers, SIGINT/SIGTERM, two stderr error paths) are untouched.Verified live: a 200-Dockerfile fixture producing 2400 diagnostics / 1.04 MB of
--jsonnow parses cleanly. The fixture was confirmed to actually reproduce the truncation on the oldprocess.exit()code (JSON.parse failed every run, bytes short of the payload) — a real regression test, not synthetic. Exit codes preserved exactly:--scoreexits 1 below 50;--json/default exit 1 on any error-severity diagnostic.B — badge rendered
NaN/100and cached it for an hour ✅?s=abc→Number("abc")= NaN survived all clamps and was served withCache-Control: public, max-age=3600. Fixed with a sharedparseScoreQueryhelper inapps/web/lib/score.tsthat falls back when the value isn't finite, applied to both the badge and OG routes.grep NaN apps/web/app/share/is now empty. (Empty?s=renders0, a present finite value; absent/nullfalls back to 100 — deliberate.)C — score thresholds diverged into three copies⚠️ partially unified
Extracted
SCORE_BUCKETS+getScoreBucketintopackages/core/src/scoring.tsand exported them frompackages/core/src/index.ts;calculateScorenow derives its label from the table with the returned strings byte-identical (incl. the⚠️U+FE0F variation selector — verified against a hex dump and by a new"Critical 🚨"test).Wiring
apps/webto consume core hit a real blocker and STOPPED per the plan:apps/web/tsconfig.jsontargets ES2017, but core's raw-TS entry pulls indockerfile-parser.ts/security.ts, which use ES2018 named-capture-group regex (TS1503). Rather than create a 4th copy, the web-side core dependency was reverted cleanly (package.json/bun.lockshow no net diff) and the existingapps/web/lib/score.tstable was completed with the missingCriticalbucket. Result: 3 copies → 2 (core is now the single source for core/CLI; the web app has one local table pending the tsconfig decision below).D —
ignore.filesis a validated no-op → reported, NOT implementedIt validates but is read by nothing (
filterDiagnosticsonly consumescategories). Two options are surfaced for the maintainer (implement with a glob dep, which grows the CLI and raises existing users' scores; or remove it so it fails loudly, with a doc fix in plan 009). Neither implemented — this is a product decision.Two decisions for the maintainer
apps/web/tsconfig.jsontarget to ES2018 (safe for Next 16/React 19) so the web app can import core'sSCORE_BUCKETS, achieving a true single source; or keep the local web table.ignore.files.Verification
ultracite0 ·bun run build0 (CLI + core + web)..changeset/quiet-planets-listen.md→@docker-doctor/clipatch (covers defect A exit-flush + defect CSCORE_BUCKETSexport). Defect B isapps/web-only.Stacking & merge note
Base is
advisor/002-cli-verification-baseline(PR #14). Auto-retargets tomainon #14's merge.packages/core/src/scoring.tsandpackages/core/test/scoring.test.ts— 008 changes the formula, this PR refactors the label buckets and adds a scoring test. Whichever merges second needs a small conflict resolution in both files (keep 008's monotonic formula + this PR'sSCORE_BUCKETS, and both test sets).🤖 Generated with Claude Code