Skip to content

react-doctor@0.7.0

Choose a tag to compare

@github-actions github-actions released this 04 Jul 05:40
574cba6

Patch Changes

  • #1060 ced746f Thanks @rayhanadev! - The whole cache stack now survives CI's fresh checkouts, so the GitHub Action's persisted REACT_DOCTOR_CACHE_DIR actually warms every layer instead of only the content-addressed ones. The whole-repo scan-result cache moves under the shared per-project cache root (honoring REACT_DOCTOR_CACHE_DIR like the lint, sidecar, dead-code, and supply-chain caches — previously it silently escaped the action's cache into node_modules/.cache or the OS temp dir), and its key drops every stat-based fingerprint that a re-clone rotates: config files and gitignored dotenv files are now content-hashed, and the toolchain is keyed by package versions (matching the lint ruleset hash) rather than install mtimes. The stat-fingerprinted dead-code caches gain mtime repair (the ninja/restat pattern): entries carry a content-hash witness, and a stat mismatch over identical bytes — every file after a fresh checkout — re-hashes once, accepts the entry, and persists the refreshed stat so the cost is paid once per checkout, not per run. This covers core's whole-project dead-code result cache (per-file (mtime, size, hash) records replacing the stats-in-key fingerprint) and deslop-js's incremental summary store (parse summaries, package-reference facts, and the manifest/bundler-config fingerprints feeding the collect/resolution hashes). Expired supply-chain score entries are also pruned past their TTL so restored cache directories stop accumulating dead purls. Everything stays fail-open and byte-identical to an uncached scan; on a re-cloned repo with one changed file, a warm scan now replays ~100% of lint, sidecar, and parse work instead of starting cold.

  • #1056 20d81f6 Thanks @rayhanadev! - Hardened the dead-code result cache key against two silent-staleness classes. The fingerprinted extension and manifest name lists are now imported from deslop-js/analyzed-inputs — a new dependency-free subpath export assembled from the same constants deslop's own readers consume — so a deslop upgrade that widens its walk can never under-invalidate the cache. And the key now includes @react-doctor/core's own version, so upgrading react-doctor re-analyzes instead of replaying cached diagnostics shaped by an older core's post-processing. The cache schema-version constant remains for cache-format changes only.

  • #1053 e257a5e Thanks @rayhanadev! - Rescans now skip the dead-code analysis entirely when nothing it reads has changed. The pass persists its diagnostics keyed by a fingerprint over the analyzed source tree (stat-based, so additions, deletions, and edits all invalidate), the project's manifests, tsconfigs, lockfiles, knip/entry/ignore configuration, and the analyzer version — on an unchanged-input rerun the stored result replays instead of re-walking the whole import graph, cutting several seconds off warm rescans of large repos. Only complete, successful passes are stored; REACT_DOCTOR_NO_CACHE (or the granular REACT_DOCTOR_NO_DEAD_CODE_CACHE) disables it.

  • #1057 ce49250 Thanks @rayhanadev! - Dead-code analysis is now incremental across scans. deslop-js gains an opt-in incrementalCachePath config field: one stat walk per run (following directory symlinks, like the glob scans it stands in for) validates four independently keyed layers — per-file parse summaries (mtime+size), the collected file list, the module-resolution map (dropped whenever the file set or a bundler/tsconfig-like config changes, since resolution is file-set-dependent), and the per-file package-reference facts behind the unused-dependency content scans. The same walk also answers the stale-package file-discovery globs (config/docs/rescue/package.json/nx/tsconfig scans, verified byte-identical against fast-glob), so a cached run never re-walks the tree for them. Entry resolution deliberately stays live every run (it reads config/doc/sibling-source content no fingerprint can validate); on cached runs it moves to a dedicated worker thread so its filesystem work overlaps the main-thread analysis instead of serializing after it. Every layer fails open — corrupt, truncated, or version/config-mismatched stores degrade to a fresh computation, never a wrong result — writes are atomic and skipped when clean, and results are byte-identical to an uncached run. Unused-export detection also indexes re-export edges by source module, dropping an O(entry points × edges) scan that dominated the detector on large repos.

    react-doctor points the analysis worker at a per-project summary store next to the existing whole-result dead-code cache, so a changed-files rescan of a large repo re-parses only what changed (sentry, 9k files: ~8.0s → ~3.2s with 0-10 files edited; ~4.0s after adding a file; the cache-fill run costs the same as an uncached scan). The worker also stops running the discarded DRY-pattern redundancy detectors (reportRedundancy: false), which shrinks the summary store by dropping fields only those detectors read, and reports the cache outcome as deadCode.summaryCacheHits / deadCode.summaryCacheMisses in anonymized telemetry (absent whenever no analysis consulted the store). REACT_DOCTOR_NO_CACHE (or the granular REACT_DOCTOR_NO_DEAD_CODE_CACHE) disables it.

  • #1054 8c004f0 Thanks @rayhanadev! - Instant reruns now also work with uncommitted changes: the whole-repo scan-result cache no longer requires a clean worktree — it keys on the exact dirty state (every modified, staged, renamed, deleted, and untracked path plus a hash of its content), so rescanning the same work-in-progress tree hits the cache while any edit still invalidates it.

  • #1052 1571119 Thanks @rayhanadev! - Restore instant reruns on large repos: raise runGit's output cap so the whole-repo scan-result cache works past ~15k tracked files.

    The cache's clean-worktree gates shell out to git through a helper that used Node's default 1 MiB maxBuffer. On repos with roughly 15-25k tracked files (getsentry/sentry: 20,343), git ls-files -v alone exceeds that, execFileSync throws ENOBUFS, the helper swallows it into null, and the gates read null as "hidden tracked state" — so the cache silently never stored or served a scan on exactly the repos where the instant-rerun path saves the most time. The helper now runs with an explicit 64 MiB cap, which clears monorepos with hundreds of thousands of files while still bounding a pathological child process.

  • #1058 ea9a775 Thanks @rayhanadev! - Warm rescans now skip the cross-file lint sidecar for files whose dependencies are unchanged. Previously the per-file lint cache replayed most rules but re-linted EVERY file with the cross-file ruleset (no-barrel-import, the Next.js metadata/Suspense rules, no-mutating-reducer-state, the React Native text rules) on every scan, because a sibling edit can flip an unchanged file's verdict. Each file now carries a dependency fingerprint — the exact set of filesystem probes (resolved import targets, barrel chains, ancestor layouts, nearest package.json/tsconfig, and the negative resolution candidates that catch shadowing) its cross-file rules consulted — and the sidecar replays the stored diagnostics when every probe still answers the same, re-linting only files whose dependency set changed. Every entry fails open (corrupt store, unparseable file, partial lint → fresh re-lint), a cross-file rule without a registered dependency collector re-lints everywhere, and REACT_DOCTOR_NO_CACHE (or the granular REACT_DOCTOR_NO_SIDECAR_CACHE) disables it.

  • #1059 bdf8074 Thanks @rayhanadev! - Added cache-temperature telemetry to the per-scan Sentry wide event, so cache effectiveness is queryable at a glance instead of being inferred from per-subsystem dims. cache.temperature classifies every scan as turbo (whole-repo scan-result replay — now marked by an explicit wholeRepoCacheHit flag on the replay path, never inferred from absent dims), warm (any incremental reuse across the per-file lint, sidecar, or dead-code caches), cold (caches on, zero reuse), or disabled (the global REACT_DOCTOR_NO_CACHE off-switch). cache.warmth is the numeric headline magnitude in [0, 1] — the plain mean of the known subsystem reuse fractions, skipping subsystems that never consulted a cache. The existing per-subsystem dims (lint.cacheHitRatio, lint.sidecarReplayRatio, deadCode.cacheHit, deadCode.summaryCacheHits/Misses) are unchanged. Telemetry-only: no new counters, no JSON-report or cache-schema changes.

  • Updated dependencies [ced746f, 20d81f6, ce49250]:

    • deslop-js@0.7.0
    • oxlint-plugin-react-doctor@0.7.0