Files
firehose/blogex/Makefile
T
Firehose Bot 97fc024a38 add make setup and network-free deps.check guard
mix deps.get contacts hex.pm even when everything is already installed
(the advisory check), so the everyday loops failed in offline/sandboxed
environments; and the root deps target only installed app's deps, never
blogex's.

make setup (root) installs Hex deps for both apps plus the app's JS
packages, once per clone, explicitly. check, test and test.coverage in
both apps now depend on a local deps.check guard instead: every package
pinned in mix.lock must exist in deps/, else fail loudly pointing at
make setup. AGENTS.md documents the new target.
2026-10-29 17:44:16 +00:00

121 lines
5.8 KiB
Makefile

# Makefile for Blogex library
MISE_BIN ?= mise
MISE_EXEC = $(MISE_BIN) exec --
.PHONY: check precommit deps deps.check compile test test.coverage coverage crap format format.check verify
# Cheap static gate for this library (no database, no network, no coverage).
# The deps.check guard fails loudly with a pointer at `make setup` when Hex
# deps are missing. Runs from the root `make check`, which loops app then
# blogex. `format.check` only reports:
# a gate that rewrites the files it judges can never fail. Fix formatting with
# `make format`.
#
# Sensor slot: add `credo` once the "credo strict, tuned config (both apps)"
# yak lands blogex's credo dep + .credo.exs. Do not list it before then — a
# gate that claims a check it did not run is worse than no gate.
check: deps.check compile format.check
# Per-app precommit loop: same target name and alias shape as app/, so both
# apps share one habit (compile --warning-as-errors, format, test).
precommit:
$(MISE_EXEC) mix precommit
# Sync dependencies
deps:
$(MISE_EXEC) mix deps.get
# Local, network-free guard: every package pinned in mix.lock must be present
# in deps/. check, test and test.coverage depend on this instead of `deps`, so
# the everyday loops never touch the network: `mix deps.get` contacts hex.pm
# even when everything is already installed (the advisory check), which fails
# in offline/sandboxed environments. Fetching is `make setup`'s explicit job,
# run from the repo root.
deps.check:
@test -f mix.lock || { echo "deps.check: no mix.lock - run 'make setup' at the repo root" >&2; exit 1; }
@missing=""; \
for dep in $$(sed -n 's/^[[:space:]]*"\([^"]*\)".*/\1/p' mix.lock); do \
[ -d "deps/$$dep" ] || missing="$$missing $$dep"; \
done; \
test -z "$$missing" || { echo "deps.check: missing:$$missing - run 'make setup' at the repo root" >&2; exit 1; }
# Compile the project (warnings are fatal)
compile:
$(MISE_EXEC) mix compile --warnings-as-errors
# Run the test suite
test: deps.check
$(MISE_EXEC) mix test
# Run the suite once with coverage exported, so the coverage floor check (and,
# later, CRAP) reads the export instead of running the suite a second time.
# Stale exports are removed first: the floor must never report an older run.
test.coverage: deps.check
rm -f cover/*.coverdata
$(MISE_EXEC) mix test --cover --export-coverage default
# Enforce the coverage floor (mix.exs -> test_coverage) from that export. No
# test run. `mix test.coverage` exits 0 when it finds no export at all, so the
# guard below is load-bearing: a floor check that did not run must not pass.
coverage:
@test -f cover/default.coverdata || { echo "coverage: no exported coverage - run 'make test.coverage' first" >&2; exit 1; }
$(MISE_EXEC) mix test.coverage
# Enforce CRAP scores (complexity x coverage) from the same export. No test run.
# Runs in MIX_ENV=test because that is the environment the export came from:
# running in dev would compile a second build and score against a different set
# of modules. The guard is load-bearing for the same reason as `coverage`:
# ex_crap falls back to 0% coverage when the export is missing, which would
# report scores that are pessimistic instead of absent - a false failure is
# less bad than a false pass, but neither is acceptable in the Lens pass.
#
# Threshold is ex_crap's default (30), kept deliberately: decision 2026-10-28
# (human, yak "ratchets, deferred decisions, handoff practice") is not to
# ratchet --max-score down yet - below 30 is a policy change, not a cleanup.
# Baseline at that threshold, same date: 0 offenders above 30, worst functions
# Blogex.LinkValidator.validate_link/1 and validate_links/1 at 6.00
# ("Summary: files=12 functions=61 scored=61 worst_score=6.00"). A failing run
# prints "High scores: N" plus the offending functions; on a passing run read
# N=0 off the worst score rather than inventing a count.
crap:
@test -f cover/default.coverdata || { echo "crap: no exported coverage - run 'make test.coverage' first" >&2; exit 1; }
MIX_ENV=test $(MISE_EXEC) mix crap
# Format code. MUTATES the tree; deliberately not part of `check` (nor of the
# Lens pass), which must never write: a gate that fixes what it judges can never
# fail, and would dirty the tree a push is about to send. `make check` uses
# `format.check` below instead.
format:
$(MISE_EXEC) mix format
# Verify formatting without writing. Fails with the offending files, so the fix
# is explicit: run `make format` (or `make -C blogex format`).
format.check:
$(MISE_EXEC) mix format --check-formatted
# =============================================================================
# Lens pass (heavy checks; root `make verify` runs this before every push)
# =============================================================================
#
# Sensor slots, cheapest first. Add a sensor as its yak lands, both as a target
# and to VERIFY_SENSORS — that variable is the single source of truth for what
# this target reports as wired.
# - coverage (yak: coverage floors + framework-glue exclusions policy)
# WIRED below (test.coverage + coverage).
# - crap (yak: CRAP scores with ex_crap (both apps))
# WIRED below (crap); reuses the coverage export.
# - mutation (yak: mutation testing with muex — a sensor, not a gate)
#
# The suite runs FIRST: it is what produces the coverage export that the
# sensors below read. (Sampling and reporting are separate steps because
# `mix test --cover --export-coverage` exports instead of printing a summary,
# and reporting through `mix test.coverage` keeps it to one suite run.) Sensors
# still run cheapest-first among themselves.
VERIFY_SENSORS := coverage crap
verify:
@echo "==> verify(blogex): suite (coverage exported), then sensors [$(VERIFY_SENSORS)]"
@$(MAKE) test.coverage
@for sensor in $(VERIFY_SENSORS); do echo "==> verify(blogex): sensor $$sensor"; $(MAKE) $$sensor || exit 1; done