Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions docs/release-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,28 @@ detecteert het in deze volgorde (meest specifiek eerst):
2. `pyproject.toml` — Frappe- en Python-apps (statische `[project].version` of
een dynamische version via flit/hatch, uit `<module>/__init__.py`)
3. `package.json` — skill-packages, templates, JS-apps
4. `VERSION` — taal-agnostische fallback: een plain-text bestand op de repo-root
met precies een semver-regel (bijvoorbeeld `0.1.0`). Bedoeld voor repos zonder
taal-manifest, zoals docs-, template- en scaffold-repos, zodat ook die een
conventionele version-plek hebben. Laagste precedentie: hij wordt alleen
gelezen als geen van de drie manifesten hierboven bestaat.

Ontbreekt een manifest, of is de version niet leesbaar, dan faalt de workflow
luid. Er is geen stille fallback naar een ander manifest.
luid. Er is geen stille fallback naar een ander manifest. Let op: dit geldt ook
voor `VERSION`. Bestaat er wel een `package.json` maar zonder `version`-veld, dan
faalt de workflow luid op die `package.json`; hij valt niet stilletjes terug op
een `VERSION`-bestand. Elk repo heeft precies een version-bron.

## Adoptie in een repo

1. Kopieer `docs/release.yml.template` uit deze repo naar
`.github/workflows/release.yml` in je eigen repo.
2. Commit dat via de normale branch- en PR-flow (`GIT_PRACTICES.md`).
3. Zorg dat de repo een `CHANGELOG.md` in Keep a Changelog-formaat heeft, met
3. Zorg dat de repo een version-bron heeft op de voor z'n type conventionele
plek: een taal-manifest (`.claude-plugin/plugin.json`, `pyproject.toml` of
`package.json`), of, voor een repo zonder taal-manifest, een `VERSION`-bestand
op de repo-root met een enkele semver-regel (bijvoorbeeld `0.1.0`).
4. Zorg dat de repo een `CHANGELOG.md` in Keep a Changelog-formaat heeft, met
per version een `## [<version>]`-sectie. Een bump zonder bijbehorende
CHANGELOG-sectie laat de workflow luid falen.

Expand Down
30 changes: 29 additions & 1 deletion scripts/detect-release.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
1. .claude-plugin/plugin.json (Claude Code plugins)
2. pyproject.toml (Frappe / Python apps)
3. package.json (skill-packages / templates / JS apps)
4. VERSION (language-agnostic plain-text fallback:
docs / scaffolds / templates without any
language manifest; one semver line)

Notes are NOT required to be present here: on a genuine bump the caller workflow
enforces that a CHANGELOG section exists (notes_found=true). Keeping that policy
Expand Down Expand Up @@ -47,6 +50,27 @@ def read_json_version(path: Path) -> str:
return version.strip()


# A plain-text VERSION file must hold exactly one semver line. This is stricter
# than the JSON/TOML readers on purpose: a schemaless text file has no field to
# key on, so the semver shape is the only guard against a stray "VERSION" file
# (e.g. licensing text) being mistaken for a release manifest. Fail loud, no
# guessing.
_SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$")


def read_version_file(path: Path) -> str:
lines = [ln.strip() for ln in path.read_text(encoding="utf-8").splitlines()]
nonblank = [ln for ln in lines if ln]
if not nonblank:
die(f"{path} is empty; expected a single semver line (e.g. 0.1.0)")
if len(nonblank) > 1:
die(f"{path} has multiple non-blank lines; expected a single semver line")
version = nonblank[0]
if not _SEMVER_RE.match(version):
die(f"{path} content '{version}' is not a valid semver (e.g. 0.1.0)")
return version


def read_module_version(module_file: Path) -> str | None:
if not module_file.is_file():
return None
Expand Down Expand Up @@ -111,9 +135,13 @@ def detect(repo: Path) -> tuple[str, str]:
if package.is_file():
return "package.json", read_json_version(package)

version_file = repo / "VERSION"
if version_file.is_file():
return "VERSION", read_version_file(version_file)

die(
f"no release manifest found in '{repo}'. Expected one of: "
".claude-plugin/plugin.json, pyproject.toml, package.json"
".claude-plugin/plugin.json, pyproject.toml, package.json, VERSION"
)


Expand Down