diff --git a/README-cs.md b/README-cs.md
index ece7d55..ef5f37f 100644
--- a/README-cs.md
+++ b/README-cs.md
@@ -157,10 +157,34 @@ Pozor na jedno omezení: **`renovate-config-validator` nevaliduje názvy
managerů.** `matchManagers: ["npm", "pnpm", "yarn"]` projde čistě a nematchuje
nic. Gate pokrývá schéma, ne význam.
-## Pod vlastním účtem
+### Co gate kontroluje
-Repozitář si zkopíruj, ale **neforkuj ho pro každou organizaci zvlášť.**
-Dědění je levnější:
+`validate.sh` pouští dvě věci a ta druhá je důležitější:
+
+- `renovate-config-validator --strict` — presety jsou správně zapsané
+- `tooling/test_policy.py` — presety **rozhodují** to, co mají
+
+Validátor kontroluje tvar, ne význam. Každá chyba, kterou tenhle preset vydal,
+jím prošla: neukotvený vzor dávající důvěru namespace, který nikdo nevlastní,
+pravidlo zařazené tak, že rušilo to nad sebou, dva neexistující názvy managerů.
+Policy test je má zmrazené jako případy a spadne, kdyby obnova lockfilu zase
+dostala automerge.
+
+Renovate matching reimplementuje, místo aby volal Renovate, takže se od
+skutečného enginu může rozejít. Selhání je důvod se podívat; průchod je slabší
+důkaz než dry run.
+
+## Tři způsoby, jak si to vzít, a co který stojí
+
+**Jen dědit.** Opravy k tobě dorazí ve chvíli, kdy vzniknou. Nic neudržuješ.
+
+```json
+{ "extends": ["github>ownctrl/supply-chain"] }
+```
+
+**Dědit a přebít.** Totéž plus vlastní doplňky. Tohle použij pro druhou značku
+nebo tým místo forku — jedna bezpečnostní oprava by se neměla dělat tolikrát,
+kolik máš kopií.
```json
{
@@ -169,7 +193,59 @@ Dědění je levnější:
}
```
-Fork znamená opravit každou bezpečnostní vadu tolikrát, kolik máš kopií.
+**Vzít si kopii.** Tenhle repozitář je GitHub template. Sáhni po něm, když
+politiku potřebuješ vlastnit — odstřižené prostředí, požadavek na compliance,
+nesouhlas s nějakým rozhodnutím tady.
+
+Ale ber to s cenovkou: **kopie přestane dostávat opravy.** V tomhle presetu
+se našly čtyři vady, na které by nikdo nepřišel čtením dokumentace, a kopie
+vzatá před kteroukoli z nich si ji nechala. U bezpečnostní politiky je tohle
+ten drahý směr.
+
+Když chceš vlastní adresu **i** opravy, ať tvoje kopie dědí odsud:
+
+```json
+{
+ "extends": ["github>ownctrl/supply-chain"],
+ "packageRules": [ /* jen tvoje odchylky */ ]
+}
+```
+
+Workflow `Setup Owner` po ručním spuštění přepíše reference a LICENSE na
+nového vlastníka.
+
+## Co to nedělá
+
+Preset rozhoduje, které aktualizace závislostí smějí přistát bez lidského
+pohledu. To je celý jeho rozsah a stojí za to říct natvrdo, kde končí:
+
+- **Hlídá aktualizace, ne to, co už máš.** Hostilní verze, která ti už leží
+ v lockfilu, je mimo jeho dosah. Zkontroluj lockfile přímo a rotuj všechno, na
+ co ten balíček mohl dosáhnout.
+- **Není to skener.** Neprohlíží obsah balíčků, neověřuje, že vydaný artefakt
+ odpovídá zdrojáku, ani nedetekuje kompromitaci. Kombinuj ho s něčím, co to
+ umí.
+- **Odstup není záruka.** `minimumReleaseAge` kupuje čas, aby si někdo jiný
+ všiml špatného vydání. Nemusí si všimnout nikdo. Shai-Hulud zůstal
+ u některých balíčků nepovšimnutý déle než sedm dní.
+- **Watch list je snímek.** 428 balíčků známých z jednoho útoku v listopadu
+ 2025. O tom příštím neříká nic.
+- **Policy test reimplementuje Renovate matching**, místo aby volal Renovate,
+ takže se od skutečného enginu může rozejít.
+
+Poskytováno tak, jak je, bez záruky, pod [licencí MIT](./LICENSE). Rozhodnutí,
+co smí tvůj projekt sloučit bez dozoru, je tvoje; tenhle preset je výchozí bod
+s odůvodněním, ne náhrada za to rozhodnutí.
+
+## Workflow `Setup Owner`
+
+`.github/workflows/setup-owner.yml` je tu pro cestu s kopií. Po ručním
+spuštění přepíše reference presetu a copyright v LICENSE na nového vlastníka
+a pak sám sebe smaže.
+
+Běží s `contents: write` a pushuje na výchozí větev, což k té práci potřebuje.
+Je jen `workflow_dispatch` — nic ho nespouští automaticky. Když preset dědíš
+místo kopírování, nikdy ho nepustíš.
## Verzování
diff --git a/README.md b/README.md
index 3132e0f..758c607 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-[](https://opensource.org/licenses/MIT)
+[](https://opensource.org/licenses/MIT)
[](https://renovatebot.com/)

@@ -160,11 +160,17 @@ Pin a release if you do not want your policy to change under you:
{ "extends": ["github>ownctrl/supply-chain#v1.0.0"] }
```
-### Using it under your own account
+### Three ways to adopt this, and what each costs
-Copy this repo, then reference your own copy. Do **not** fork it per
-organisation — inherit instead, so one security fix does not have to be applied
-once per copy:
+**Just extend it.** Fixes reach you the moment they land. Nothing to maintain.
+
+```json
+{ "extends": ["github>ownctrl/supply-chain"] }
+```
+
+**Extend it and override.** Same, plus your own additions. Use this for a
+second brand or team rather than forking — one security fix should not have to
+be applied once per copy.
```json
{
@@ -173,8 +179,26 @@ once per copy:
}
```
-The `Setup Owner` workflow rewrites the examples and LICENSE to the new owner
-when you dispatch it manually.
+**Take a copy.** This repo is a GitHub template. Use it when you need to own
+the policy outright — an air-gapped mirror, a compliance requirement, a
+disagreement with a decision here.
+
+Be clear about the price: **a copy stops receiving fixes.** Four faults were
+found in this preset that no amount of reading the documentation would have
+surfaced, and a copy taken before each one kept it. For a security policy that
+is the expensive direction.
+
+If you want your own address *and* the fixes, have your copy extend upstream:
+
+```json
+{
+ "extends": ["github>ownctrl/supply-chain"],
+ "packageRules": [ /* your deviations, and only those */ ]
+}
+```
+
+The `Setup Owner` workflow rewrites the preset references and LICENSE to the
+new owner when you dispatch it manually.
### JavaScript runtimes and package managers
@@ -246,6 +270,23 @@ ln -s ../../tooling/validate.sh .git/hooks/pre-push
A local hook is a convenience, not a boundary — anyone can skip it. CI stays the
real gate.
+### What the gate checks
+
+`validate.sh` runs two things, and the second matters more:
+
+- `renovate-config-validator --strict` — the presets are well formed
+- `tooling/test_policy.py` — the presets *decide* what they should
+
+The validator checks shape, not meaning. Every fault this preset has shipped
+passed it: an unanchored pattern granting trust to a namespace nobody owns, a
+rule ordered so it undid the one above it, two manager names that do not exist.
+The policy test freezes those as cases, and fails if lockfile maintenance ever
+gets automerge back.
+
+It reimplements Renovate's matching rather than calling Renovate, so it can
+drift from the real engine. A failure is a reason to look; a pass is weaker
+evidence than a dry run.
+
## Testing locally
You can test this config locally before deploying:
@@ -309,6 +350,40 @@ The full list lives in [`default.json`](./default.json). Affected version ranges
---
+## What this does not do
+
+This preset decides which dependency updates may merge unattended. That is its
+whole scope, and it is worth being blunt about the edges:
+
+- **It gates updates, not what you already have.** A hostile version already in
+ your lockfile is outside its reach. Check the lockfile directly and rotate
+ anything the package could have read.
+- **It is not a scanner.** It does not inspect package contents, verify that a
+ published artifact matches its source, or detect compromise. Pair it with
+ something that does.
+- **A delay is not a guarantee.** `minimumReleaseAge` buys time for someone
+ else to notice a bad release. Nobody may notice. Shai-Hulud went unnoticed
+ for longer than seven days in some packages.
+- **The watch list is a snapshot.** 428 packages known compromised in one
+ attack, in November 2025. It says nothing about the next one.
+- **The policy test reimplements Renovate's matching** rather than calling
+ Renovate, so it can drift from the real engine.
+
+Provided as is, without warranty, under the [MIT licence](./LICENSE). Deciding
+what your project may merge unattended is your decision; this preset is a
+starting point with reasoning attached, not a substitute for making it.
+
+## The `Setup Owner` workflow
+
+`.github/workflows/setup-owner.yml` exists for the copy path. Dispatch it
+manually after taking a copy and it rewrites the preset references and the
+LICENSE copyright to whoever now owns the repo, then deletes itself.
+
+It runs with `contents: write` and pushes to the default branch, which is what
+it needs to do that job. It is `workflow_dispatch` only — nothing triggers it
+automatically. If you are extending this preset rather than copying it, you
+will never run it.
+
## Versioning
`main` is what an unpinned `github>ownctrl/supply-chain` resolves to, so every
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..c3f4faa
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,45 @@
+# Security policy
+
+## Reporting
+
+Report a problem with this preset through
+[private vulnerability reporting](https://github.com/ownctrl/supply-chain/security/advisories/new),
+not a public issue.
+
+Worth reporting:
+
+- a rule that lets something merge unattended when it should not
+- a matcher that is broader than it reads — an unanchored pattern over a
+ package prefix nobody owns is the fault this preset has shipped most often
+- a package on the watch list that should not be, or one missing that should
+- remediation text pointing somewhere a reader cannot follow
+
+## What this preset is and is not
+
+It decides **which dependency updates may land without a human looking at
+them.** That is all. It does not scan code, detect compromise, or verify that a
+published package matches its source.
+
+It cannot protect a repository that has already installed a hostile version. It
+gates *updates*, so a compromised package already in your lockfile is outside
+its reach — check the lockfile directly and rotate anything the package could
+have read.
+
+## Known limitations
+
+- `renovate-config-validator` does not validate manager names. A typo'd manager
+ silently disables the rule containing it. `tooling/test_policy.py` checks the
+ names this preset uses against a hardcoded list, which needs updating when
+ Renovate adds a manager.
+- `tooling/test_policy.py` reimplements Renovate's matching rather than calling
+ Renovate. It can drift from the real engine. A failure is a reason to look; a
+ pass is weaker evidence than a Renovate dry run.
+- `minimumReleaseAge` does not apply to `pin`, `lockFileMaintenance`,
+ `lockfileUpdate`, `rollback`, `bump` or `replacement`. This preset disables
+ automerge for lockfile maintenance because of it.
+- Renovate has a hosted app on GitHub.com only. On every other forge you run it
+ yourself, and its token can write to all of your repositories.
+
+## Supported versions
+
+The latest tag. Older tags do not get fixes — move the pin.
diff --git a/aggressive.json b/aggressive.json
index 996643b..1b16612 100644
--- a/aggressive.json
+++ b/aggressive.json
@@ -1,20 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "Low-friction: updates any time, no release-age floor, higher PR limit. Not for use during an active supply-chain incident.",
- "extends": [
- "local>ownctrl/supply-chain"
- ],
- "schedule": [
- "at any time"
- ],
+ "extends": ["local>ownctrl/supply-chain"],
+ "schedule": ["at any time"],
"prConcurrentLimit": 10,
"minimumReleaseAge": null,
"packageRules": [
{
"description": "Top-level minimumReleaseAge cannot lift the inherited npm rule -- a packageRule outranks it, so drop the floor here too",
- "matchDatasources": [
- "npm"
- ],
+ "matchDatasources": ["npm"],
"minimumReleaseAge": null
}
]
diff --git a/default.json b/default.json
index 735a572..ee07335 100644
--- a/default.json
+++ b/default.json
@@ -140,7 +140,14 @@
},
{
"description": "Python: group together",
- "matchManagers": ["pip_requirements", "pip-compile", "poetry", "pep621", "pipenv", "setup-cfg"],
+ "matchManagers": [
+ "pip_requirements",
+ "pip-compile",
+ "poetry",
+ "pep621",
+ "pipenv",
+ "setup-cfg"
+ ],
"groupName": "Python",
"separateMinorPatch": false
},
@@ -182,7 +189,15 @@
},
{
"description": "Kubernetes: group together",
- "matchManagers": ["kubernetes", "helmv3", "helm-values", "helm-requirements", "helmfile", "flux", "argocd"],
+ "matchManagers": [
+ "kubernetes",
+ "helmv3",
+ "helm-values",
+ "helm-requirements",
+ "helmfile",
+ "flux",
+ "argocd"
+ ],
"groupName": "Kubernetes",
"separateMinorPatch": false
},
diff --git a/lockdown.json b/lockdown.json
index d7c235d..d30d96f 100644
--- a/lockdown.json
+++ b/lockdown.json
@@ -1,37 +1,26 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "🔒 Lockdown: no automerge anywhere, 14-day floor, every update waits for dashboard approval. For use during an active supply-chain incident.",
- "extends": [
- "local>ownctrl/supply-chain"
- ],
- "schedule": [
- "before 06:00 on monday"
- ],
+ "extends": ["local>ownctrl/supply-chain"],
+ "schedule": ["before 06:00 on monday"],
"minimumReleaseAge": "14 days",
"prConcurrentLimit": 2,
"dependencyDashboardApproval": true,
"packageRules": [
{
"description": "LOCKDOWN: npm floor to 14 days -- must be a packageRule, the top-level value is outranked by the inherited npm rule",
- "matchDatasources": [
- "npm"
- ],
+ "matchDatasources": ["npm"],
"minimumReleaseAge": "14 days"
},
{
"description": "LOCKDOWN: Disable ALL automerge",
"automerge": false,
"dependencyDashboardApproval": true,
- "matchPackageNames": [
- "*"
- ]
+ "matchPackageNames": ["*"]
},
{
"description": "LOCKDOWN: Production deps require manual approval",
- "matchDepTypes": [
- "dependencies",
- "peerDependencies"
- ],
+ "matchDepTypes": ["dependencies", "peerDependencies"],
"prPriority": -50,
"prBodyNotes": [
"🔒 **LOCKDOWN MODE ACTIVE**",
@@ -47,9 +36,7 @@
},
{
"description": "LOCKDOWN: Block all major updates",
- "matchUpdateTypes": [
- "major"
- ],
+ "matchUpdateTypes": ["major"],
"enabled": false
}
]
diff --git a/no-automerge.json b/no-automerge.json
index 4e1a055..365d7de 100644
--- a/no-automerge.json
+++ b/no-automerge.json
@@ -1,15 +1,11 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": "Disable automerge entirely, keeping every other policy from the base preset.",
- "extends": [
- "local>ownctrl/supply-chain"
- ],
+ "extends": ["local>ownctrl/supply-chain"],
"packageRules": [
{
"automerge": false,
- "matchPackageNames": [
- "*"
- ]
+ "matchPackageNames": ["*"]
}
]
}
diff --git a/renovate.json b/renovate.json
index 0a27d99..34842e5 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,18 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
- "description": "This repo eats its own dog food: it consumes the preset it publishes. Without this, the action digests and the pinned renovate version in .github/workflows/ would rot, and a stale pin is worse than a floating tag.",
- "extends": [
- "local>ownctrl/supply-chain"
- ],
+ "description": "This repo eats its own dog food: it consumes the preset it publishes. Without this, the action digests in .github/workflows/ and the tool versions pinned in tooling/validate.sh would rot, and a stale pin is worse than a floating tag.",
+ "extends": ["local>ownctrl/supply-chain"],
"customManagers": [
{
- "description": "Keep the validator pin in tooling/validate.sh updated",
+ "description": "Keep the pinned tool versions in tooling/validate.sh updated",
"customType": "regex",
- "managerFilePatterns": [
- "/^tooling/validate\\.sh$/"
- ],
+ "managerFilePatterns": ["/^tooling/validate\\.sh$/"],
"matchStrings": [
- "# renovate: datasource=(?\\S+) depName=(?\\S+)\\s+RENOVATE_VERSION=\"\\$\\{RENOVATE_VERSION:-(?[^}\"]+)\\}\""
+ "# renovate: datasource=(?\\S+) depName=(?\\S+)\\s+[A-Z_]+_VERSION=\"\\$\\{[A-Z_]+:-(?[^}\"]+)\\}\""
]
}
]
diff --git a/tooling/test_policy.py b/tooling/test_policy.py
new file mode 100755
index 0000000..6ac976f
--- /dev/null
+++ b/tooling/test_policy.py
@@ -0,0 +1,165 @@
+#!/usr/bin/env python3
+"""Assert what this preset lets merge unattended.
+
+`renovate-config-validator` checks that default.json is well formed. It does
+not check what the rules *decide* -- it will happily accept a rule that grants
+automerge to every package on npm, or one whose matcher never fires. Every
+fault this preset has shipped was of that kind:
+
+ * `^jest` unanchored matched every `jest-*` package on npm, a prefix nobody
+ owns, and those automerged with the trust meant for Jest.
+ * Per-tool rules sat after the anchored trust list and re-granted automerge
+ through looser patterns, undoing the anchoring above them.
+ * The GitHub Actions rule had no matchUpdateTypes, so it overrode
+ "major: automerge false" and merged bare digest moves unattended.
+ * `matchManagers: ["npm", "pnpm", "yarn"]` named two managers that do not
+ exist, so a rule that looked like it covered three ecosystems covered one.
+
+The validator passed all four. The cases below are those bugs, frozen.
+
+LIMITATION, and it is not a small one: this reimplements Renovate's matching
+rather than calling Renovate. It can drift from the real engine. Treat a
+failure here as a reason to look, and a pass as weaker evidence than a Renovate
+dry run. It exists because the alternative is checking nothing.
+"""
+
+import fnmatch
+import json
+import re
+import sys
+from pathlib import Path
+
+CONFIG = Path(__file__).resolve().parent.parent / "default.json"
+
+
+def _matches_name(pattern: str, name: str) -> bool:
+ """Renovate matchPackageNames: exact, glob, or /regex/."""
+ if pattern == "*":
+ return True
+ if len(pattern) > 1 and pattern.startswith("/") and pattern.endswith("/"):
+ return re.search(pattern[1:-1], name) is not None
+ if any(c in pattern for c in "*?["):
+ return fnmatch.fnmatch(name, pattern)
+ return pattern == name
+
+
+def _rule_applies(rule, *, name, dep_type, manager, update_type, datasource):
+ for key, value in (
+ ("matchManagers", manager),
+ ("matchDepTypes", dep_type),
+ ("matchUpdateTypes", update_type),
+ ("matchDatasources", datasource),
+ ):
+ if key in rule and value not in rule[key]:
+ return False
+ if "matchPackageNames" in rule:
+ if not any(_matches_name(p, name) for p in rule["matchPackageNames"]):
+ return False
+ return True
+
+
+def resolve(rules, key, **ctx):
+ """Last matching rule wins, which is how Renovate layers packageRules."""
+ value, source = None, "unset"
+ for rule in rules:
+ if _rule_applies(rule, **ctx) and key in rule:
+ value, source = rule[key], rule.get("description", "")
+ return value, source
+
+
+# name, depType, manager, updateType, datasource, automerge expected
+CASES = [
+ # Trusted dev tooling merges unattended. That is the whole allowance.
+ ("jest", "devDependencies", "npm", "patch", "npm", True),
+ ("vitest", "devDependencies", "npm", "patch", "npm", True),
+ ("oxlint", "devDependencies", "npm", "patch", "npm", True),
+ ("typescript", "devDependencies", "npm", "patch", "npm", True),
+ ("@biomejs/biome", "devDependencies", "npm", "patch", "npm", True),
+ ("@types/node", "devDependencies", "npm", "patch", "npm", True),
+ # Neighbours in unowned npm prefixes must NOT inherit that trust.
+ ("jest-environment-foo", "devDependencies", "npm", "patch", "npm", False),
+ ("vitest-fetch-mock", "devDependencies", "npm", "patch", "npm", False),
+ ("oxlint-plugin-evil", "devDependencies", "npm", "patch", "npm", False),
+ ("typescript-eslint-hijack", "devDependencies", "npm", "patch", "npm", False),
+ # Production dependencies never merge unattended, whatever they are called.
+ ("typescript", "dependencies", "npm", "patch", "npm", False),
+ ("vitest", "dependencies", "npm", "patch", "npm", False),
+ ("@biomejs/biome", "dependencies", "npm", "patch", "npm", False),
+ ("express", "dependencies", "npm", "patch", "npm", False),
+ ("serde", "dependencies", "cargo", "patch", "crate", False),
+ ("django", "dependencies", "poetry", "patch", "pypi", False),
+ # Watch-listed packages stay gated even as devDependencies.
+ ("posthog-node", "dependencies", "npm", "patch", "npm", False),
+ ("tinycolor2", "devDependencies", "npm", "patch", "npm", False),
+ # CI actions: minor/patch only. A bare digest move on an unchanged tag is
+ # the tj-actions/changed-files vector and must wait for a human.
+ ("actions/checkout", None, "github-actions", "patch", "github-tags", True),
+ ("actions/checkout", None, "github-actions", "minor", "github-tags", True),
+ ("actions/checkout", None, "github-actions", "digest", "github-tags", False),
+ ("actions/checkout", None, "github-actions", "major", "github-tags", False),
+]
+
+
+def main() -> int:
+ config = json.loads(CONFIG.read_text())
+ rules = config["packageRules"]
+ failures = []
+
+ for name, dep_type, manager, update_type, datasource, expected in CASES:
+ got, source = resolve(
+ rules,
+ "automerge",
+ name=name,
+ dep_type=dep_type,
+ manager=manager,
+ update_type=update_type,
+ datasource=datasource,
+ )
+ if bool(got) is not expected:
+ failures.append(
+ f" {name} ({dep_type or manager}, {update_type}): "
+ f"expected automerge={expected}, got {bool(got)} "
+ f"from rule {source!r}"
+ )
+
+ # Lockfile refreshes are not covered by minimumReleaseAge, so automerging
+ # one would pull every transitive dependency in unreviewed and ungated.
+ if config["lockFileMaintenance"].get("automerge"):
+ failures.append(
+ " lockFileMaintenance.automerge is true: minimumReleaseAge does "
+ "not apply to lockfile refreshes, so this merges every transitive "
+ "update with no age gate and no review"
+ )
+
+ # Renovate has no pnpm or yarn manager; the npm manager reads all three
+ # lockfiles. A rule naming them looks broader than it is, and the schema
+ # validator does not catch it.
+ known = {
+ "npm", "bun", "bun-version", "deno", "cargo", "gomod", "docker",
+ "github-actions", "nix", "terraform", "ansible", "nuget", "composer",
+ "bundler", "pub", "swift", "cocoapods", "kubernetes", "helmv3",
+ "helm-values", "helm-requirements", "helmfile", "flux", "argocd",
+ "gradle", "gradle-wrapper", "maven", "maven-wrapper", "sbt",
+ "pip_requirements", "pip-compile", "poetry", "pep621", "pipenv",
+ "setup-cfg",
+ }
+ for rule in rules:
+ for manager in rule.get("matchManagers", []):
+ if manager not in known:
+ failures.append(
+ f" unknown manager {manager!r} in rule "
+ f"{rule.get('description', '')!r} -- it matches nothing. "
+ f"If Renovate added it, add it to `known` here."
+ )
+
+ if failures:
+ print(f"policy: {len(failures)} failure(s)\n")
+ print("\n".join(failures))
+ return 1
+
+ print(f"policy: {len(CASES)} cases, lockfile gate and manager names all OK")
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tooling/validate.sh b/tooling/validate.sh
index a569c38..bca45a1 100755
--- a/tooling/validate.sh
+++ b/tooling/validate.sh
@@ -13,6 +13,8 @@ set -euo pipefail
# renovate: datasource=npm depName=renovate
RENOVATE_VERSION="${RENOVATE_VERSION:-44.39.1}"
+# renovate: datasource=npm depName=@biomejs/biome
+BIOME_VERSION="${BIOME_VERSION:-2.5.9}"
cd "$(dirname "$0")/.."
@@ -31,3 +33,12 @@ fi
echo "validating ${#presets[@]} presets against renovate@${RENOVATE_VERSION}"
npx --yes --package "renovate@${RENOVATE_VERSION}" -- \
renovate-config-validator --strict "${presets[@]}"
+
+# The validator checks that the rules are well formed, not what they decide.
+# Every fault this preset has shipped passed it.
+python3 tooling/test_policy.py
+
+# Formatting. Pinned to the version biome.json declares -- biome rejects a
+# schema from a different release, so the two move together or not at all.
+echo "checking formatting with @biomejs/biome@${BIOME_VERSION}"
+npx --yes --package "@biomejs/biome@${BIOME_VERSION}" -- biome check .