From e71fbd632c8172da92064055b631319e0f3260ec Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Thu, 22 Oct 2026 09:01:22 -0400 Subject: [PATCH 1/2] ci: release by a version-bump PR, like the other packs release-please could not open its release PR: the org does not allow GitHub Actions to create pull requests, so Release PR failed on every push to main. The other packs release by merging a PR that bumps the chart's version, which release.yaml already handles. Drop release-pr.yaml, its config and manifest, and the x-release-please-version markers. bump_version.py sets every version string (chart version and appVersion, the API and MCP __version__, ui/package.json and its lockfile) in one command, and the version check now compares them with the chart's version. ci-cd.md, the README, AGENTS.md and the CHANGELOG header describe the release PR. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM --- .github/scripts/bump_version.py | 49 +++++++++++++++++++++++++++++ .github/scripts/check_versions.py | 8 ++--- .github/workflows/ci.yaml | 4 +-- .github/workflows/release-pr.yaml | 42 ------------------------- .release-please-manifest.json | 3 -- AGENTS.md | 7 +++-- CHANGELOG.md | 4 +-- README.md | 5 +-- api/pyproject.toml | 2 +- api/src/nebari_apps_api/__init__.py | 2 +- charts/nebari-apps/Chart.yaml | 4 +-- docs/src/content/docs/ci-cd.md | 18 ++++++----- mcp/pyproject.toml | 2 +- mcp/src/nebari_apps_mcp/__init__.py | 2 +- release-please-config.json | 34 -------------------- ui/vite.config.ts | 2 +- 16 files changed, 82 insertions(+), 106 deletions(-) create mode 100644 .github/scripts/bump_version.py delete mode 100644 .github/workflows/release-pr.yaml delete mode 100644 .release-please-manifest.json delete mode 100644 release-please-config.json diff --git a/.github/scripts/bump_version.py b/.github/scripts/bump_version.py new file mode 100644 index 0000000..769c093 --- /dev/null +++ b/.github/scripts/bump_version.py @@ -0,0 +1,49 @@ +"""Set every version string to the given release version. + +The chart's `version` and `appVersion`, the API and MCP `__version__`, and +`ui/package.json` with its lockfile all carry the pack's release version; +the "Version strings" CI check fails when they disagree. Merging the change +to `charts/nebari-apps/Chart.yaml` is the release (`release.yaml`). + + python3 .github/scripts/bump_version.py 0.2.0 +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SEMVER = re.compile(r"^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$") + +# (file, pattern, count): each pattern's group 1 and 2 surround the version. +EDITS = [ + ("charts/nebari-apps/Chart.yaml", r'^(version:\s*)[^\s#]+()', 1), + ("charts/nebari-apps/Chart.yaml", r'^(appVersion:\s*")[^"]+(")', 1), + ("api/src/nebari_apps_api/__init__.py", r'^(__version__ = ")[^"]+(")', 1), + ("mcp/src/nebari_apps_mcp/__init__.py", r'^(__version__ = ")[^"]+(")', 1), + ("ui/package.json", r'^( "version": ")[^"]+(")', 1), + # The lockfile's top-level version and packages[""].version. + ("ui/package-lock.json", r'^( (?: )?"version": ")[^"]+(")', 2), +] + + +def main() -> int: + if len(sys.argv) != 2 or not SEMVER.match(sys.argv[1]): + print("usage: bump_version.py X.Y.Z", file=sys.stderr) + return 2 + version = sys.argv[1] + for path, pattern, count in EDITS: + file = ROOT / path + text, n = re.subn(pattern, rf"\g<1>{version}\g<2>", file.read_text(), count=count, flags=re.M) + if n != count: + print(f"::error::{path}: expected {count} version string(s), found {n}", file=sys.stderr) + return 1 + file.write_text(text) + print(f"set every version string to {version}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/scripts/check_versions.py b/.github/scripts/check_versions.py index d846285..380a5c7 100644 --- a/.github/scripts/check_versions.py +++ b/.github/scripts/check_versions.py @@ -1,6 +1,6 @@ -"""Fail when a version string disagrees with .release-please-manifest.json. +"""Fail when a version string disagrees with the chart's `version`. -release-please bumps all of these together; a hand edit to one of them +`bump_version.py` sets all of these together; a hand edit to one of them would ship mismatched versions. """ @@ -20,7 +20,7 @@ def grep(path: str, pattern: str) -> str | None: def main() -> int: - want = json.loads((ROOT / ".release-please-manifest.json").read_text())["."] + want = grep("charts/nebari-apps/Chart.yaml", r"^version:\s*\"?([^\"\s#]+)") lock = json.loads((ROOT / "ui/package-lock.json").read_text()) found = { "Chart.yaml version": grep("charts/nebari-apps/Chart.yaml", r"^version:\s*\"?([^\"\s#]+)"), @@ -35,7 +35,7 @@ def main() -> int: for k, v in found.items(): print(("ok " if v == want else "FAIL ") + f"{k}: {v}") if bad: - print(f"::error::version strings must all be {want} (the release-please manifest)") + print(f"::error::version strings must all be {want} (the chart's version; set them with bump_version.py)") return 1 return 0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 13be985..c2a9fee 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -429,9 +429,9 @@ jobs: - name: Paths named in AGENTS.md and the skill exist run: python3 .github/scripts/check_doc_paths.py - # release-please bumps all of these together; a hand edit to one of them + # bump_version.py sets all of these together; a hand edit to one of them # would ship mismatched versions. - - name: Every version string matches .release-please-manifest.json + - name: Every version string matches the chart's version run: python3 .github/scripts/check_versions.py # One version per runtime: the pin file CI reads must match the image diff --git a/.github/workflows/release-pr.yaml b/.github/workflows/release-pr.yaml deleted file mode 100644 index fa2bc2c..0000000 --- a/.github/workflows/release-pr.yaml +++ /dev/null @@ -1,42 +0,0 @@ -name: Release PR - -# Keeps one open "chore: release X.Y.Z" pull request that bumps every version -# string (Chart.yaml version and appVersion, the api and mcp __version__, -# ui/package.json and its lockfile) and prepends the release's CHANGELOG.md -# section, built from the conventional-commit titles merged since the last -# release. Merging it is the release: the Chart.yaml bump runs release.yaml. -# -# release-please never creates the tag or GitHub Release itself -# (skip-github-release): pack-release does, as nebari-apps-, and the -# tag format below matches it so release-please finds the previous release. -# Labels are skipped too, so a merged release PR is never left "pending". - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false - -jobs: - release-pr: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 - with: - # A PR opened with the default GITHUB_TOKEN does not trigger CI; set - # RELEASE_PLEASE_TOKEN (a bot token with contents + pull-requests - # write) so the release PR runs its checks without a manual reopen. - token: ${{ secrets.RELEASE_PLEASE_TOKEN || github.token }} - config-file: release-please-config.json - manifest-file: .release-please-manifest.json - skip-github-release: true - skip-labeling: true diff --git a/.release-please-manifest.json b/.release-please-manifest.json deleted file mode 100644 index 466df71..0000000 --- a/.release-please-manifest.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.1.0" -} diff --git a/AGENTS.md b/AGENTS.md index 29d20b5..cbbc049 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -103,9 +103,10 @@ helm lint charts/nebari-apps --set clusterDomain=nebari.example.com normalized reference it checked. apps-api (`images.py`) mirrors the parsing and matching rules for early 422s; change both together, with the shared test cases. Image pods keep the untrusted-pod rules below and never relax `runAsNonRoot` for a root image. -- **Version strings are owned by release-please.** Never hand-edit the version in - `charts/nebari-apps/Chart.yaml`, the `__version__` files, or `ui/package.json`; the - "Version strings" check compares them with `.release-please-manifest.json`. +- **Version strings move together.** Change the version in `charts/nebari-apps/Chart.yaml`, + the `__version__` files and `ui/package.json` only with + `python3 .github/scripts/bump_version.py X.Y.Z`, in a release PR; the "Version strings" + check compares them with the chart's `version`. - **Runtime versions are pinned once:** `ui/.node-version`, `api/.python-version`, `mcp/.python-version`, `docs/.bun-version`, and the `toolchain` line in `operator/go.mod` (`authz/go.mod` carries the same line). diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b00166..8720d71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog -Maintained by [release-please](https://github.com/googleapis/release-please) from the -conventional-commit titles of merged pull requests. See [Releases](README.md#releases). +Each release PR adds its section, written from the Conventional Commit titles of the pull +requests merged since the last release. See [Releases](README.md#releases). ## 0.1.0 (2026-08-26) diff --git a/README.md b/README.md index 8a22537..6072df9 100644 --- a/README.md +++ b/README.md @@ -124,8 +124,9 @@ The full list is in [Getting started](https://packs.nebari.dev/apps-pack/getting ## Releases -[release-please](https://github.com/googleapis/release-please) keeps a `chore: release X.Y.Z` -pull request open; merging it is the release. The chart is published to +A release is a pull request that bumps the version +(`python3 .github/scripts/bump_version.py X.Y.Z`) and adds its `CHANGELOG.md` section; merging +it is the release, as in the other Nebari packs. The chart is published to `oci://ghcr.io/nebari-dev/apps-pack/charts` and to the [org Helm repository](https://github.com/nebari-dev/helm-repository), and the chart and images are signed with cosign diff --git a/api/pyproject.toml b/api/pyproject.toml index 12836c3..fba9e7c 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -28,7 +28,7 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" -# The version lives in __init__.py (release-please bumps it), so uv.lock +# The version lives in __init__.py (bump_version.py sets it), so uv.lock # does not record it and a release needs no relock. [tool.hatch.version] path = "src/nebari_apps_api/__init__.py" diff --git a/api/src/nebari_apps_api/__init__.py b/api/src/nebari_apps_api/__init__.py index 9b2e131..6278ce7 100644 --- a/api/src/nebari_apps_api/__init__.py +++ b/api/src/nebari_apps_api/__init__.py @@ -1,3 +1,3 @@ """nebari-apps-api - REST API for the Nebari Apps Pack.""" -__version__ = "0.1.0" # x-release-please-version +__version__ = "0.1.0" diff --git a/charts/nebari-apps/Chart.yaml b/charts/nebari-apps/Chart.yaml index 87071c5..8ab4e77 100644 --- a/charts/nebari-apps/Chart.yaml +++ b/charts/nebari-apps/Chart.yaml @@ -6,8 +6,8 @@ description: >- apps-operator that reconciles Apps into Deployments, Services, and NebariApps. type: application -version: 0.1.0 # x-release-please-version -appVersion: "0.1.0" # x-release-please-version +version: 0.1.0 +appVersion: "0.1.0" home: https://github.com/nebari-dev/apps-pack sources: - https://github.com/nebari-dev/apps-pack diff --git a/docs/src/content/docs/ci-cd.md b/docs/src/content/docs/ci-cd.md index b90d69f..b897a30 100644 --- a/docs/src/content/docs/ci-cd.md +++ b/docs/src/content/docs/ci-cd.md @@ -44,15 +44,19 @@ The signer identity is the org workflow at the commit this repository pins ## Cutting a release +Releases work as in the other Nebari packs: a pull request bumps the chart's version, and +merging it is the release. + 1. **Merge pull requests with Conventional Commit titles.** They are squash-merged, so the title is the commit, and it decides the next version: `feat:` bumps the minor version - while the pack is below 1.0, `fix:` the patch version. -2. **release-please keeps a release PR open** (`release-pr.yaml`), titled - `chore: release X.Y.Z`. It bumps every version string together (the chart's `version` - and `appVersion`, the API and MCP `__version__`, `ui/package.json` and its lockfile) and - adds the release's section to `CHANGELOG.md`. Without a `RELEASE_PLEASE_TOKEN` secret the - PR is opened with `GITHUB_TOKEN`, which does not start CI; close and reopen it to run the - checks. + while the pack is below 1.0, `fix:` the patch version (a breaking change, `!`, also bumps + the minor version below 1.0). +2. **Open a release PR** titled `chore: release X.Y.Z`. Set every version string with + `python3 .github/scripts/bump_version.py X.Y.Z` (the chart's `version` and `appVersion`, + the API and MCP `__version__`, `ui/package.json` and its lockfile; CI fails if they + disagree), and add the release's section to `CHANGELOG.md` from the feature, fix and + breaking-change titles merged since the last release + (`git log --oneline nebari-apps-..main`). 3. **Merging the release PR is the release.** The `Chart.yaml` version bump runs `release.yaml` (the org's `pack-release` workflow), which: - pins the chart's five image tags to `sha-`, built by the same push, diff --git a/mcp/pyproject.toml b/mcp/pyproject.toml index 6f4cc2f..abb1bc4 100644 --- a/mcp/pyproject.toml +++ b/mcp/pyproject.toml @@ -31,7 +31,7 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" -# The version lives in __init__.py (release-please bumps it), so uv.lock +# The version lives in __init__.py (bump_version.py sets it), so uv.lock # does not record it and a release needs no relock. [tool.hatch.version] path = "src/nebari_apps_mcp/__init__.py" diff --git a/mcp/src/nebari_apps_mcp/__init__.py b/mcp/src/nebari_apps_mcp/__init__.py index e32898e..70969fa 100644 --- a/mcp/src/nebari_apps_mcp/__init__.py +++ b/mcp/src/nebari_apps_mcp/__init__.py @@ -1,3 +1,3 @@ """nebari-apps-mcp - agent-facing MCP server for the Nebari Apps Pack.""" -__version__ = "0.1.0" # x-release-please-version +__version__ = "0.1.0" diff --git a/release-please-config.json b/release-please-config.json deleted file mode 100644 index 93ffdf3..0000000 --- a/release-please-config.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", - "release-type": "simple", - "bootstrap-sha": "45856542a75d2ac69dab2cbe72dc64a6dadb130d", - "bump-minor-pre-major": true, - "include-component-in-tag": true, - "include-v-in-tag": false, - "pull-request-title-pattern": "chore: release ${version}", - "changelog-sections": [ - { "type": "feat", "section": "Features" }, - { "type": "fix", "section": "Bug fixes" }, - { "type": "perf", "section": "Performance" }, - { "type": "revert", "section": "Reverts" }, - { "type": "docs", "section": "Documentation" }, - { "type": "deps", "section": "Dependencies" }, - { "type": "ci", "section": "CI", "hidden": true }, - { "type": "chore", "section": "Miscellaneous", "hidden": true }, - { "type": "test", "section": "Tests", "hidden": true } - ], - "packages": { - ".": { - "component": "nebari-apps", - "package-name": "nebari-apps", - "extra-files": [ - "charts/nebari-apps/Chart.yaml", - "api/src/nebari_apps_api/__init__.py", - "mcp/src/nebari_apps_mcp/__init__.py", - { "type": "json", "path": "ui/package.json", "jsonpath": "$.version" }, - { "type": "json", "path": "ui/package-lock.json", "jsonpath": "$.version" }, - { "type": "json", "path": "ui/package-lock.json", "jsonpath": "$.packages[''].version" } - ] - } - } -} diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 3b8f5e7..ef3e9aa 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -6,7 +6,7 @@ import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; -// The UI's own release, bumped by release-please, baked into the bundle (the +// The UI's own release, set by bump_version.py, baked into the bundle (the // Settings page shows it next to the API's). const { version } = JSON.parse(readFileSync(path.resolve(__dirname, "package.json"), "utf8")); From 19df1d7784dcc81ca739dfe5faa9a50ef0bc634b Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Thu, 22 Oct 2026 09:02:43 -0400 Subject: [PATCH 2/2] docs(ci-cd): the version check compares with the chart's version Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ap1Tqx1QLHRGVVSXdxeduM --- docs/src/content/docs/ci-cd.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/content/docs/ci-cd.md b/docs/src/content/docs/ci-cd.md index b897a30..8ec3ac6 100644 --- a/docs/src/content/docs/ci-cd.md +++ b/docs/src/content/docs/ci-cd.md @@ -12,7 +12,7 @@ description: What CI checks on every pull request, how images and charts are bui | | UI | Biome, the production build, the bundle budget, vitest and its coverage thresholds, `npm audit --audit-level=high` | | | UI e2e + a11y | Playwright on Chromium against the UI job's build (apps-api mocked): a user flow or keyboard path that breaks, or a `critical`/`serious` axe violation (WCAG 2.0/2.1 A and AA) on any page in light or dark, at desktop width and at 390px (where a page that scrolls sideways also fails); uploads the HTML report every run | | | Helm chart | `helm lint`/`helm template` errors, `values.schema.json`, a stale chart README, chart RBAC assertions, the alert rules (promtool), kubeconform on the examples and rendered chart | -| | Repo invariants | a path in `AGENTS.md` or the skill that does not exist, version strings that disagree with the release manifest, a pin file that disagrees with its Dockerfile, actionlint, zizmor | +| | Repo invariants | a path in `AGENTS.md` or the skill that does not exist, version strings that disagree with the chart's `version`, a pin file that disagrees with its Dockerfile, actionlint, zizmor | | **Images** (`images.yaml`) | one per image | the image does not build, or (`ui`) serves the wrong security headers; on changes to the images' sources only | | **Docs** (`docs.yml`) | docs | the site's tests or build, or a broken external link (on docs changes only) |