CI
mdvs check exits with code 1 when any file violates the schema, so it slots
straight into a CI pipeline as a frontmatter linter. This page covers the GitHub
Actions case, but the same shape works on GitLab CI, CircleCI, or any runner
that can install a binary and run a command.
Minimal GitHub Actions workflow
# .github/workflows/check-frontmatter.yml
name: Frontmatter check
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mdvs
env:
MDVS_VERSION: vX.Y.Z # pin to a specific release — see below
run: |
curl --proto '=https' --tlsv1.2 -LsSf \
"https://github.com/edochi/mdvs/releases/download/${MDVS_VERSION}/mdvs-installer.sh" | sh
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
- name: Validate frontmatter
run: mdvs check --no-update
Replace vX.Y.Z with a real release tag (see the
releases page). This adds a check
that runs on every PR and every push to main. If a contributor introduces a
file with a wrong type, missing required field, disallowed field, or
unrepresentable frontmatter, the job fails and the PR is blocked until it’s
fixed.
Pin the mdvs version
The installer URL above pulls a specific release tag. GitHub also exposes a
releases/latest/download/... URL that always redirects to the newest release —
convenient for casual use, and that’s what the
README install snippet uses — but in
CI you want reproducibility. Pinning a specific tag means a green check
today still passes (or still fails the same way) tomorrow, regardless of what
mdvs ships in the meantime.
Bump the pinned version when you’re ready to adopt new validation behavior. The mdvs release notes call out anything that affects validation output.
--no-update for deterministic CI
The --no-update flag (or [check].auto_update = false in mdvs.toml) tells
check to validate against the committed schema instead of re-running inference
first. This matters in CI:
- With auto-update on:
checkre-infers the schema before validating and rewritesmdvs.tomlon disk, absorbing any new frontmatter field. On a runner that rewrite is thrown away with the checkout, but the run validated against a schema that differs from the committed one, and the new field is never mentioned. - With
--no-update:mdvs.tomlis left untouched and the run validates against exactly what’s committed. A new field is reported under a New fields heading in the output.
Re-inference only ever adds fields — it does not widen an existing field’s
type or relax its constraints. A value that breaks a declared categories list,
or a field with the wrong type, fails in both modes.
In practice: in CI, always use --no-update. You validate against the
committed schema, nothing is rewritten mid-run, and additions are visible in the
log.
check does not fail on undeclared fields
Worth being explicit, because it is easy to assume otherwise: a frontmatter
field that appears in no [[fields.field]] entry is not a violation, in
either mode. --no-update surfaces it as informational output and the command
still exits 0.
Checked 2 files — no violations, 1 new field(s)
Validation iterates the fields declared in mdvs.toml, so a key it has never
seen is reported, not rejected. Disallowed means something narrower: a
declared field appearing at a path outside its allowed globs.
If you want new fields to break the build today, gate on the JSON output — it
carries a new_fields array:
mdvs check --no-update --output json | jq -e '.new_fields | length == 0'
jq -e exits non-zero when the expression is false, so the step fails as soon
as an undeclared field shows up. Run it in addition to
mdvs check --no-update, which still owns the real violations. A schema-level
“freeze this directory” option does not exist yet.
Caching the install
The installer step downloads a small binary (~6 MB on Linux) and finishes in
well under a second. There’s usually no point caching it. If you want to avoid
the network call entirely on every run, use actions/cache keyed on the mdvs
version string, or commit a vendored binary into the repo and skip the install
step.
What check does (and doesn’t)
mdvs check covers frontmatter validation only:
- ✓ Wrong types (a
Booleanfield with a string value) - ✓ Missing required fields per directory
- ✓ Disallowed fields (a declared field appearing outside its
allowedpaths) - ✓ Null violations
- ✓ Category, length, range, and regex constraint violations
- ✓ Frontmatter that can’t be parsed at all (broken YAML, broken TOML, broken JSON)
It does not flag a field that appears in no [[fields.field]] entry — see
above. It also does not check
spelling, link validity, markdown style, or anything in the body content. Pair
it with a markdown linter (markdownlint, vale) for those concerns. They run
independently and have no conflict — mdvs check and a body-content linter
cover orthogonal parts of the file.
Other CI systems
The shape translates directly:
- GitLab CI: the same two-step install-then-run pattern in
.gitlab-ci.yml. Use the install script underbefore_script:and runmdvs check --no-updatein the job. - CircleCI: an
orbor a custom step that installs the binary and invokes the check. - Pre-commit hook:
mdvs check --no-updateas a hook entry in.pre-commit-config.yamlruns the check locally on every commit, catching issues before they reach CI. See the dedicated pre-commit recipe for both the framework and plain-git-hook setups.
The contract is always the same: install mdvs, run mdvs check --no-update,
fail on non-zero exit.