Skip to content

refactor: extract duplicated status/report logic into shared utilities - #5

Open
devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1787168097-shared-utilities
Open

devin-ai-integration[bot] wants to merge 1 commit into
masterfrom
devin/1787168097-shared-utilities

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Five patterns were duplicated across formatter.ts, report.ts, scanner.ts, cli.ts and github.ts. They now live in one place, so adding a status or a summary counter is a single-file change instead of four. Output is unchanged (verified byte-for-byte against the pre-refactor code, see below).

New src/link-status.ts owns everything keyed by LinkStatus:

  • STATUS_DISPLAY — replaces the parallel statusEmoji() (report) / statusIcon() (formatter) switch statements, which drifted apart for insecure-http. Stores { emoji, symbol, color } with color as a name ('green' | ...) so the module stays chalk-free; formatter.ts maps names to chalk via a local COLORS record.
  • SUMMARY_ROWS — the Markdown summary table and the console counters line were two hand-maintained lists of the same 7 counters, in the same order, with different labels. Now one list of { key, emoji, label, shortLabel, color }:
    // report.ts
    for (const row of SUMMARY_ROWS) lines.push(`| ${row.emoji} ${row.label} | ${summary[row.key]} |`);
    // formatter.ts
    SUMMARY_ROWS.map((row) => COLORS[row.color](`${summary[row.key]} ${row.shortLabel}`)).join('  ')
  • isFailureStatus / isHealthy / hasIssue — the filter predicates previously inlined in both reporters (r.status === 'broken' || r.status === 'malformed' || r.status === 'timeout', etc.). They accept a structural LinkFacets, so no import cycle with scanner.ts.
  • STATUS_SUMMARY_KEY — summarize() in scanner.ts had one if per status; it now looks up the counter key. insecure-http maps to null (it is counted via the isHttp flag), preserving current behaviour.
  • shouldFailScan(counts, thresholds) — the identical 3-clause exit-code expression in cli.ts and github.ts. Both call sites pass config, which already satisfies FailureThresholds.

ScanSummary now extends StatusCounts instead of re-declaring the 7 counters.

Also deduplicated:

  • URLDN_SHORTENER_URL was defined twice → src/constants.ts.
  • error instanceof Error ? error.message : String(error) appeared 4× → toErrorMessage() in utils.ts; isValidUrl/isInsecureHttp shared a try/catch new URL() → parseUrl().
  • printConsoleReport repeated the same "heading, loop, per-entry line, dimmed detail lines" block 5× → one printSection({ heading, icon, color, results, limit?, suffix?, details?, footer? }) helper.
  • checkUrl built the same CheckResult literal 5× → a local result() closure filling url/isHttp/durationMs/redirectChain, plus classifyStatusCode() for the 2xx/3xx/else branching.
  • INLINE_LINK_RE and IMAGE_MARKDOWN_RE were the same pattern modulo a leading ! → composed from shared LINK_LABEL/LINK_TARGET sources.
  • upsertPullRequestComment spread context.repo instead of repeating owner:/repo: three times.

Verification

Beyond lint, format:check, typecheck and the suite (new test/link-status.test.ts, plus tests for parseUrl/toErrorMessage), I ran a throwaway parity test that imported the pre-refactor src/ from git archive HEAD alongside the new one and asserted identical output for: the console report (over all 48 status/flag combinations, including the ">20 ok links" truncation branch), every verbose line, the Markdown report (issues and no-issues paths), checkUrl across status codes 100..500 with and without redirects, extractLinksFromMarkdown, and scan summaries. All matched exactly; the scaffold was deleted and is not part of this PR.

Link to Devin session: https://app.devin.ai/sessions/5084b99d963a4a8db00d7c0bcbce1c74
Requested by: @lahcenassmira

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@lahcenassmira lahcenassmira self-assigned this Aug 19, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant