From 437f0b5495f5dd88c2796ca90977ebed285a8d80 Mon Sep 17 00:00:00 2001 From: Saket Aryan Date: Wed, 10 Jun 2026 22:16:58 +0530 Subject: [PATCH] ci: route all release publishing through a single Release Router workflow (#5475) --- .github/workflows/cd.yml | 20 +++++-- .github/workflows/cli-node-cd.yml | 22 ++++++-- .github/workflows/cli-python-cd.yml | 19 +++++-- .github/workflows/openclaw-cd.yml | 22 ++++++-- .github/workflows/opencode-plugin-cd.yml | 22 ++++++-- .github/workflows/pi-agent-plugin-cd.yml | 22 ++++++-- .github/workflows/release.yml | 68 ++++++++++++++++++++++++ .github/workflows/ts-sdk-cd.yml | 21 ++++++-- .github/workflows/vercel-ai-cd.yml | 22 ++++++-- AGENTS.md | 6 +++ 10 files changed, 213 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index efb7c958b..c7346b52f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,20 +1,33 @@ name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI +# Dispatched by release.yml (Release Router) when a release tagged v* is +# published. Can also be dispatched manually to re-publish a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. v1.2.3)' + required: true + type: string + prerelease: + description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI # Pure SDK version tags only (v1.2.3) — excludes package-prefixed tags # like vercel-ai-v* that also start with 'v' - if: startsWith(github.event.release.tag_name, 'v') && !contains(github.event.release.tag_name, '-v') + if: startsWith(inputs.tag, 'v') && !contains(inputs.tag, '-v') runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v2 + with: + ref: ${{ inputs.tag }} - name: Set up Python uses: actions/setup-python@v2 @@ -41,7 +54,6 @@ jobs: # packages_dir: dist/ - name: Publish distribution 📦 to PyPI - if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-v') uses: pypa/gh-action-pypi-publish@release/v1 with: packages_dir: dist/ diff --git a/.github/workflows/cli-node-cd.yml b/.github/workflows/cli-node-cd.yml index c583abb52..35694b6ff 100644 --- a/.github/workflows/cli-node-cd.yml +++ b/.github/workflows/cli-node-cd.yml @@ -1,13 +1,25 @@ name: Publish @mem0/cli 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged +# cli-node-v* is published. Can also be dispatched manually to re-publish +# a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. cli-node-v0.2.0)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish @mem0/cli 📦 to npm - if: startsWith(github.event.release.tag_name, 'cli-node-v') + if: startsWith(inputs.tag, 'cli-node-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +28,8 @@ jobs: working-directory: cli/node steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -38,7 +52,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/.github/workflows/cli-python-cd.yml b/.github/workflows/cli-python-cd.yml index 316bc1df2..b0ef33144 100644 --- a/.github/workflows/cli-python-cd.yml +++ b/.github/workflows/cli-python-cd.yml @@ -1,13 +1,24 @@ name: Publish mem0-cli 🐍 distributions 📦 to PyPI +# Dispatched by release.yml (Release Router) when a release tagged cli-v* is +# published. Can also be dispatched manually to re-publish a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. cli-v0.2.0)' + required: true + type: string + prerelease: + description: 'Unused for PyPI (pre-releases are expressed in the version itself); accepted for router uniformity' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish mem0-cli 📦 to PyPI - if: startsWith(github.event.release.tag_name, 'cli-v') + if: startsWith(inputs.tag, 'cli-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +27,8 @@ jobs: working-directory: cli/python steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/openclaw-cd.yml b/.github/workflows/openclaw-cd.yml index 8a0d0e7e0..cc9aaf874 100644 --- a/.github/workflows/openclaw-cd.yml +++ b/.github/workflows/openclaw-cd.yml @@ -1,13 +1,25 @@ name: Publish @mem0/openclaw-mem0 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged +# openclaw-v* is published. Can also be dispatched manually to re-publish +# a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. openclaw-v0.5.0)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish @mem0/openclaw-mem0 📦 to npm - if: startsWith(github.event.release.tag_name, 'openclaw-v') + if: startsWith(inputs.tag, 'openclaw-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +28,8 @@ jobs: working-directory: openclaw steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -38,7 +52,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/.github/workflows/opencode-plugin-cd.yml b/.github/workflows/opencode-plugin-cd.yml index 746569aa8..dd1a293d9 100644 --- a/.github/workflows/opencode-plugin-cd.yml +++ b/.github/workflows/opencode-plugin-cd.yml @@ -1,13 +1,25 @@ name: Publish @mem0/opencode-plugin 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged +# opencode-v* is published. Can also be dispatched manually to re-publish +# a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. opencode-v0.2.0)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish @mem0/opencode-plugin 📦 to npm - if: startsWith(github.event.release.tag_name, 'opencode-v') + if: startsWith(inputs.tag, 'opencode-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +28,8 @@ jobs: working-directory: mem0-plugin/.opencode-plugin steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install Bun uses: oven-sh/setup-bun@v2 @@ -36,7 +50,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/.github/workflows/pi-agent-plugin-cd.yml b/.github/workflows/pi-agent-plugin-cd.yml index fb09e9684..f32bb7196 100644 --- a/.github/workflows/pi-agent-plugin-cd.yml +++ b/.github/workflows/pi-agent-plugin-cd.yml @@ -1,13 +1,25 @@ name: Publish @mem0/pi-agent-plugin 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged +# pi-agent-v* is published. Can also be dispatched manually to re-publish +# a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. pi-agent-v0.1.1)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish @mem0/pi-agent-plugin 📦 to npm - if: startsWith(github.event.release.tag_name, 'pi-agent-v') + if: startsWith(inputs.tag, 'pi-agent-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +28,8 @@ jobs: working-directory: pi-agent-plugin steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -38,7 +52,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..5be797b7b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Release Router 🚦 + +# Single entry point for all release publishing. +# +# Package CD workflows no longer listen to release events themselves — this +# router inspects the release tag and dispatches only the matching pipeline, +# so each release produces one routed run instead of one real run plus seven +# skipped ones. +# +# Re-publishing a release (e.g. after fixing registry settings) does NOT +# require deleting and recreating it anymore — manually dispatch the +# package's CD workflow from the tag instead: +# +# gh workflow run -cd.yml --ref refs/tags/ -f tag= +# +# Note: dispatching runs the workflow file as it exists at the given ref, so +# this router can only dispatch tags created after the workflow_dispatch +# conversion landed on main. For older tags, dispatch manually from main. + +on: + release: + types: [published] + +permissions: + actions: write + +jobs: + route: + name: Route ${{ github.event.release.tag_name }} to its CD pipeline + runs-on: ubuntu-latest + steps: + - name: Match tag prefix to CD workflow + id: match + env: + TAG: ${{ github.event.release.tag_name }} + run: | + # Specific package prefixes first; the bare v* (Python SDK) arm + # must stay last so prefixed tags that also start with 'v' + # (vercel-ai-v*) can never be routed to the Python pipeline. + case "$TAG" in + ts-v*) workflow="ts-sdk-cd.yml" ;; + cli-node-v*) workflow="cli-node-cd.yml" ;; + cli-v*) workflow="cli-python-cd.yml" ;; + vercel-ai-v*) workflow="vercel-ai-cd.yml" ;; + openclaw-v*) workflow="openclaw-cd.yml" ;; + opencode-v*) workflow="opencode-plugin-cd.yml" ;; + pi-agent-v*) workflow="pi-agent-plugin-cd.yml" ;; + v*) workflow="cd.yml" ;; + *) + echo "::error::Release tag '$TAG' does not match any known package prefix — nothing will be published. See the tag prefix table in AGENTS.md." + exit 1 + ;; + esac + echo "workflow=$workflow" >> "$GITHUB_OUTPUT" + echo ":outbox_tray: Routed \`$TAG\` → \`$workflow\`" >> "$GITHUB_STEP_SUMMARY" + + - name: Dispatch ${{ steps.match.outputs.workflow }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ github.event.release.tag_name }} + run: | + # --ref points at the tag so the dispatched run builds (and signs + # provenance for) the exact tagged commit. + gh workflow run "${{ steps.match.outputs.workflow }}" \ + --repo "$GITHUB_REPOSITORY" \ + --ref "refs/tags/$TAG" \ + -f tag="$TAG" \ + -f prerelease="${{ github.event.release.prerelease }}" diff --git a/.github/workflows/ts-sdk-cd.yml b/.github/workflows/ts-sdk-cd.yml index 8b7202ee9..07218d3db 100644 --- a/.github/workflows/ts-sdk-cd.yml +++ b/.github/workflows/ts-sdk-cd.yml @@ -1,13 +1,24 @@ name: Publish mem0ai 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged ts-v* is +# published. Can also be dispatched manually to re-publish a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. ts-v2.1.0)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish mem0ai 📦 to npm - if: startsWith(github.event.release.tag_name, 'ts-v') + if: startsWith(inputs.tag, 'ts-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +27,8 @@ jobs: working-directory: mem0-ts steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -38,7 +51,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/.github/workflows/vercel-ai-cd.yml b/.github/workflows/vercel-ai-cd.yml index 172afc288..18dd4b31d 100644 --- a/.github/workflows/vercel-ai-cd.yml +++ b/.github/workflows/vercel-ai-cd.yml @@ -1,13 +1,25 @@ name: Publish @mem0/vercel-ai-provider 📦 to npm +# Dispatched by release.yml (Release Router) when a release tagged +# vercel-ai-v* is published. Can also be dispatched manually to re-publish +# a tag. on: - release: - types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build and publish (e.g. vercel-ai-v2.0.7)' + required: true + type: string + prerelease: + description: 'Publish under the version preid dist-tag instead of latest' + required: false + type: boolean + default: false jobs: build-n-publish: name: Build and publish @mem0/vercel-ai-provider 📦 to npm - if: startsWith(github.event.release.tag_name, 'vercel-ai-v') + if: startsWith(inputs.tag, 'vercel-ai-v') runs-on: ubuntu-latest permissions: id-token: write @@ -16,6 +28,8 @@ jobs: working-directory: vercel-ai-sdk steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Install pnpm uses: pnpm/action-setup@v4 @@ -38,7 +52,7 @@ jobs: - name: Publish to npm run: | - if [ "${{ github.event.release.prerelease }}" = "true" ]; then + if [ "${{ inputs.prerelease }}" = "true" ]; then PREID=$(node -p "require('./package.json').version.split('-')[1].split('.')[0]") npx npm@latest publish --provenance --access public --tag "$PREID" else diff --git a/AGENTS.md b/AGENTS.md index 4d4c25978..40efa7e41 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -418,8 +418,11 @@ To add a new LLM, embedding, vector store, or reranker provider: ### CD Workflows (automated publishing) +Publishing is routed through a single entry point: **`release.yml` (Release Router)** is the only workflow that listens to `release: published` events. It matches the release tag prefix and dispatches the corresponding package workflow via `workflow_dispatch`, so each release produces exactly one routed run (no skipped runs from the other pipelines). + | Workflow | File | Tag Prefix | Target | |----------|------|------------|--------| +| Release Router | `release.yml` | (all releases) | dispatches the matching workflow below | | Python SDK | `cd.yml` | `v*` | PyPI (`mem0ai`) | | TypeScript SDK | `ts-sdk-cd.yml` | `ts-v*` | npm (`mem0ai`) | | Python CLI | `cli-python-cd.yml` | `cli-v*` | PyPI (`mem0-cli`) | @@ -429,8 +432,11 @@ To add a new LLM, embedding, vector store, or reranker provider: | OpenCode Plugin | `opencode-plugin-cd.yml` | `opencode-v*` | npm (`@mem0/opencode-plugin`) | | Pi Agent Plugin | `pi-agent-plugin-cd.yml` | `pi-agent-v*` | npm (`@mem0/pi-agent-plugin`) | +- Package CD workflows are `workflow_dispatch`-only (inputs: `tag`, `prerelease`); they check out and build the given tag. Registry trusted-publisher settings stay pinned to each package's own workflow filename. - All publishing uses **OIDC trusted publishing** — no tokens or secrets required. - First publish of a new npm package must be done manually; OIDC works for subsequent versions. +- To re-publish a release (e.g. after a registry settings fix), do **not** delete/recreate the GitHub release — manually dispatch the package workflow instead: `gh workflow run -cd.yml --ref refs/tags/ -f tag=`. +- When adding a new package: add its CD workflow (`workflow_dispatch` with `tag`/`prerelease` inputs), then register its tag prefix in the `case` block in `release.yml`. Keep the bare `v*` arm last. ### Utility Workflows