From 7dbb50a57b476ab529e007e4aaef1f2d80c4b5c6 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 16 Feb 2026 21:18:24 -0800 Subject: [PATCH 1/2] Eliminate double towncrier run in release workflow Add build_quartz Dagger function that builds the Quartz site without running towncrier, and reorder the workflow so towncrier runs once on the runner before passing the updated source tree to Dagger. Co-Authored-By: Claude Opus 4.6 --- .dagger/src/blumeops_ci/main.py | 7 +++- .forgejo/workflows/build-blumeops.yaml | 35 +++++++++---------- .../eliminate-double-towncrier.infra.md | 1 + 3 files changed, 23 insertions(+), 20 deletions(-) create mode 100644 docs/changelog.d/eliminate-double-towncrier.infra.md diff --git a/.dagger/src/blumeops_ci/main.py b/.dagger/src/blumeops_ci/main.py index 8f2373f..b98af9e 100644 --- a/.dagger/src/blumeops_ci/main.py +++ b/.dagger/src/blumeops_ci/main.py @@ -47,12 +47,17 @@ class BlumeopsCi: async def build_docs(self, src: dagger.Directory, version: str) -> dagger.File: """Build changelog then Quartz site. Returns docs tarball.""" updated_src = await self.build_changelog(src, version) + return await self.build_quartz(updated_src, version) + + @function + async def build_quartz(self, src: dagger.Directory, version: str) -> dagger.File: + """Build Quartz docs site from pre-processed source. Returns docs tarball.""" return await ( dag.container() .from_("node:22-slim") .with_exec(["apt-get", "update", "-qq"]) .with_exec(["apt-get", "install", "-y", "-qq", "git"]) - .with_directory("/workspace", updated_src) + .with_directory("/workspace", src) .with_workdir("/workspace") .with_exec( [ diff --git a/.forgejo/workflows/build-blumeops.yaml b/.forgejo/workflows/build-blumeops.yaml index dca745a..e6930bd 100644 --- a/.forgejo/workflows/build-blumeops.yaml +++ b/.forgejo/workflows/build-blumeops.yaml @@ -108,30 +108,14 @@ jobs: with: fetch-depth: 0 - - name: Build docs - run: | - VERSION="${{ steps.version.outputs.version }}" - TARBALL="docs-${VERSION}.tar.gz" - echo "Building docs via Dagger..." - # build-docs calls build_changelog internally (towncrier runs inside - # the Dagger container). The host working tree is not modified — only - # the tarball is exported. Towncrier runs a second time on the runner - # in the next step so that CHANGELOG.md and fragment deletion are - # captured in the git commit. - dagger call build-docs --src=. --version="$VERSION" \ - export --path="./$TARBALL" - echo "Build complete!" - ls -lh "$TARBALL" - - name: Build changelog id: changelog run: | VERSION="${{ steps.version.outputs.version }}" - # Run towncrier on the runner (not in Dagger) so that CHANGELOG.md - # updates and fragment deletions appear in the working tree for the - # git commit step. This is intentionally a second towncrier run — - # the first happened inside the Dagger build-docs container above. + # Run towncrier on the runner so that CHANGELOG.md updates and + # fragment deletions appear in the working tree for both the Quartz + # build (next step) and the git commit step. # Check if there are any changelog fragments FRAGMENTS=$(find docs/changelog.d -name "*.md" -not -name ".gitkeep" 2>/dev/null | wc -l) @@ -157,6 +141,19 @@ jobs: echo "" > /tmp/release_notes.md fi + - name: Build docs + run: | + VERSION="${{ steps.version.outputs.version }}" + TARBALL="docs-${VERSION}.tar.gz" + echo "Building docs via Dagger..." + # Towncrier already ran on the runner above, so the working tree + # has an up-to-date CHANGELOG.md. build-quartz skips towncrier + # and only runs the Quartz static site build. + dagger call build-quartz --src=. --version="$VERSION" \ + export --path="./$TARBALL" + echo "Build complete!" + ls -lh "$TARBALL" + - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/changelog.d/eliminate-double-towncrier.infra.md b/docs/changelog.d/eliminate-double-towncrier.infra.md new file mode 100644 index 0000000..3c6524d --- /dev/null +++ b/docs/changelog.d/eliminate-double-towncrier.infra.md @@ -0,0 +1 @@ +Eliminate double towncrier run in release workflow — changelog is now built once on the runner, then the pre-processed source tree is passed to a new `build_quartz` Dagger function for the Quartz site build only. -- 2.50.1 (Apple Git-155) From 5af97e6a7e9126bc006ce7a15b43c1f4ed37003c Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 16 Feb 2026 21:23:02 -0800 Subject: [PATCH 2/2] Drop build_changelog, simplify build_docs to Quartz-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build_changelog is no longer needed — towncrier now runs on the runner before Dagger. build_docs becomes the direct Quartz build (no towncrier delegation). Update docs to match. Co-Authored-By: Claude Opus 4.6 --- .dagger/src/blumeops_ci/main.py | 28 +------------------------- .forgejo/workflows/build-blumeops.yaml | 6 +++--- docs/how-to/update-documentation.md | 2 +- docs/reference/tools/dagger.md | 3 +-- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/.dagger/src/blumeops_ci/main.py b/.dagger/src/blumeops_ci/main.py index b98af9e..5cd70e4 100644 --- a/.dagger/src/blumeops_ci/main.py +++ b/.dagger/src/blumeops_ci/main.py @@ -23,35 +23,9 @@ class BlumeopsCi: ref = f"{registry}/blumeops/{container_name}:{version}" return await ctr.publish(ref) - @function - async def build_changelog( - self, src: dagger.Directory, version: str - ) -> dagger.Directory: - """Run towncrier to build changelog, return modified source tree.""" - return await ( - dag.container() - .from_("python:3.12-slim") - .with_env_variable("TZ", "America/Los_Angeles") - # git is required because towncrier stages CHANGELOG.md via git add - .with_exec(["apt-get", "update", "-qq"]) - .with_exec(["apt-get", "install", "-y", "-qq", "git"]) - .with_exec(["pip", "install", "towncrier"]) - .with_directory("/workspace", src) - .with_workdir("/workspace") - .with_exec(["git", "init"]) - .with_exec(["towncrier", "build", "--version", version, "--yes"]) - .directory("/workspace") - ) - @function async def build_docs(self, src: dagger.Directory, version: str) -> dagger.File: - """Build changelog then Quartz site. Returns docs tarball.""" - updated_src = await self.build_changelog(src, version) - return await self.build_quartz(updated_src, version) - - @function - async def build_quartz(self, src: dagger.Directory, version: str) -> dagger.File: - """Build Quartz docs site from pre-processed source. Returns docs tarball.""" + """Build Quartz docs site. Returns docs tarball.""" return await ( dag.container() .from_("node:22-slim") diff --git a/.forgejo/workflows/build-blumeops.yaml b/.forgejo/workflows/build-blumeops.yaml index e6930bd..e771033 100644 --- a/.forgejo/workflows/build-blumeops.yaml +++ b/.forgejo/workflows/build-blumeops.yaml @@ -147,9 +147,9 @@ jobs: TARBALL="docs-${VERSION}.tar.gz" echo "Building docs via Dagger..." # Towncrier already ran on the runner above, so the working tree - # has an up-to-date CHANGELOG.md. build-quartz skips towncrier - # and only runs the Quartz static site build. - dagger call build-quartz --src=. --version="$VERSION" \ + # has an up-to-date CHANGELOG.md. build-docs now only runs the + # Quartz static site build (no towncrier). + dagger call build-docs --src=. --version="$VERSION" \ export --path="./$TARBALL" echo "Build complete!" ls -lh "$TARBALL" diff --git a/docs/how-to/update-documentation.md b/docs/how-to/update-documentation.md index e9991e6..4df624c 100644 --- a/docs/how-to/update-documentation.md +++ b/docs/how-to/update-documentation.md @@ -26,7 +26,7 @@ Direct link: https://forge.ops.eblu.me/eblume/blumeops/actions?workflow=build-bl The `build-blumeops` workflow (`.forgejo/workflows/build-blumeops.yaml`): 1. **Resolves version** — Uses input or auto-increments from latest release -2. **Builds changelog** — Calls `dagger call build-changelog` (towncrier in a container) +2. **Builds changelog** — Runs towncrier on the runner to update `CHANGELOG.md` 3. **Builds docs** — Calls `dagger call build-docs` (Quartz build in a container) 4. **Creates release** — Uploads `docs-.tar.gz` to Forgejo releases 5. **Updates deployment** — Edits `argocd/manifests/docs/deployment.yaml` with new URL diff --git a/docs/reference/tools/dagger.md b/docs/reference/tools/dagger.md index c78a706..a793d9b 100644 --- a/docs/reference/tools/dagger.md +++ b/docs/reference/tools/dagger.md @@ -27,8 +27,7 @@ Build engine for BlumeOps CI/CD pipelines. Replaces shell-based build scripts wi |----------|-----------|-------------| | `build` | `(src, container_name) → Container` | Build a container from `containers//Dockerfile` | | `publish` | `(src, container_name, version, registry?) → str` | Build and push to registry (default: `registry.ops.eblu.me`) | -| `build_changelog` | `(src, version) → Directory` | Run towncrier to collect changelog fragments | -| `build_docs` | `(src, version) → File` | Build changelog then Quartz site, return docs tarball | +| `build_docs` | `(src, version) → File` | Build Quartz docs site, return docs tarball | ## CLI Examples -- 2.50.1 (Apple Git-155)