diff --git a/.github/workflows/alpha-release.yml b/.github/workflows/alpha-release.yml new file mode 100644 index 000000000..6f61fa864 --- /dev/null +++ b/.github/workflows/alpha-release.yml @@ -0,0 +1,195 @@ +name: Alpha Release + +# Publishes a worker from a feature branch for integration testing without +# touching the branch or main. The tag points at an ephemeral commit whose +# manifest is bumped to an alpha version; only the tag is pushed. +# +# The regular release workflow handles this tag normally, producing a GitHub +# prerelease and publishing the worker to the isolated `experimental` channel. +# Run this workflow from main and provide the feature branch or pull-request +# ref to release. That lets it test PRs created before this workflow existed. + +on: + workflow_dispatch: + inputs: + source_ref: + description: 'Feature branch or pull-request ref (e.g. feat/my-worker or refs/pull/123/head)' + required: true + type: string + worker: + description: 'Worker module to publish from this branch' + required: true + type: choice + options: + - acp + - approval-gate + - bridge + - browser + - claude-code + - codex + - devin + - console + - grok + - context-manager + - cron + - database + - email + - eval + - harness + - hermes + - http + - iii-directory + - lsp + - image-resize + - llm-router + - fp + - github + - mcp + - memory + - memory-consolidate + - opencode + - pi + - provider-anthropic + - provider-claude-code + - provider-kimi + - provider-llamacpp + - provider-openai + - provider-openai-codex + - provider-xai + - provider-zai + - pubsub + - queue + - rbac-proxy + - session-manager + - slack + - telegram-bot + - shell + - state + - storage + - scrapling + - web + - worktree + bump: + description: 'Base version for the alpha (none = use the manifest version as its base)' + required: true + type: choice + options: + - patch + - minor + - major + - none + default: patch + +permissions: + contents: write + +concurrency: + group: alpha-release-${{ inputs.worker }} + cancel-in-progress: false + +jobs: + prepare: + name: Bump and tag alpha + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout source ref + uses: actions/checkout@v5 + with: + fetch-depth: 0 + ref: ${{ inputs.source_ref }} + + - name: Refuse to release main + env: + SOURCE_REF: ${{ inputs.source_ref }} + run: | + set -euo pipefail + git fetch origin main + source_sha=$(git rev-parse HEAD) + main_sha=$(git rev-parse origin/main) + if [[ "$source_sha" == "$main_sha" ]]; then + echo "::error::Alpha Release is for feature branches; use Create Tag for main releases" + exit 1 + fi + echo "::notice::Preparing alpha release from $SOURCE_REF at $source_sha" + + - name: Discover manifest + id: meta + env: + WORKER: ${{ inputs.worker }} + run: | + set -euo pipefail + if [[ ! -f "$WORKER/iii.worker.yaml" ]]; then + echo "::error::$WORKER/iii.worker.yaml is missing" + exit 1 + fi + manifest=$(grep '^manifest:' "$WORKER/iii.worker.yaml" | head -n1 | awk '{print $2}') + if [[ -z "$manifest" ]]; then + echo "::error::$WORKER/iii.worker.yaml has no 'manifest' key" + exit 1 + fi + echo "manifest=$manifest" >> "$GITHUB_OUTPUT" + + - name: Calculate and write alpha version + id: version + env: + WORKER: ${{ inputs.worker }} + BUMP: ${{ inputs.bump }} + MANIFEST: ${{ steps.meta.outputs.manifest }} + run: | + set -euo pipefail + version=$(python3 .github/scripts/manifest_version.py bump "$WORKER/$MANIFEST" \ + --kind "$BUMP" --suffix alpha --worker "$WORKER") + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "tag=${WORKER}/v${version}" >> "$GITHUB_OUTPUT" + echo "::notice::${WORKER}: alpha ${version}@experimental" + + - name: Validate manifest update + env: + WORKER: ${{ inputs.worker }} + MANIFEST: ${{ steps.meta.outputs.manifest }} + VERSION: ${{ steps.version.outputs.version }} + run: | + set -euo pipefail + python3 .github/scripts/manifest_version.py verify \ + "$WORKER/$MANIFEST" --expected "$VERSION" + + - name: Sync Cargo.lock to alpha version + env: + WORKER: ${{ inputs.worker }} + MANIFEST: ${{ steps.meta.outputs.manifest }} + run: | + set -euo pipefail + python3 .github/scripts/manifest_version.py sync-lock "$WORKER/$MANIFEST" + + - name: Check tag does not exist + env: + TAG: ${{ steps.version.outputs.tag }} + run: | + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "::error::Tag $TAG already exists" + exit 1 + fi + + - name: Commit alpha version and push only its tag + env: + TAG: ${{ steps.version.outputs.tag }} + WORKER: ${{ inputs.worker }} + VERSION: ${{ steps.version.outputs.version }} + run: | + set -euo pipefail + git config user.name "workers-ci[bot]" + git config user.email "workers-ci[bot]@users.noreply.github.com" + git add -A + git commit -m "chore(${WORKER}): alpha v${VERSION} [skip ci]" + git tag -a "$TAG" -m "Alpha release $TAG + + worker: $WORKER + version: $VERSION + registry-tag: experimental + " + # The ephemeral commit is reachable only through this tag. Never + # push the selected feature branch or main from this workflow. + git push origin "$TAG" + echo "::notice::Published $TAG@experimental" diff --git a/docs/sops/release.md b/docs/sops/release.md index 037ec323a..e4a45c4e8 100644 --- a/docs/sops/release.md +++ b/docs/sops/release.md @@ -173,6 +173,23 @@ way the GitHub Release is marked prerelease and still builds and publishes (unless `interface_smoke: false`). A hand-pushed tag must carry the `.N` counter — `parse_release_tag.py` detects prereleases as `-.`. +### Alpha release from a pull request branch + +To publish a worker from an unmerged pull request for integration testing, use +**Actions → Alpha Release** from `main`. Set **Source ref** to the pull request +branch (or `refs/pull//head`), then choose the worker and the intended +base-version bump. The workflow creates an ephemeral commit with an +`-alpha.N` manifest version, then pushes only its annotated tag, for example +`browser/v1.4.0-alpha.1`. + +The release pipeline publishes that tag as a GitHub prerelease and assigns it +the `experimental` registry channel (`browser@experimental`). Neither the +selected branch nor `main` is pushed or changed. The channel is shared: a new +alpha release for the same worker moves `experimental` to that version. + +**Source ref** must not resolve to `main`; use **Create Tag** for a release +that should move `latest` or `next`. + ### Dry run Tag shape: `/vX.Y.Z-dry-run.1` (parsed by `parse_release_tag.py`).