Skip to content
Merged
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
71 changes: 71 additions & 0 deletions .github/workflows/promote-stable-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Advances docs-stable when SuperDoc itself ships a stable release.
#
# Scope is intentionally narrow: only release-superdoc.yml. The CLI/SDK/MCP
# bundle and the React/esign/template-builder/vscode-ext wrappers do not
# advance docs-stable, even when they release successfully — docs-stable
# represents the documentation for the stable SuperDoc release, not every
# stable package release.
#
# A successful release-superdoc.yml run is not enough on its own: the run
# could have been a semantic-release no-op (no qualifying commits). Check
# that a v* tag actually appeared between the run's head_sha and
# origin/stable before pushing.
name: 🚀 Promote stable docs

on:
workflow_run:
workflows:
- "📦 Release superdoc"
types:
- completed

permissions:
contents: write

concurrency:
group: promote-stable-docs
cancel-in-progress: false

jobs:
promote:
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'stable'
runs-on: ubuntu-24.04
steps:
- name: Generate token
id: generate_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.generate_token.outputs.token }}

- name: Detect SuperDoc release
id: detect
env:
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
git fetch origin stable --tags --force

# v* tags reachable from origin/stable that were NOT reachable from
# the run's head_sha. If empty, the release-superdoc.yml run was a
# semantic-release no-op and docs-stable must not advance.
tags_at_stable=$(git tag --merged origin/stable --list 'v[0-9]*' | sort -u)
tags_at_head=$(git tag --merged "${HEAD_SHA}" --list 'v[0-9]*' | sort -u)
new_tags=$(comm -23 <(echo "${tags_at_stable}") <(echo "${tags_at_head}"))

if [ -z "${new_tags}" ]; then
echo "released=false" >> "${GITHUB_OUTPUT}"
echo "No new v* tag between ${HEAD_SHA} and origin/stable — release-superdoc was a no-op."
else
echo "released=true" >> "${GITHUB_OUTPUT}"
echo "New SuperDoc tag(s) detected: $(echo "${new_tags}" | tr '\n' ' ')"
fi

- name: Push docs-stable
if: steps.detect.outputs.released == 'true'
run: git push origin "refs/remotes/origin/stable:refs/heads/docs-stable"
7 changes: 5 additions & 2 deletions .github/workflows/release-esign.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml.
# Auto-releases on push to main (@next) and stable (@latest).
name: 📦 Release esign

on:
push:
branches:
- main
- stable
paths:
- 'packages/esign/**'
- 'pnpm-workspace.yaml'
Expand All @@ -17,6 +17,9 @@ permissions:
packages: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`.
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ github.ref_name != 'stable' }}

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-react.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml.
# Auto-releases on push to main (@next) and stable (@latest).
name: 📦 Release react

on:
push:
branches:
- main
- stable
paths:
# React declares `superdoc` in dependencies (not peerDependencies), so
# existing consumers with lockfiles won't pick up a new core version
Expand All @@ -27,6 +27,9 @@ permissions:
packages: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`.
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ github.ref_name != 'stable' }}

Expand Down
90 changes: 26 additions & 64 deletions .github/workflows/release-stable.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Sequential stable release orchestrator.
# This is the only workflow that auto-releases on push to stable.
name: 📦 Release stable
# Stable tooling mini-bundle: CLI -> SDK -> MCP, in order, on the same runner.
# These three share artifacts (SDK packages CLI native binaries; MCP imports
# SDK + engine code), so they release together with one queue slot and one
# permission context (including PyPI OIDC).
#
# Out of scope: superdoc, react, esign, template-builder, vscode-ext. Each of
# those has its own per-package stable workflow.
name: 📦 Release stable tooling (CLI/SDK/MCP)

on:
push:
Expand All @@ -14,8 +19,14 @@ permissions:
id-token: write

concurrency:
# Keep [skip ci] writeback runs out of the shared stable queue so they cannot
# replace a real pending stable push while the orchestrator is active.
# All stable release workflows (this bundle + superdoc + react/esign/
# template-builder/vscode-ext) share the `release-stable` group so that
# @semantic-release/git pushes to `stable` serialize. Per-workflow groups
# would let multiple workflows publish in parallel and race on
# `git push origin stable`, leaving npm/PyPI tarballs published without
# a corresponding tag/commit pushed.
# [skip ci] writeback runs use a per-run group so they cannot evict a real
# pending stable push while the bundle is active.
group: ${{ github.event_name == 'push' && contains(github.event.head_commit.message, '[skip ci]') && format('release-stable-skip-{0}', github.run_id) || 'release-stable' }}
cancel-in-progress: false

Expand Down Expand Up @@ -92,59 +103,20 @@ jobs:
- name: Build packages
run: pnpm run build

- name: Test vscode-ext
run: pnpm --prefix apps/vscode-ext run test

- name: Snapshot SDK tags before release
id: sdk_tags_before
run: echo "tags=$(git tag --list 'sdk-v*' | sort | tr '\n' ',')" >> "$GITHUB_OUTPUT"

- name: Release stable packages sequentially
- name: Release stable tooling (CLI -> SDK -> MCP)
id: stable_release
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
LINEAR_TOKEN: ${{ secrets.LINEAR_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
VSCE_PAT: ${{ secrets.VSCE_PAT }}
GITHUB_REF_NAME: stable
run: node scripts/release-local-stable.mjs

- name: Detect SDK release tag at HEAD
id: sdk_release
run: |
BEFORE="${{ steps.sdk_tags_before.outputs.tags }}"
AFTER=$(git tag --list 'sdk-v*' | sort | tr '\n' ',')
RELEASE_TAG=$(git tag --points-at HEAD --list 'sdk-v*' --sort=-version:refname | head -n 1)
if [ -z "$RELEASE_TAG" ]; then
echo "release_present=false" >> "$GITHUB_OUTPUT"
echo "released=false" >> "$GITHUB_OUTPUT"
echo "version=" >> "$GITHUB_OUTPUT"
echo "dist_tag=" >> "$GITHUB_OUTPUT"
echo "No SDK release tag at HEAD."
else
echo "release_present=true" >> "$GITHUB_OUTPUT"
if [ "$BEFORE" = "$AFTER" ]; then
echo "released=false" >> "$GITHUB_OUTPUT"
else
echo "released=true" >> "$GITHUB_OUTPUT"
fi
VERSION="${RELEASE_TAG#sdk-v}"
if [[ "$VERSION" == *-next.* ]]; then
DIST_TAG="next"
else
DIST_TAG="latest"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "dist_tag=$DIST_TAG" >> "$GITHUB_OUTPUT"
if [ "$BEFORE" = "$AFTER" ]; then
echo "SDK release tag already present at HEAD: $RELEASE_TAG"
else
echo "Released SDK v$VERSION"
fi
fi

# SDK release coordinates come from the orchestrator's step outputs, not
# `git tag --points-at HEAD`. After CLI -> SDK -> MCP, HEAD lands on
# MCP's version commit and the SDK tag is no longer at HEAD.
- name: Publish recovered SDK companion Python packages to PyPI
if: steps.stable_release.outputs.sdk_python_snapshot_companion_dir != ''
uses: pypa/gh-action-pypi-publish@release/v1
Expand All @@ -160,32 +132,22 @@ jobs:
skip-existing: true

- name: Build and verify Python SDK
if: steps.sdk_release.outputs.release_present == 'true'
if: steps.stable_release.outputs.sdk_release_present == 'true'
run: node packages/sdk/scripts/build-python-sdk.mjs

- name: Publish companion Python packages to PyPI
if: steps.sdk_release.outputs.release_present == 'true'
if: steps.stable_release.outputs.sdk_release_present == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/sdk/langs/python/companion-dist/
skip-existing: true

- name: Publish main Python SDK to PyPI
if: steps.sdk_release.outputs.release_present == 'true'
if: steps.stable_release.outputs.sdk_release_present == 'true'
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: packages/sdk/langs/python/dist/
skip-existing: true

# Stable docs deploy from `docs-stable`. The orchestrator emits
# `promote_sha` only when the run released (or no-op'd) on the SHA it
# checked out; it stays empty on failure or deferral, so production docs
# never advance past a commit whose packages were not actually published.
- name: Promote stable docs
if: steps.stable_release.outputs.promote_sha != ''
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
PROMOTE_SHA: ${{ steps.stable_release.outputs.promote_sha }}
run: |
set -euo pipefail
git push origin "${PROMOTE_SHA}:refs/heads/docs-stable"
# Docs promotion lives in promote-stable-docs.yml, which triggers on the
# SuperDoc workflow specifically. Tooling-bundle releases (CLI/SDK/MCP)
# do not advance docs-stable.
9 changes: 7 additions & 2 deletions .github/workflows/release-superdoc.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Auto-releases on push to main (@next channel).
# Stable releases are orchestrated centrally by release-stable.yml.
# Auto-releases on push to main (@next) and stable (@latest).
# docs-stable is advanced by promote-stable-docs.yml when this workflow
# completes a real release on stable (not a no-op or PR preview).
# Manual PR preview: dispatch with pr_number to publish @pr-<number>
name: 📦 Release superdoc

on:
push:
branches:
- main
- stable
paths:
- 'packages/superdoc/**'
- 'packages/layout-engine/**'
Expand All @@ -29,6 +31,9 @@ permissions:
pull-requests: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`.
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ github.ref_name != 'stable' }}

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-template-builder.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml.
# Auto-releases on push to main (@next) and stable (@latest).
name: 📦 Release template-builder

on:
push:
branches:
- main
- stable
paths:
- 'packages/template-builder/**'
- 'pnpm-workspace.yaml'
Expand All @@ -17,6 +17,9 @@ permissions:
packages: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`.
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ github.ref_name != 'stable' }}

Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/release-vscode-ext.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Auto-releases on push to main (@next).
# Stable releases are orchestrated centrally by release-stable.yml.
# Auto-releases on push to main (@next) and stable (@latest).
name: 📦 Release vscode-ext

on:
push:
branches:
- main
- stable
paths:
- 'apps/vscode-ext/**'
- 'packages/superdoc/**'
Expand All @@ -23,6 +23,9 @@ permissions:
packages: write

concurrency:
# Stable releases share the `release-stable` group so @semantic-release/git
# pushes to `stable` serialize across workflows; per-workflow groups would
# let releases race on `git push origin stable`.
group: ${{ github.ref_name == 'stable' && 'release-stable' || format('{0}-{1}', github.workflow, github.ref) }}
cancel-in-progress: ${{ github.ref_name != 'stable' }}

Expand Down
6 changes: 6 additions & 0 deletions apps/mcp/.releaserc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ const config = {
[
'@semantic-release/exec',
{
// MCP's published tarball declares `dist/` in `files` and a
// `dist/index.js` bin. Root `pnpm run build` only runs
// build:superdoc + type-check and does not produce apps/mcp/dist.
// Build MCP here so semantic-release ships a working tarball
// regardless of which workflow drives the release.
prepareCmd: 'pnpm run build',
publishCmd: 'pnpm publish --no-git-checks --access public --tag ${nextRelease.channel || "latest"}',
},
],
Expand Down
4 changes: 2 additions & 2 deletions cicd.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,12 @@ The workflow is `.github/workflows/release-cli.yml`. It analyzes commits across

| Command | What it does |
|---------|-------------|
| `pnpm run release:local` | Releases **superdocCLISDK** in sequence on `stable` |
| `pnpm run release:local` | Releases **CLISDKMCP** in sequence on `stable` (matches CI's tooling bundle) |
| `pnpm run release:local:superdoc` | Releases superdoc only |
| `pnpm run release:local:cli` | Releases CLI only |
| `pnpm run release:local:sdk` | Releases SDK only |

All accept `-- --dry-run` to preview without publishing. The combined orchestrator (`release:local`) enforces a `stable` branch guard (override with `--branch=<name>`).
All accept `-- --dry-run` to preview without publishing. The combined orchestrator (`release:local`) enforces a `stable` branch guard (override with `--branch=<name>`). On stable, this matches what CI's tooling bundle workflow does (`.github/workflows/release-stable.yml`); per-package workflows handle superdoc, react, esign, template-builder, and vscode-ext independently.

`@semantic-release/git` automatically pushes version commits and tags when releasing on the `stable` branch. This is existing behavior for superdoc, CLI, and SDK.

Expand Down
Loading
Loading