Create Tag #188
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Create Tag | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| target: | |
| description: 'Release target' | |
| required: true | |
| type: choice | |
| options: | |
| - iii | |
| - motia | |
| bump: | |
| description: 'Version bump type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| default: patch | |
| prerelease: | |
| description: 'Pre-release label (none = stable release)' | |
| required: true | |
| type: choice | |
| options: | |
| - none | |
| - alpha | |
| - beta | |
| - rc | |
| - next | |
| default: none | |
| dry_run: | |
| description: 'Dry run (creates -dry-run.N tag, builds without publishing)' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| actions: write | |
| jobs: | |
| create-tag: | |
| name: Bump Versions & Create Tag | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Generate token | |
| id: generate_token | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ secrets.III_CI_APP_ID }} | |
| private-key: ${{ secrets.III_CI_APP_PRIVATE_KEY }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ steps.generate_token.outputs.token }} | |
| - name: Pre-flight checks | |
| env: | |
| TARGET: ${{ inputs.target }} | |
| run: | | |
| BRANCH=$(git rev-parse --abbrev-ref HEAD) | |
| if [[ "$BRANCH" != "main" ]]; then | |
| echo "::error::Must be on main branch (currently on $BRANCH)" | |
| exit 1 | |
| fi | |
| if [[ "$TARGET" == "iii" ]]; then | |
| for f in engine/Cargo.toml \ | |
| sdk/packages/rust/iii/Cargo.toml \ | |
| sdk/packages/node/iii/package.json \ | |
| sdk/packages/node/iii-browser/package.json \ | |
| sdk/packages/python/iii/pyproject.toml \ | |
| console/packages/console-rust/Cargo.toml; do | |
| if [[ ! -f "$f" ]]; then | |
| echo "::error::$f not found" | |
| exit 1 | |
| fi | |
| done | |
| fi | |
| if [[ "$TARGET" == "motia" ]]; then | |
| for f in frameworks/motia/motia-js/packages/motia/package.json \ | |
| frameworks/motia/motia-py/packages/motia/pyproject.toml; do | |
| if [[ ! -f "$f" ]]; then | |
| echo "::error::$f not found" | |
| exit 1 | |
| fi | |
| done | |
| fi | |
| - name: Calculate versions | |
| id: versions | |
| env: | |
| TARGET: ${{ inputs.target }} | |
| BUMP: ${{ inputs.bump }} | |
| PRERELEASE: ${{ inputs.prerelease }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| run: | | |
| read_cargo_version() { grep '^version = ' "$1" | head -n1 | cut -d'"' -f2; } | |
| read_json_version() { grep '"version":' "$1" | head -n1 | cut -d'"' -f4; } | |
| bump_version() { | |
| local current="$1" | |
| local tag_prefix="$2" | |
| local bump_type="$3" | |
| local base="${current%%-*}" | |
| IFS='.' read -r major minor patch <<< "$base" | |
| # Check if current version already has the requested pre-release label | |
| local current_pre="" | |
| if [[ "$current" =~ -([a-z]+)\.[0-9]+$ ]]; then | |
| current_pre="${BASH_REMATCH[1]}" | |
| fi | |
| local new_base | |
| if [[ "$PRERELEASE" != "none" && "$current_pre" == "$PRERELEASE" ]]; then | |
| # Same pre-release channel: keep base, increment counter | |
| # e.g. 0.12.0-next.1 → 0.12.0-next.2 | |
| new_base="$base" | |
| elif [[ "$PRERELEASE" == "none" && -n "$current_pre" ]]; then | |
| # Promoting pre-release to stable: use base as-is | |
| # e.g. 0.12.0-next.3 → 0.12.0 | |
| new_base="$base" | |
| else | |
| # Stable → stable bump, or stable → new pre-release | |
| # e.g. 0.11.0 + minor → 0.12.0 | |
| # e.g. 0.11.0 + minor + next → 0.12.0-next.1 | |
| case "$bump_type" in | |
| major) major=$((major + 1)); minor=0; patch=0 ;; | |
| minor) minor=$((minor + 1)); patch=0 ;; | |
| patch) patch=$((patch + 1)) ;; | |
| esac | |
| new_base="${major}.${minor}.${patch}" | |
| fi | |
| if [[ "$PRERELEASE" != "none" ]]; then | |
| local existing | |
| existing=$(git tag -l "${tag_prefix}/v${new_base}-${PRERELEASE}.*" 2>/dev/null | sort -V) | |
| local pre_num=1 | |
| if [[ -n "$existing" ]]; then | |
| local latest_pre | |
| latest_pre=$(echo "$existing" | tail -n1) | |
| pre_num=$(echo "$latest_pre" | grep -oE '\.[0-9]+$' | tr -d '.') | |
| pre_num=$((pre_num + 1)) | |
| fi | |
| echo "${new_base}-${PRERELEASE}.${pre_num}" | |
| else | |
| echo "$new_base" | |
| fi | |
| } | |
| next_dry_run_version() { | |
| local base="$1" | |
| local tag_prefix="$2" | |
| local existing | |
| existing=$(git tag -l "${tag_prefix}/v${base}-dry-run.*" 2>/dev/null | sort -V) | |
| local num=1 | |
| if [[ -n "$existing" ]]; then | |
| local latest | |
| latest=$(echo "$existing" | tail -n1) | |
| num=$(echo "$latest" | grep -oE '\.[0-9]+$' | tr -d '.') | |
| num=$((num + 1)) | |
| fi | |
| echo "${base}-dry-run.${num}" | |
| } | |
| to_pep440() { | |
| local version="$1" | |
| if [[ "$version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)-([a-z]+)\.([0-9]+)$ ]]; then | |
| local base="${BASH_REMATCH[1]}" | |
| local label="${BASH_REMATCH[2]}" | |
| local num="${BASH_REMATCH[3]}" | |
| case "$label" in | |
| rc) echo "${base}rc${num}" ;; | |
| alpha) echo "${base}a${num}" ;; | |
| beta) echo "${base}b${num}" ;; | |
| next) echo "${base}.dev${num}" ;; | |
| esac | |
| else | |
| echo "$version" | |
| fi | |
| } | |
| if [[ "$PRERELEASE" != "none" ]]; then | |
| echo "is_prerelease=true" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=$PRERELEASE" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_prerelease=false" >> "$GITHUB_OUTPUT" | |
| echo "npm_tag=latest" >> "$GITHUB_OUTPUT" | |
| fi | |
| if [[ "$TARGET" == "iii" ]]; then | |
| current=$(read_cargo_version "engine/Cargo.toml") | |
| new_ver=$(bump_version "$current" "iii" "$BUMP") | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| base_ver="${new_ver%%-*}" | |
| new_ver=$(next_dry_run_version "$base_ver" "iii") | |
| fi | |
| py_ver=$(to_pep440 "$new_ver") | |
| echo "version=$new_ver" >> "$GITHUB_OUTPUT" | |
| echo "python_version=$py_ver" >> "$GITHUB_OUTPUT" | |
| echo "tag=iii/v${new_ver}" >> "$GITHUB_OUTPUT" | |
| echo "current=$current" >> "$GITHUB_OUTPUT" | |
| echo "::notice::iii: $current -> $new_ver (python: $py_ver)" | |
| fi | |
| if [[ "$TARGET" == "motia" ]]; then | |
| current=$(read_json_version "frameworks/motia/motia-js/packages/motia/package.json") | |
| new_ver=$(bump_version "$current" "motia" "$BUMP") | |
| if [[ "$DRY_RUN" == "true" ]]; then | |
| base_ver="${new_ver%%-*}" | |
| new_ver=$(next_dry_run_version "$base_ver" "motia") | |
| fi | |
| py_ver=$(to_pep440 "$new_ver") | |
| echo "version=$new_ver" >> "$GITHUB_OUTPUT" | |
| echo "python_version=$py_ver" >> "$GITHUB_OUTPUT" | |
| echo "tag=motia/v${new_ver}" >> "$GITHUB_OUTPUT" | |
| echo "current=$current" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Motia: $current -> $new_ver (python: $py_ver)" | |
| fi | |
| - name: Update iii manifests | |
| if: inputs.target == 'iii' && inputs.dry_run != true | |
| env: | |
| NEW_VERSION: ${{ steps.versions.outputs.version }} | |
| PY_VERSION: ${{ steps.versions.outputs.python_version }} | |
| run: | | |
| sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"${NEW_VERSION}\"/}" engine/Cargo.toml | |
| echo "Updated engine/Cargo.toml to $NEW_VERSION" | |
| sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"${NEW_VERSION}\"/}" sdk/packages/rust/iii/Cargo.toml | |
| echo "Updated sdk/packages/rust/iii/Cargo.toml to $NEW_VERSION" | |
| jq --arg v "$NEW_VERSION" '.version = $v' sdk/packages/node/iii/package.json > tmp.json && mv tmp.json sdk/packages/node/iii/package.json | |
| echo "Updated sdk/packages/node/iii/package.json to $NEW_VERSION" | |
| jq --arg v "$NEW_VERSION" '.version = $v' sdk/packages/node/iii-browser/package.json > tmp.json && mv tmp.json sdk/packages/node/iii-browser/package.json | |
| echo "Updated sdk/packages/node/iii-browser/package.json to $NEW_VERSION" | |
| sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"${PY_VERSION}\"/}" sdk/packages/python/iii/pyproject.toml | |
| echo "Updated sdk/packages/python/iii/pyproject.toml to $PY_VERSION" | |
| sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"${NEW_VERSION}\"/}" console/packages/console-rust/Cargo.toml | |
| echo "Updated console/packages/console-rust/Cargo.toml to $NEW_VERSION" | |
| - name: Update motia manifests | |
| if: inputs.target == 'motia' && inputs.dry_run != true | |
| env: | |
| NEW_VERSION: ${{ steps.versions.outputs.version }} | |
| PY_VERSION: ${{ steps.versions.outputs.python_version }} | |
| run: | | |
| jq --arg v "$NEW_VERSION" '.version = $v' frameworks/motia/motia-js/packages/motia/package.json > tmp.json && mv tmp.json frameworks/motia/motia-js/packages/motia/package.json | |
| echo "Updated frameworks/motia/motia-js/packages/motia/package.json to $NEW_VERSION" | |
| sed -i "0,/^version = \".*\"/{s/^version = \".*\"/version = \"${PY_VERSION}\"/}" frameworks/motia/motia-py/packages/motia/pyproject.toml | |
| echo "Updated frameworks/motia/motia-py/packages/motia/pyproject.toml to $PY_VERSION" | |
| - name: Validate version updates | |
| if: inputs.dry_run != true | |
| env: | |
| TARGET: ${{ inputs.target }} | |
| VERSION: ${{ steps.versions.outputs.version }} | |
| PY_VERSION: ${{ steps.versions.outputs.python_version }} | |
| run: | | |
| check_cargo() { | |
| local actual | |
| actual=$(grep '^version = ' "$1" | head -n1 | cut -d'"' -f2) | |
| if [[ "$actual" != "$2" ]]; then | |
| echo "::error::$1 version mismatch: expected $2, got $actual" | |
| exit 1 | |
| fi | |
| echo " $1 -> $actual" | |
| } | |
| check_json() { | |
| local actual | |
| actual=$(grep '"version":' "$1" | head -n1 | cut -d'"' -f4) | |
| if [[ "$actual" != "$2" ]]; then | |
| echo "::error::$1 version mismatch: expected $2, got $actual" | |
| exit 1 | |
| fi | |
| echo " $1 -> $actual" | |
| } | |
| echo "Validating version updates..." | |
| if [[ "$TARGET" == "iii" ]]; then | |
| check_cargo "engine/Cargo.toml" "$VERSION" | |
| check_cargo "sdk/packages/rust/iii/Cargo.toml" "$VERSION" | |
| check_json "sdk/packages/node/iii/package.json" "$VERSION" | |
| check_json "sdk/packages/node/iii-browser/package.json" "$VERSION" | |
| check_cargo "sdk/packages/python/iii/pyproject.toml" "$PY_VERSION" | |
| check_cargo "console/packages/console-rust/Cargo.toml" "$VERSION" | |
| fi | |
| if [[ "$TARGET" == "motia" ]]; then | |
| check_json "frameworks/motia/motia-js/packages/motia/package.json" "$VERSION" | |
| check_cargo "frameworks/motia/motia-py/packages/motia/pyproject.toml" "$PY_VERSION" | |
| fi | |
| echo "All versions validated." | |
| - name: Check tag does not exist | |
| env: | |
| TAG: ${{ steps.versions.outputs.tag }} | |
| run: | | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "::error::Tag $TAG already exists" | |
| exit 1 | |
| fi | |
| - name: Configure git identity | |
| run: | | |
| git config user.name "iii-ci[bot]" | |
| git config user.email "iii-ci[bot]@users.noreply.github.com" | |
| - name: Commit and push | |
| if: inputs.dry_run != true | |
| env: | |
| TAG: ${{ steps.versions.outputs.tag }} | |
| run: | | |
| git add -A | |
| git commit -m "chore: bump versions for release -- ${{ inputs.target }}($TAG)" | |
| git push origin main | |
| - name: Create and push tag | |
| env: | |
| TAG: ${{ steps.versions.outputs.tag }} | |
| run: | | |
| git tag -a "$TAG" -m "Release $TAG" | |
| git push origin "$TAG" | |
| echo "Created tag: $TAG" | |
| - name: Notify Slack | |
| continue-on-error: true | |
| uses: slackapi/slack-github-action@v2.0.0 | |
| with: | |
| method: chat.postMessage | |
| token: ${{ secrets.SLACK_BOT_TOKEN }} | |
| payload: | | |
| channel: ${{ secrets.SLACK_CHANNEL_ID }} | |
| text: "${{ inputs.dry_run == true && '[DRY RUN] ' || '' }}Tag created for release" | |
| blocks: | |
| - type: "section" | |
| text: | |
| type: "mrkdwn" | |
| text: "${{ inputs.dry_run == true && ':test_tube: *Dry Run — Tag Preview*' || ':label: *Tag Created*' }}\n\n:package: ${{ inputs.target }}: `${{ steps.versions.outputs.tag }}`\nPre-release: `${{ inputs.prerelease }}`\nTriggered by: ${{ github.actor }}${{ inputs.dry_run == true && '\n\n_No commits, tags, or publishes — validation only_' || '' }}" | |
| - type: "context" | |
| elements: | |
| - type: "mrkdwn" | |
| text: "<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Workflow>" |