BrighterCommand / BrighterCommand/Brighter
CI guard: fail the build when a `.csproj` is not referenced by `Brighter.slnx`
- Dominant language
- C#
- Stars
- 2.5k
- Forks
- 296
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 21
Description
A project can sit under `samples/`, compile perfectly for anyone who opens it, and still never
be built by CI — because CI builds `Brighter.slnx` and the project was never added to it.
That happened with `TodoApi` (#4272): it went in with #3953 on 2026-02-01 and was still
unreferenced roughly six months later.
### Why it goes unnoticed
A missing solution entry is invisible from every direction anyone actually looks:
- the file is in the right directory, so a listing looks correct
- it compiles when opened in an IDE, so the author sees it work
- **CI cannot go red over it** — a project outside the solution cannot fail the build
- no output anywhere says *"and one project was skipped"*
The failure mode is silence, and silence is indistinguishable from success. The only way to
find it is to compare two lists nobody has a reason to compare.
### Suggested guard
```bash
diff <(git ls-files '*.csproj' | sort) \
<(grep -o 'Path="[^"]*\.csproj"' Brighter.slnx | sed 's/Path="//;s/"//' | sort)
```
Run against `master` today this prints exactly one line — `TodoApi` — and exits 1. Once that
is registered it is clean, so it can go straight into `ci.yml` as a step.
Two details that are worth keeping if you rewrite it, because both cost me a wrong answer
first:
- **`git ls-files`, not `find`.** `find` reads the working filesystem, which picks up
`bin/`/`obj/` copies and untracked scratch projects. It also reports whatever casing the
local filesystem has: on a case-insensitive macOS checkout `find` shows
`src/Paramore.Brighter.MessageScheduler.Aws` while the repository — and Linux CI — has
`.AWS`. A naive `find`-based diff therefore reports phantom mismatches that do not exist on
the runner. `git ls-files` reports what the repository contains.
- **Scope the whole repo, not just `samples/`.** `.csproj` files live under `benchmarks/`,
`samples/`, `src/`, `tests/` and `tools/`, and the solution references all five. An
unregistered *test* project is worse than an unregistered sample — it is a suite that
reports nothing rather than failing.
One decision I did not want to make for you: **whether a deliberate exclusion needs an escape
hatch.** If some project is intentionally out of the solution, the guard wants an allowlist or
it becomes a check people learn to ignore. If none exist, it can stay strict, which is better.
Happy to open a PR once the scope and the allowlist question are settled.
Contributor guide
Assessment
This issue has not been assessed yet.