ci: fail a PR that leaves an orphaned image under src/content - #12771
Conversation
docs#12728 dropped the real-invoice billing example from billing.mdx —
both <Image> uses and both import lines — and left
invoice-previous-period.png and invoice-next-period.png behind with
nothing pointing at them.
A repo-wide sweep for the same pattern (path-relative-to-images/, not
basename — see the next commit) turned up five more:
merge-protections/freeze-main.png and stacked-gh-pr.png /
stacked-mergify-pr.png were never wired into the pages they were added
for, and workflow/writing-your-first-rule/{summary,config-editor}.png
belonged to a page removed wholesale in 068ac36.
Deleting first, before the check that follows, so that check is never
red on main.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Change-Id: I770291bedc49acb609264a58b7032c9fad738c58
|
This pull request is part of a Mergify stack:
|
Merge Protections🔴 2 of 6 protections blocking · waiting on 👀 reviews
🔴 👀 Review RequirementsWaiting for
This rule is failing.
🔴 🔎 ReviewsWaiting for
This rule is failing.
Show 4 satisfied protections🟢 🤖 Continuous Integration
🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
🟢 📕 PR description
🟢 🚦 Auto-queueWhen all merge protections are satisfied, this pull request will be queued automatically. |
jd
left a comment
There was a problem hiding this comment.
missing entry in mergify config to make this ci mandatory
9c3f2e2 to
e913075
Compare
Revision history
|
There was a problem hiding this comment.
🟡 Changes recommended
The scanner omits supported .avif assets, allowing orphaned AVIF files to pass undetected.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds automated detection of orphaned images under src/content, with tests and CI/merge protections.
Changes:
- Adds a path-aware orphaned-image scanner.
- Adds Vitest coverage, a package script, and documentation.
- Integrates the check into CI and Mergify protections.
File summaries
| File | Summary |
|---|---|
scripts/check-orphaned-images.test.mjs |
Adds scanner and repository tests. |
scripts/check-orphaned-images.mjs |
Implements orphan detection; moderate issue: supported .avif files are excluded. |
package.json |
Adds the check script. |
AGENTS.md |
Documents the command. |
.mergify.yml |
Requires the check for merging. |
.github/workflows/ci.yaml |
Adds the CI validation job. |
Review details
Suppressed comments (1)
scripts/check-orphaned-images.mjs:100
- These needles discard the path above the selected
images/directory. Ifsrc/content/images/hero.pngandsrc/content/docs/images/hero.pngboth exist (both layouts are supported byfindImages), a single import of either produces/images/hero.png, sofindOrphansmarks both files as referenced and lets the other orphan pass. Please either resolve imports per source file or detect duplicate needles and fail as ambiguous.
const idx = parts.lastIndexOf(IMAGES_DIRNAME);
return `/${parts.slice(idx).join('/')}`;
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Prose moves and an asset does not, and a one-off deletion pass alone just recurs on the next page rewrite or removal. Adds scripts/check-orphaned-images.mjs, matching the internal-leaks check's shape (plain-Node scanner, its own vitest suite, pnpm script, CI job): for every image file under any images/ directory within src/content/, it checks whether any source file (src/, integrations/, plugins/) spells out `/images/<path from the nearest such directory>` — the substring every relative import carries, anchored on the leading slash, regardless of how many `../` it climbs. Matches on that anchored path, not on basename alone. A basename-only sweep of the whole tree is what missed workflow/writing-your-first-rule/config-editor.png in the previous commit on the first pass: a same-named config-editor.png under configuration/ is genuinely imported, so the orphan read as referenced. It also does not assume every image lives under src/content/images/ — src/content/docs/images/merge-queue-hero.jpg is real and referenced, sitting next to the page that imports it instead of in the shared tree, so the scan walks for any images/ directory under src/content/ rather than that one path. Also covers .avif (a supported documentation image format the first pass omitted), and fails closed when two images share the same `/images/<...>` needle across different roots: rather than let one import clear both, it reports every image in a colliding group so a human resolves the ambiguity. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Change-Id: I7a8544e1209554c67202175985a75db65b0ac00f
e913075 to
2fa2c6e
Compare
|
Pushed a fix for both things Copilot's review flagged:
|
Prose moves and an asset does not, and a one-off deletion pass alone
just recurs on the next page rewrite or removal. Adds
scripts/check-orphaned-images.mjs, matching the internal-leaks check's
shape (plain-Node scanner, its own vitest suite, pnpm script, CI job):
for every image file under any images/ directory within src/content/,
it checks whether any source file (src/, integrations/, plugins/)
spells out
/images/<path from the nearest such directory>— thesubstring every relative import carries, anchored on the leading
slash, regardless of how many
../it climbs.Matches on that anchored path, not on basename alone. A basename-only
sweep of the whole tree is what missed
workflow/writing-your-first-rule/config-editor.png in the previous
commit on the first pass: a same-named config-editor.png under
configuration/ is genuinely imported, so the orphan read as
referenced. It also does not assume every image lives under
src/content/images/ — src/content/docs/images/merge-queue-hero.jpg is
real and referenced, sitting next to the page that imports it instead
of in the shared tree, so the scan walks for any images/ directory
under src/content/ rather than that one path.
Also covers .avif (a supported documentation image format the first
pass omitted), and fails closed when two images share the same
/images/<...>needle across different roots: rather than let oneimport clear both, it reports every image in a colliding group so a
human resolves the ambiguity.
Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com