diff --git a/.codegen.json b/.codegen.json index ee2037e0ee8..0866ebe7ae9 100644 --- a/.codegen.json +++ b/.codegen.json @@ -8,6 +8,13 @@ "python/uv.lock": "name = \"databricks-bundles\"\nversion = \"$VERSION\"", "libs/template/templates/default/library/versions.tmpl": "{{define \"latest_databricks_bundles_version\" -}}$VERSION{{- end}}" }, + "nextchanges_sections": { + "notable-changes": "Notable Changes", + "cli": "CLI", + "bundles": "Bundles", + "dependency-updates": "Dependency Updates", + "api-changes": "API Changes" + }, "toolchain": { "required": [ "go" diff --git a/.nextchanges/api-changes/.gitkeep b/.nextchanges/api-changes/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/.nextchanges/api-changes/README.md b/.nextchanges/api-changes/README.md new file mode 100644 index 00000000000..2a775835e62 --- /dev/null +++ b/.nextchanges/api-changes/README.md @@ -0,0 +1,6 @@ +# API Changes + +Fragments here render under **API Changes** in the released changelog. Add a +`.md` file describing your change. + +See [`../README.md`](../README.md) for how fragments are written and released. diff --git a/.nextchanges/bundles/.gitkeep b/.nextchanges/bundles/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/.nextchanges/bundles/README.md b/.nextchanges/bundles/README.md new file mode 100644 index 00000000000..f275abbcfbf --- /dev/null +++ b/.nextchanges/bundles/README.md @@ -0,0 +1,6 @@ +# Bundles + +Fragments here render under **Bundles** in the released changelog. Add a +`.md` file describing your change. + +See [`../README.md`](../README.md) for how fragments are written and released. diff --git a/.nextchanges/cli/.gitkeep b/.nextchanges/cli/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/.nextchanges/cli/README.md b/.nextchanges/cli/README.md new file mode 100644 index 00000000000..02c2b99c89f --- /dev/null +++ b/.nextchanges/cli/README.md @@ -0,0 +1,6 @@ +# CLI + +Fragments here render under **CLI** in the released changelog. Add a `.md` +file describing your change. + +See [`../README.md`](../README.md) for how fragments are written and released. diff --git a/.nextchanges/dependency-updates/.gitkeep b/.nextchanges/dependency-updates/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/.nextchanges/dependency-updates/README.md b/.nextchanges/dependency-updates/README.md new file mode 100644 index 00000000000..04d255fd19b --- /dev/null +++ b/.nextchanges/dependency-updates/README.md @@ -0,0 +1,6 @@ +# Dependency Updates + +Fragments here render under **Dependency Updates** in the released changelog. Add +a `.md` file describing your change. + +See [`../README.md`](../README.md) for how fragments are written and released. diff --git a/.nextchanges/notable-changes/.gitkeep b/.nextchanges/notable-changes/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/.nextchanges/notable-changes/README.md b/.nextchanges/notable-changes/README.md new file mode 100644 index 00000000000..73008ba92d8 --- /dev/null +++ b/.nextchanges/notable-changes/README.md @@ -0,0 +1,6 @@ +# Notable Changes + +Fragments here render under **Notable Changes** — prominent entries called out at +the top of the released changelog. Add a `.md` file describing your change. + +See [`../README.md`](../README.md) for how fragments are written and released. diff --git a/tools/validate_nextchanges.py b/tools/validate_nextchanges.py index c89eb9b0083..300c764c41d 100755 --- a/tools/validate_nextchanges.py +++ b/tools/validate_nextchanges.py @@ -12,45 +12,67 @@ """ import argparse +import json import pathlib import re import sys CHANGELOG_DIR = ".nextchanges" - -# Known section subdirectories. Mirrors NEXTCHANGES_SECTIONS in -# internal/genkit/tagging.py — keep the two in sync. -SECTIONS = ("notable-changes", "cli", "bundles", "dependency-updates", "api-changes") +CODEGEN_FILE = ".codegen.json" +NEXTCHANGES_SECTIONS_KEY = "nextchanges_sections" # .nextchanges/version holds the next release version; the release reads it and # bumps it. Accept a bare semver (optionally v-prefixed), e.g. 1.4.0 / v1.4.0. VERSION_FILE = "version" SEMVER_RE = re.compile(r"^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$") -# Non-fragment files allowed to sit alongside fragments at any depth. -SCAFFOLDING = ("README.md", ".gitkeep") +# README.md is allowed both at the .nextchanges root (the docs) and inside each +# section directory: the release renderer skips it, so a committed README.md +# keeps otherwise-empty section directories present in git without being +# mistaken for a fragment. +README = "README.md" + + +def load_sections(root): + codegen_path = root / CODEGEN_FILE + try: + codegen = json.loads(codegen_path.read_text(encoding="utf-8")) + except FileNotFoundError as err: + raise ValueError(f"{CODEGEN_FILE} is missing") from err + except json.JSONDecodeError as err: + raise ValueError(f"{CODEGEN_FILE} is not valid JSON: {err}") from err + + sections = codegen.get(NEXTCHANGES_SECTIONS_KEY) + if not isinstance(sections, dict) or not sections: + raise ValueError(f"{CODEGEN_FILE} must define a non-empty {NEXTCHANGES_SECTIONS_KEY} object") + return tuple(sections) -def find_problems(changelog_dir): + +def find_problems(changelog_dir, sections): """Return a list of ``(path, message)`` for anything unexpected under ``.nextchanges/``: files that aren't a section fragment or known scaffolding, empty fragments, and a missing/malformed version file.""" problems = [] + known_sections = set(sections) for path in sorted(changelog_dir.rglob("*")): if path.is_dir(): continue rel = path.relative_to(changelog_dir) name = path.name - # Root-level: only the version file and scaffolding belong here. + # Root-level: only the version file and root documentation belong here. This prevents + # someone accidentally putting a .md into .nextchanges thinking it would be picked up. if len(rel.parts) == 1: - if name != VERSION_FILE and name not in SCAFFOLDING: + if name != VERSION_FILE and name != README: problems.append((path, "unexpected file at .nextchanges root")) continue # Section-level: .nextchanges/
/. - if len(rel.parts) == 2 and rel.parts[0] in SECTIONS: - if name in SCAFFOLDING: + if len(rel.parts) == 2 and rel.parts[0] in known_sections: + # README.md holds section docs and keeps the directory in git; the + # renderer skips it, so it is not treated as a fragment. + if name == README: continue if not name.endswith(".md"): problems.append((path, "unexpected file (fragments must be *.md)")) @@ -78,12 +100,18 @@ def main(argv=None): if not changelog_dir.is_dir(): return - problems = find_problems(changelog_dir) + try: + sections = load_sections(args.root) + except ValueError as err: + print(err, file=sys.stderr) + sys.exit(1) + + problems = find_problems(changelog_dir, sections) if problems: for path, msg in problems: print(f"{path}: {msg}", file=sys.stderr) print(f"\nFragments must live at {CHANGELOG_DIR}/
/.md", file=sys.stderr) - print(f"Valid sections: {', '.join(SECTIONS)}", file=sys.stderr) + print(f"Valid sections: {', '.join(sections)}", file=sys.stderr) print(f"{CHANGELOG_DIR}/{VERSION_FILE} must hold the next release version.", file=sys.stderr) sys.exit(1)