Skip to content
Open
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
233 changes: 233 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
name: Release

on:
push:
branches:
- 'release/**'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (publish with --dry-run)'
required: false
default: false
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write
id-token: write

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 20
environment: Release
if: startsWith(github.ref, 'refs/heads/release/')

steps:
- name: Checkout code
uses: actions/checkout@v6

- uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build everything
run: |
pnpm build

- name: Pack packages
run: |
pnpm pack -r --pack-destination release-artifacts

- name: Check if versions exist on npm
id: check_versions
run: |
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT

get_npm_tag() {
local version=$1
if [[ "$version" == *"-alpha"* ]]; then
echo "alpha"
elif [[ "$version" == *"-beta"* ]]; then
echo "beta"
elif [[ "$version" == *"-rc"* ]]; then
echo "rc"
else
echo "latest"
fi
}

check_npm_version() {
local pkg_name=$1
local pkg_path=$2
local output_key=$3

LOCAL_VERSION=$(node -p "require('./${pkg_path}/package.json').version")
NPM_TAG=$(get_npm_tag "$LOCAL_VERSION")
echo "${pkg_name} local version: $LOCAL_VERSION (tag: $NPM_TAG)"

echo "${output_key}_version=${LOCAL_VERSION}" >> $GITHUB_OUTPUT
echo "${output_key}_tag=${NPM_TAG}" >> $GITHUB_OUTPUT

if npm view "${pkg_name}@${LOCAL_VERSION}" version 2>/dev/null; then
echo "${pkg_name}@${LOCAL_VERSION} already exists on npm"
echo "${output_key}_exists=true" >> $GITHUB_OUTPUT
else
echo "${pkg_name}@${LOCAL_VERSION} does not exist on npm"
echo "${output_key}_exists=false" >> $GITHUB_OUTPUT
fi
}

check_npm_version "@tonconnect/protocol" "packages/protocol" "protocol"
check_npm_version "@tonconnect/sdk" "packages/sdk" "sdk"
check_npm_version "@tonconnect/ui" "packages/ui" "ui"
check_npm_version "@tonconnect/ui-react" "packages/ui-react" "ui_react"
check_npm_version "@tonconnect/isomorphic-eventsource" "packages/isomorphic-eventsource" "isomorphic_eventsource"
check_npm_version "@tonconnect/isomorphic-fetch" "packages/isomorphic-fetch" "isomorphic_fetch"

# Use sdk version for release tag
SDK_VERSION=$(node -p "require('./packages/sdk/package.json').version")
echo "release_version=${SDK_VERSION}" >> $GITHUB_OUTPUT

- name: Create git tags
if: inputs.dry_run != 'true'
run: |
TAG="v${{ steps.check_versions.outputs.release_version }}"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists, skipping"
else
git tag "$TAG"
git push origin "$TAG"
fi

- name: Publish @tonconnect/protocol
if: steps.check_versions.outputs.protocol_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/protocol" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.protocol_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Publish @tonconnect/isomorphic-eventsource
if: steps.check_versions.outputs.isomorphic_eventsource_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/isomorphic-eventsource" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.isomorphic_eventsource_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Publish @tonconnect/isomorphic-fetch
if: steps.check_versions.outputs.isomorphic_fetch_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/isomorphic-fetch" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.isomorphic_fetch_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Publish @tonconnect/sdk
if: steps.check_versions.outputs.sdk_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/sdk" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.sdk_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Publish @tonconnect/ui
if: steps.check_versions.outputs.ui_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/ui" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.ui_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Publish @tonconnect/ui-react
if: steps.check_versions.outputs.ui_react_exists == 'false'
run: |
DRY_RUN_FLAG=""
if [ "${{ inputs.dry_run }}" = "true" ]; then
DRY_RUN_FLAG="--dry-run"
echo "🏜️ DRY RUN mode"
fi
pnpm publish \
--filter="@tonconnect/ui-react" \
--access=public \
--publish-branch="${{ steps.check_versions.outputs.branch_name }}" \
--tag=${{ steps.check_versions.outputs.ui_react_tag }} \
--provenance \
--verbose \
$DRY_RUN_FLAG

- name: Create Release Draft
if: inputs.dry_run != 'true'
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.check_versions.outputs.release_version }}
name: Release v${{ steps.check_versions.outputs.release_version }}
draft: true
files: |
release-artifacts/tonconnect-protocol-*.tgz
release-artifacts/tonconnect-sdk-*.tgz
release-artifacts/tonconnect-ui-*.tgz
release-artifacts/tonconnect-ui-react-*.tgz
release-artifacts/tonconnect-isomorphic-eventsource-*.tgz
release-artifacts/tonconnect-isomorphic-fetch-*.tgz
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 changes: 69 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Release Process

## Overview

Releases are published **only from `release/*` branches** and require **manual approval** via the GitHub `Release` environment.

| npm tag | Version pattern | Branch example | Use case |
| ---------- | --------------------- | ----------------------- | --------------------------- |
| `beta` | `x.y.z-beta.N` | `release/2.5.0-beta.1` | Pre-release testing |
| `latest` | `x.y.z` | `release/2.5.0` | Stable production release |

> `dev` publishing is not part of this workflow.

## How It Works

1. **Create a release branch** from `main`:
```bash
git checkout main
git pull
git checkout -b release/2.5.0-beta.1
```

2. **Bump versions** in the packages you want to publish using changesets or manually:
```bash
pnpm changeset # interactive version bump
pnpm changeset version # apply changesets
git add -A && git commit -m "chore: release 2.5.0-beta.1"
```

3. **Push the branch**:
```bash
git push -u origin release/2.5.0-beta.1
```

4. **Trigger the workflow**: Go to **Actions → Release → Run workflow**, select your `release/*` branch, and click **Run workflow**.

5. **Approve the deployment**: A reviewer with access to the `Release` environment must approve the run. The workflow will wait for approval before publishing.

6. The workflow will:
- Install, lint, build, and test
- Compare each package version with npm — only changed packages are published
- Detect the npm tag automatically from the version string (`-beta` → `beta`, `-rc` → `rc`, `-alpha` → `alpha`, otherwise `latest`)
- Publish each changed package with the correct tag
- Create git tags and a GitHub Release

## Safeguards

- **Approval required** — the `Release` GitHub environment must have protection rules with required reviewers.
- **`release/*` branches only** — the workflow will not run on `main`, `develop`, or feature branches.
- **No major bumps in CI** — major version increments are blocked; release them manually.
- **Idempotent** — packages already published at the same version are skipped.
- **Dry run** — set the `dry_run` input to `true` to validate without publishing.

## Promoting Beta to Stable

```bash
# 1. Create the stable release branch
git checkout release/2.5.0-beta.3
git checkout -b release/2.5.0

# 2. Remove pre-release suffixes from package.json versions
# e.g. "2.5.0-beta.3" → "2.5.0"

# 3. Commit and push
git add -A && git commit -m "chore: release 2.5.0"
git push -u origin release/2.5.0

# 4. Trigger & approve the workflow from Actions tab
```
Loading