426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
1020 lines
42 KiB
YAML
1020 lines
42 KiB
YAML
name: NPM Release
|
|
|
|
# Workflow for all NPM releases:
|
|
# - Beta: On v*-beta.* tag pushes
|
|
# - Production: On stable GitHub release creation
|
|
#
|
|
# Version Management:
|
|
# - Uses lerna version and publish commands with consistent patterns
|
|
# - Commits version changes to git FIRST, then publishes to NPM
|
|
# - Prevents infinite loops with [skip ci] in commit messages
|
|
# - Proper error handling without masking critical failures
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- "v*-beta.*"
|
|
release:
|
|
types: [created]
|
|
workflow_dispatch:
|
|
inputs:
|
|
release_type:
|
|
description: "Manual release type (only for prerelease testing)"
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- beta
|
|
# Note: 'latest' removed - production releases MUST be done via GitHub releases
|
|
|
|
concurrency:
|
|
# Release runs create and push commits/tags. Serialize them per branch so
|
|
# later runs don't fail with non-fast-forward pushes while an earlier release
|
|
# is still updating the branch.
|
|
group: npm-release-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
# Publishing uses the NPM_TOKEN secret (a granular automation token with
|
|
# read+write on the @elizaos/* scope).
|
|
#
|
|
# We deliberately do NOT grant `id-token: write` here. When an OIDC token is
|
|
# available, lerna 9.0.7 attempts npm trusted publishing *first* and hard-fails
|
|
# the whole release if a package has no Trusted Publisher entry configured on
|
|
# npmjs.com — it never falls back to NPM_TOKEN. The observed failure was:
|
|
# lerna verb oidc Failed token exchange request ... OIDC token exchange error - package not found
|
|
# lerna WARN notice Package failed to publish: @elizaos/agent
|
|
# lerna ERR! E404 Not found
|
|
# (the E404 is the masked OIDC exchange failure, not a token-access problem).
|
|
# Without `id-token: write` the OIDC env vars are absent, so lerna skips the
|
|
# exchange and publishes with the token. Provenance is disabled for the same
|
|
# reason (it requires id-token). To re-enable provenance + trusted publishing
|
|
# later, configure a Trusted Publisher for every published @elizaos/* package
|
|
# at https://docs.npmjs.com/trusted-publishers, then restore `id-token: write`
|
|
# and `NPM_CONFIG_PROVENANCE: "true"`.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
release:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write # release commits push lerna version bumps + git tags
|
|
packages: write # publish to GitHub Packages mirror
|
|
issues: write # comment on release-tracking issues
|
|
actions: read # read other workflow runs for context
|
|
# Skip if commit message contains [skip ci]. Alpha releases are disabled;
|
|
# prerelease GitHub-release events are skipped so tag pushes are the single
|
|
# npm publication path for beta versions.
|
|
if: >-
|
|
${{
|
|
!contains(github.event.head_commit.message || '', '[skip ci]') &&
|
|
!(github.event_name == 'release' && contains(github.event.release.tag_name || '', '-')) &&
|
|
!(github.ref_type == 'tag' && contains(github.ref_name || '', '-alpha'))
|
|
}}
|
|
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
with:
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Setup Git
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
# If triggered by a release, we're in detached HEAD state
|
|
# Create a temporary branch that matches Lerna's allowBranch pattern
|
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
echo "Creating temporary release branch from tag..."
|
|
git checkout -b release/production-${{ github.sha }}
|
|
fi
|
|
|
|
- name: Install system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y libpq-dev postgresql-client protobuf-compiler libwayland-dev libpipewire-0.3-dev libegl-dev libgbm-dev libxcb1-dev libssl-dev
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
|
with:
|
|
node-version: "24.x"
|
|
registry-url: "https://registry.npmjs.org"
|
|
|
|
- name: Setup Bun
|
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
|
|
with:
|
|
bun-version: "canary"
|
|
|
|
# release.yaml uses oven-sh/setup-bun directly (not setup-bun-workspace),
|
|
# so it gets no .turbo restore by default — the publish build was cold
|
|
# every run. Restore the local Turbo cache (shared with ci/test runs via
|
|
# the runner.os prefix) and the Bun install cache. Use the same
|
|
# content-derived key as setup-bun-workspace so exact hits can still save
|
|
# fresh task results after Turbo-relevant inputs change.
|
|
- name: Compute Turbo cache key
|
|
id: turbo_cache_key
|
|
run: node packages/scripts/turbo-cache-key.mjs --github-output
|
|
|
|
- name: Restore Turbo cache (GitHub Actions)
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
|
with:
|
|
path: .turbo
|
|
key: turbo-${{ runner.os }}-release-${{ steps.turbo_cache_key.outputs.turbo_cache_key }}
|
|
restore-keys: |
|
|
turbo-${{ runner.os }}-release-
|
|
turbo-${{ runner.os }}-
|
|
|
|
- name: Cache Bun install
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
|
|
with:
|
|
path: ~/.bun/install/cache
|
|
key: bun-${{ runner.os }}-canary-${{ hashFiles('bun.lock') }}
|
|
restore-keys: |
|
|
bun-${{ runner.os }}-canary-
|
|
bun-${{ runner.os }}-
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
restore_package_json() {
|
|
if [[ -n "${PACKAGE_JSON_BACKUP:-}" && -f "${PACKAGE_JSON_BACKUP}" ]]; then
|
|
mv "${PACKAGE_JSON_BACKUP}" package.json
|
|
fi
|
|
}
|
|
|
|
PACKAGE_JSON_BACKUP="$(mktemp)"
|
|
cp package.json "${PACKAGE_JSON_BACKUP}"
|
|
trap restore_package_json EXIT
|
|
|
|
node - <<'NODE'
|
|
const fs = require("node:fs");
|
|
const packageJsonPath = "package.json";
|
|
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
const optionalWorkspaceEntries = [
|
|
"plugins/plugin-sql",
|
|
"plugins/plugin-ollama",
|
|
"plugins/plugin-local-ai",
|
|
"plugins/plugin-pdf",
|
|
"plugins/plugin-whatsapp",
|
|
];
|
|
|
|
if (!Array.isArray(pkg.workspaces)) {
|
|
throw new Error("package.json is missing a workspaces array");
|
|
}
|
|
|
|
let changed = false;
|
|
for (const entry of optionalWorkspaceEntries) {
|
|
if (fs.existsSync(`${entry}/package.json`) && !pkg.workspaces.includes(entry)) {
|
|
pkg.workspaces.push(entry);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
}
|
|
NODE
|
|
|
|
bun install --ignore-scripts
|
|
restore_package_json
|
|
trap - EXIT
|
|
# --ignore-scripts prevents postinstall scripts from running, but build
|
|
# tools like esbuild and bun need their postinstall to install platform binaries.
|
|
# Run them explicitly so turbo and lerna prepublishOnly can spawn build processes.
|
|
node node_modules/esbuild/install.js 2>/dev/null || true
|
|
node node_modules/bun/install.js 2>/dev/null || true
|
|
|
|
# Determine release type and version
|
|
- name: Determine release type
|
|
id: release_type
|
|
run: |
|
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
# Extract version from tag (remove 'v' prefix if present)
|
|
VERSION="${{ github.event.release.tag_name }}"
|
|
VERSION="${VERSION#v}"
|
|
if [[ "${VERSION}" == *"-beta."* ]]; then
|
|
echo "type=beta" >> $GITHUB_OUTPUT
|
|
echo "dist_tag=beta" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "type=latest" >> $GITHUB_OUTPUT
|
|
echo "dist_tag=latest" >> $GITHUB_OUTPUT
|
|
fi
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
echo "is_release_event=true" >> $GITHUB_OUTPUT
|
|
elif [[ "${{ github.ref_type }}" == "tag" ]]; then
|
|
VERSION="${{ github.ref_name }}"
|
|
VERSION="${VERSION#v}"
|
|
if [[ "${VERSION}" == *"-beta."* ]]; then
|
|
echo "type=beta" >> $GITHUB_OUTPUT
|
|
echo "dist_tag=beta" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "type=latest" >> $GITHUB_OUTPUT
|
|
echo "dist_tag=latest" >> $GITHUB_OUTPUT
|
|
fi
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
echo "is_release_event=true" >> $GITHUB_OUTPUT
|
|
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
echo "type=${{ github.event.inputs.release_type }}" >> $GITHUB_OUTPUT
|
|
echo "dist_tag=${{ github.event.inputs.release_type }}" >> $GITHUB_OUTPUT
|
|
echo "is_release_event=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
# Version Management
|
|
- name: Version packages
|
|
id: version
|
|
run: |
|
|
RELEASE_TYPE="${{ steps.release_type.outputs.type }}"
|
|
CURRENT_VERSION=$(node -p "require('./lerna.json').version")
|
|
echo "Current version: ${CURRENT_VERSION}"
|
|
|
|
# Helper functions for version manipulation
|
|
get_base_version() {
|
|
echo "$1" | sed 's/-.*$//'
|
|
}
|
|
|
|
get_prerelease_type() {
|
|
if [[ "$1" =~ -([a-z]+)\. ]]; then
|
|
echo "${BASH_REMATCH[1]}"
|
|
else
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
bump_version() {
|
|
local version="$1"
|
|
local type="$2" # major, minor, patch
|
|
IFS='.' read -r major minor patch <<< "$version"
|
|
patch=${patch%%-*} # Remove any prerelease suffix
|
|
|
|
case "$type" in
|
|
major)
|
|
echo "$((major + 1)).0.0"
|
|
;;
|
|
minor)
|
|
echo "${major}.$((minor + 1)).0"
|
|
;;
|
|
patch)
|
|
echo "${major}.${minor}.$((patch + 1))"
|
|
;;
|
|
*)
|
|
echo "$version"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
version_packages() {
|
|
local next_version="$1"
|
|
node - "${next_version}" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const nextVersion = process.argv[2];
|
|
const root = process.cwd();
|
|
const lernaPath = path.join(root, "lerna.json");
|
|
const lernaSource = fs.readFileSync(lernaPath, "utf8");
|
|
const lerna = JSON.parse(lernaSource);
|
|
const packagePatterns = lerna.packages || [];
|
|
const packageJsonPaths = new Set();
|
|
|
|
function detectIndent(source) {
|
|
const match = source.match(/\{\n([ \t]+)"/);
|
|
if (!match) {
|
|
return 2;
|
|
}
|
|
return match[1].includes("\t") ? "\t" : match[1].length;
|
|
}
|
|
|
|
function addPackageJson(packageDir) {
|
|
const packageJsonPath = path.join(root, packageDir, "package.json");
|
|
if (!fs.existsSync(packageJsonPath)) {
|
|
return;
|
|
}
|
|
packageJsonPaths.add(packageJsonPath);
|
|
}
|
|
|
|
for (const pattern of packagePatterns) {
|
|
if (!pattern.includes("*")) {
|
|
addPackageJson(pattern);
|
|
continue;
|
|
}
|
|
|
|
const [baseDir, rest] = pattern.split("*");
|
|
const normalizedBase = baseDir.replace(/\/$/, "");
|
|
if (!fs.existsSync(path.join(root, normalizedBase))) {
|
|
continue;
|
|
}
|
|
for (const entry of fs.readdirSync(path.join(root, normalizedBase), { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) {
|
|
continue;
|
|
}
|
|
addPackageJson(path.join(normalizedBase, entry.name, rest.replace(/^\//, "")));
|
|
}
|
|
}
|
|
|
|
lerna.version = nextVersion;
|
|
fs.writeFileSync(lernaPath, `${JSON.stringify(lerna, null, detectIndent(lernaSource))}\n`);
|
|
|
|
let updated = 0;
|
|
for (const packageJsonPath of [...packageJsonPaths].sort()) {
|
|
const source = fs.readFileSync(packageJsonPath, "utf8");
|
|
const pkg = JSON.parse(source);
|
|
if (pkg.private === true) {
|
|
continue;
|
|
}
|
|
const oldVersion = pkg.version;
|
|
pkg.version = nextVersion;
|
|
fs.writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, detectIndent(source))}\n`);
|
|
updated += 1;
|
|
console.log(`${path.relative(root, packageJsonPath)}: ${oldVersion} => ${nextVersion}`);
|
|
}
|
|
|
|
console.log(`Updated ${updated} public packages to ${nextVersion}`);
|
|
NODE
|
|
}
|
|
|
|
# Determine version strategy based on release type and current version
|
|
if [[ "${{ steps.release_type.outputs.is_release_event }}" == "true" ]]; then
|
|
# Tagged release from git tag or GitHub release tag
|
|
VERSION="${{ steps.release_type.outputs.version }}"
|
|
echo "📦 Tagged release: Setting exact version to ${VERSION}"
|
|
version_packages "${VERSION}"
|
|
|
|
elif [[ "${RELEASE_TYPE}" == "beta" ]]; then
|
|
echo "🔵 Beta release workflow..."
|
|
BASE_VERSION=$(get_base_version "$CURRENT_VERSION")
|
|
|
|
# Always check existing tags to find the true highest beta version
|
|
echo "Checking for existing beta tags for base version ${BASE_VERSION}..."
|
|
git fetch --tags
|
|
|
|
HIGHEST_TAG=$(git tag -l "v${BASE_VERSION}-beta.*" 2>/dev/null | sort -V | tail -n 1)
|
|
|
|
if [[ -n "$HIGHEST_TAG" ]]; then
|
|
HIGHEST_VERSION=${HIGHEST_TAG#v}
|
|
echo "Highest existing beta tag: ${HIGHEST_VERSION}"
|
|
|
|
HIGHEST_NUM=$(echo "$HIGHEST_VERSION" | grep -o '[0-9]*$')
|
|
CURRENT_NUM=$(echo "$CURRENT_VERSION" | grep -o '[0-9]*$')
|
|
|
|
if [[ "$CURRENT_NUM" -gt "$HIGHEST_NUM" ]]; then
|
|
NEXT_VERSION="${BASE_VERSION}-beta.$((CURRENT_NUM + 1))"
|
|
echo "lerna.json (${CURRENT_VERSION}) is ahead of highest tag (${HIGHEST_VERSION}); bumping to ${NEXT_VERSION}"
|
|
else
|
|
NEXT_VERSION="${BASE_VERSION}-beta.$((HIGHEST_NUM + 1))"
|
|
echo "Versioning directly to next beta ${NEXT_VERSION}"
|
|
fi
|
|
|
|
version_packages "${NEXT_VERSION}"
|
|
else
|
|
# No existing beta tags, safe to start at .0
|
|
echo "No existing beta tags found, starting at ${BASE_VERSION}-beta.0"
|
|
version_packages "${BASE_VERSION}-beta.0"
|
|
fi
|
|
|
|
elif [[ "${RELEASE_TYPE}" == "latest" ]]; then
|
|
# Manual workflow dispatch for 'latest' should NOT be used for version bumps!
|
|
# Version bumps should ONLY come from GitHub releases with tags
|
|
echo "❌ ERROR: Manual 'latest' releases are not allowed!"
|
|
echo "Production version changes must be done through GitHub releases."
|
|
echo "Please create a GitHub release with the desired version tag instead."
|
|
exit 1
|
|
fi
|
|
|
|
# Get the new version and verify it doesn't already exist on npm.
|
|
# lerna publish from-package silently skips already-published versions,
|
|
# so we must detect collisions here and bump again if needed.
|
|
VERSION=$(node -p "require('./lerna.json').version")
|
|
|
|
if [[ "${RELEASE_TYPE}" == "beta" && "${{ steps.release_type.outputs.is_release_event }}" != "true" ]]; then
|
|
MAX_RETRIES=3
|
|
for i in $(seq 1 $MAX_RETRIES); do
|
|
# Check if this version already exists on npm
|
|
if npm view "@elizaos/core@${VERSION}" version >/dev/null 2>&1; then
|
|
echo "⚠️ Version ${VERSION} already exists on npm — bumping again (attempt ${i}/${MAX_RETRIES})"
|
|
CURRENT_VERSION=$(node -p "require('./lerna.json').version")
|
|
CURRENT_BASE=$(get_base_version "$CURRENT_VERSION")
|
|
CURRENT_NUM=$(echo "$CURRENT_VERSION" | grep -o '[0-9]*$')
|
|
NEXT_VERSION="${CURRENT_BASE}-${RELEASE_TYPE}.$((CURRENT_NUM + 1))"
|
|
version_packages "${NEXT_VERSION}"
|
|
VERSION=$(node -p "require('./lerna.json').version")
|
|
sleep 1 # Brief delay for npm registry consistency
|
|
else
|
|
echo "✅ Version ${VERSION} is available on npm"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Final check — if still colliding after retries, fail loudly
|
|
if npm view "@elizaos/core@${VERSION}" version >/dev/null 2>&1; then
|
|
echo "❌ Version ${VERSION} still exists on npm after ${MAX_RETRIES} bumps — aborting"
|
|
exit 1
|
|
fi
|
|
elif [[ "${RELEASE_TYPE}" == "beta" ]]; then
|
|
echo "Tagged beta release uses exact version ${VERSION}; not auto-bumping on npm collision."
|
|
fi
|
|
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
|
|
# Update lockfile after version changes
|
|
- name: Update lockfile
|
|
run: |
|
|
bun install --no-frozen-lockfile --ignore-scripts || true
|
|
|
|
# Commit and push version changes BEFORE building and publishing
|
|
# This ensures git is the source of truth
|
|
- name: Commit version changes
|
|
id: commit
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
RELEASE_TYPE="${{ steps.release_type.outputs.type }}"
|
|
|
|
# Stage all changes
|
|
git add -A
|
|
|
|
# Check if there are changes to commit
|
|
if git diff --staged --quiet; then
|
|
echo "No changes to commit - this might indicate a problem"
|
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Commit with [skip ci] to prevent infinite loop
|
|
git commit -m "chore: release v${VERSION} (${RELEASE_TYPE}) [skip ci]"
|
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
echo "commit_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
|
|
|
# Create and push git tag (only if not from a GitHub release)
|
|
- name: Create git tag
|
|
if: steps.release_type.outputs.is_release_event != 'true' && steps.commit.outputs.has_changes == 'true'
|
|
id: tag
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TAG_NAME="v${VERSION}"
|
|
|
|
# Check if tag already exists
|
|
if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
|
|
echo "❌ Error: Tag ${TAG_NAME} already exists"
|
|
echo "This indicates a version conflict that needs manual resolution"
|
|
exit 1
|
|
fi
|
|
|
|
# Create an annotated tag so `git push --follow-tags` publishes it.
|
|
git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}"
|
|
echo "tag_created=true" >> $GITHUB_OUTPUT
|
|
echo "tag_name=${TAG_NAME}" >> $GITHUB_OUTPUT
|
|
|
|
# Push changes to git (fails the workflow if it can't push)
|
|
- name: Push to git
|
|
if: steps.commit.outputs.has_changes == 'true' && steps.release_type.outputs.is_release_event != 'true'
|
|
run: |
|
|
TAG_NAME="${{ steps.tag.outputs.tag_name }}"
|
|
|
|
# Determine target branch for push
|
|
if [[ "${{ github.event_name }}" == "release" ]]; then
|
|
# For GitHub releases, push to main branch
|
|
TARGET_BRANCH="main"
|
|
echo "Pushing changes to main branch..."
|
|
else
|
|
# For other triggers, push to current branch
|
|
TARGET_BRANCH="${{ github.ref_name }}"
|
|
fi
|
|
|
|
retry_rebase_release_commit() {
|
|
git fetch origin "${TARGET_BRANCH}"
|
|
|
|
# `-X theirs` resolves package.json / lerna.json version conflicts
|
|
# in favor of our release commit (the "theirs" in rebase semantics
|
|
# is the patch being applied). Concurrent unrelated commits to
|
|
# develop that also touched version files would otherwise abort the
|
|
# rebase with a conflict during a queued release.
|
|
# Non-version edits in the same file still merge cleanly via the
|
|
# 3-way recursive strategy.
|
|
if ! git rebase -X theirs "origin/${TARGET_BRANCH}"; then
|
|
# Abort any half-applied rebase before returning so the next
|
|
# retry starts from a clean working tree.
|
|
git rebase --abort 2>/dev/null || true
|
|
echo "❌ Error: Failed to rebase release commit onto origin/${TARGET_BRANCH}"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -n "${TAG_NAME}" ]]; then
|
|
if git ls-remote --exit-code --tags origin "refs/tags/${TAG_NAME}" >/dev/null 2>&1; then
|
|
echo "❌ Error: Tag ${TAG_NAME} already exists on origin after retry fetch"
|
|
return 1
|
|
fi
|
|
|
|
if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
|
|
git tag -d "${TAG_NAME}"
|
|
fi
|
|
|
|
git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}"
|
|
fi
|
|
}
|
|
|
|
push_release_state() {
|
|
git push origin HEAD:${TARGET_BRANCH} --follow-tags
|
|
}
|
|
|
|
PUSHED=false
|
|
MAX_PUSH_ATTEMPTS=3
|
|
for ATTEMPT in $(seq 1 "${MAX_PUSH_ATTEMPTS}"); do
|
|
if push_release_state; then
|
|
PUSHED=true
|
|
break
|
|
fi
|
|
|
|
if [[ "${ATTEMPT}" -eq "${MAX_PUSH_ATTEMPTS}" ]]; then
|
|
break
|
|
fi
|
|
|
|
echo "⚠️ Push attempt ${ATTEMPT}/${MAX_PUSH_ATTEMPTS} failed — rebasing onto origin/${TARGET_BRANCH} and retrying..."
|
|
if ! retry_rebase_release_commit; then
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "${PUSHED}" != "true" ]]; then
|
|
echo "❌ Error: Failed to push to git repository"
|
|
echo "This could be due to:"
|
|
echo " - Protected branch restrictions"
|
|
echo " - Network issues"
|
|
echo " - Permission problems"
|
|
echo " - The target branch changing too quickly to rebase cleanly"
|
|
echo ""
|
|
echo "The version has been updated locally but not published."
|
|
echo "Manual intervention required to resolve the git push issue."
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Successfully pushed version changes and tags to git"
|
|
|
|
# Build packages with correct version numbers
|
|
# Only happens AFTER git operations succeed
|
|
- name: Build connector plugin artifacts
|
|
run: |
|
|
if [ -f "plugins/plugin-whatsapp/package.json" ]; then
|
|
echo "Building @elizaos/plugin-whatsapp artifacts..."
|
|
(cd plugins/plugin-whatsapp && bun run build)
|
|
fi
|
|
|
|
- name: Build packages
|
|
run: |
|
|
echo "Building packages with version v${{ steps.version.outputs.version }}..."
|
|
# Build only the publish-critical package graph so unrelated plugin cycles
|
|
# do not block beta releases.
|
|
RELEASE_BUILD_FILTERS=(
|
|
--filter=@elizaos/agent
|
|
--filter=@elizaos/app-core
|
|
--filter=elizaos
|
|
--filter=@elizaos/plugin-pdf
|
|
--filter=@elizaos/prompts
|
|
--filter=@elizaos/shared
|
|
--filter=@elizaos/skills
|
|
--filter=@elizaos/core
|
|
--filter=@elizaos/ui
|
|
)
|
|
bunx turbo run build --continue "${RELEASE_BUILD_FILTERS[@]}" || \
|
|
echo "Some packages had build errors — checking critical packages..."
|
|
|
|
# Fail fast if any publish-critical package is missing its release artifacts.
|
|
for artifact in \
|
|
packages/agent/dist/package.json \
|
|
packages/app-core/dist/package.json \
|
|
packages/elizaos/dist/index.js \
|
|
packages/prompts/specs/actions/plugins.generated.json \
|
|
packages/shared/dist/package.json \
|
|
packages/skills/dist/index.js \
|
|
packages/core/dist/index.node.js \
|
|
plugins/plugin-whatsapp/dist/index.d.ts \
|
|
packages/ui/dist/package.json; do
|
|
if [ ! -f "${artifact}" ]; then
|
|
echo "❌ Missing publish artifact: ${artifact}"
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "✅ All publish-critical packages built successfully"
|
|
|
|
# elizaOS/eliza#8000: block publishing another CLI that crashes on global
|
|
# install (ERR_PACKAGE_PATH_NOT_EXPORTED). npm pack -> global install under
|
|
# npm and bun, then run the bin under node and bun.
|
|
- name: elizaos global-install smoke (#8000)
|
|
run: |
|
|
bun run --cwd packages/elizaos lint:bin-exports
|
|
bun run --cwd packages/elizaos test:packaged
|
|
|
|
# Replace workspace:* references with actual versions before publishing
|
|
# This is required because Bun workspaces use workspace:* protocol
|
|
# which npm doesn't understand when the packages are published
|
|
- name: Replace workspace references
|
|
id: replace_workspace
|
|
run: |
|
|
echo "🔄 Replacing workspace:* references with actual versions..."
|
|
node packages/scripts/replace-workspace-versions.js
|
|
echo "✅ Workspace references replaced"
|
|
|
|
- name: Build and verify public package dist tarballs
|
|
run: |
|
|
node packages/scripts/verify-npm-pack-dist.mjs --all-public-dist-packages --build
|
|
|
|
# Publish to NPM (only after git operations succeed)
|
|
- name: Skip NPM publish on forks
|
|
if: github.repository != 'elizaOS/eliza'
|
|
run: |
|
|
echo "Skipping npm publish outside elizaOS/eliza."
|
|
echo "Fork workflows still validate the release build, but only the canonical upstream publishes packages."
|
|
|
|
- name: Publish to NPM
|
|
if: github.repository == 'elizaOS/eliza'
|
|
id: publish
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
DIST_TAG="${{ steps.release_type.outputs.dist_tag }}"
|
|
|
|
# Configure npm for authentication
|
|
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > ~/.npmrc
|
|
# Ensure new scoped /* packages publish as public (lerna reads
|
|
# publishConfig.access per-package; the npmrc access key is ignored).
|
|
node scripts/release-set-public-access.mjs
|
|
|
|
# Publish with appropriate dist-tag
|
|
# Commit workspace reference changes so lerna doesn't complain about uncommitted files
|
|
git add -A
|
|
git diff --staged --quiet || git commit -m "chore: replace workspace references for publishing [skip ci]"
|
|
|
|
# --no-sort: the workspace contains a known dependency cycle
|
|
# (@elizaos/agent <-> @elizaos/plugin-capacitor-bridge) which trips a
|
|
# bug in lerna 9.0.7's topological queue. The cycle is reported as a
|
|
# warning ("ECYCLE Dependency cycles detected"), and lerna then
|
|
# aborts the pack/publish step partway through with the generic
|
|
# "Not all tasks were run. This is likely a bug in Lerna." error.
|
|
# Disabling the topological sort makes lerna iterate via p-map over
|
|
# the full publish set, bypassing the broken queue. Combined with
|
|
# --concurrency 1 this still produces a serial, deterministic order.
|
|
#
|
|
# --concurrency 1 + --loglevel verbose: serialize publishes so a
|
|
# single failure is not masked by parallel output.
|
|
if ! bunx lerna publish from-package \
|
|
--dist-tag ${DIST_TAG} \
|
|
--force-publish \
|
|
--yes \
|
|
--no-verify-access \
|
|
--no-git-reset \
|
|
--no-sort \
|
|
--concurrency 1 \
|
|
--loglevel verbose; then
|
|
|
|
echo "❌ Error: Failed to publish to NPM"
|
|
echo ""
|
|
echo "Git has been updated with version v${{ steps.version.outputs.version }}"
|
|
echo "but the packages were not published to NPM."
|
|
echo ""
|
|
echo "To recover:"
|
|
echo " 1. Fix the NPM publishing issue"
|
|
echo " 2. Run 'npm run release:${DIST_TAG}' locally with proper credentials"
|
|
echo " 3. Or re-run this workflow"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Successfully published to NPM with dist-tag: ${DIST_TAG}"
|
|
|
|
# Verify the dist-tag actually points to the new version.
|
|
# lerna publish silently skips already-published versions, so the
|
|
# dist-tag may not have moved even though lerna reported success.
|
|
- name: Verify dist-tag
|
|
if: steps.publish.outcome == 'success'
|
|
env:
|
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
DIST_TAG="${{ steps.release_type.outputs.dist_tag }}"
|
|
INITIAL_ATTEMPTS=6
|
|
FIX_ATTEMPTS=12
|
|
SLEEP_SECONDS=10
|
|
|
|
echo "Verifying dist-tag '${DIST_TAG}' points to ${VERSION}..."
|
|
|
|
get_actual_tag() {
|
|
npm view "@elizaos/core@${DIST_TAG}" version 2>/dev/null || echo "unknown"
|
|
}
|
|
|
|
wait_for_dist_tag() {
|
|
local expected="$1"
|
|
local attempts="$2"
|
|
local phase="$3"
|
|
local actual=""
|
|
|
|
for attempt in $(seq 1 "${attempts}"); do
|
|
actual=$(get_actual_tag)
|
|
|
|
if [[ "$actual" == "$expected" ]]; then
|
|
echo "✅ dist-tag '${DIST_TAG}' points to ${expected} during ${phase} check (attempt ${attempt}/${attempts})"
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$attempt" -lt "$attempts" ]]; then
|
|
echo "⏳ dist-tag '${DIST_TAG}' currently points to ${actual}; waiting ${SLEEP_SECONDS}s for ${phase} propagation (${attempt}/${attempts})..."
|
|
sleep "${SLEEP_SECONDS}"
|
|
fi
|
|
done
|
|
|
|
echo "⚠️ dist-tag '${DIST_TAG}' still points to ${actual} after ${phase} check"
|
|
return 1
|
|
}
|
|
|
|
if wait_for_dist_tag "${VERSION}" "${INITIAL_ATTEMPTS}" "initial"; then
|
|
exit 0
|
|
fi
|
|
|
|
ACTUAL=$(get_actual_tag)
|
|
echo "⚠️ dist-tag '${DIST_TAG}' points to ${ACTUAL}, expected ${VERSION}"
|
|
echo "Forcing dist-tag update for all published packages..."
|
|
|
|
# Get list of public packages from lerna, plus @elizaos/core
|
|
# which is published from packages/core but may not appear
|
|
# in lerna ls if the workspace config changed.
|
|
PACKAGES=$(bunx lerna ls --json --no-private 2>/dev/null | node -e "
|
|
const pkgs = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
const names = new Set(pkgs.map(p => p.name));
|
|
names.add('@elizaos/core');
|
|
names.forEach(n => console.log(n));
|
|
")
|
|
|
|
FIXED=0
|
|
FAILED=0
|
|
PENDING=0
|
|
for PKG in $PACKAGES; do
|
|
if npm view "${PKG}@${VERSION}" version >/dev/null 2>&1; then
|
|
if npm dist-tag add "${PKG}@${VERSION}" "${DIST_TAG}" 2>/dev/null; then
|
|
echo " ✅ Updated dist-tag for ${PKG}"
|
|
FIXED=$((FIXED + 1))
|
|
else
|
|
echo " ⚠️ Failed to update dist-tag for ${PKG}"
|
|
FAILED=$((FAILED + 1))
|
|
fi
|
|
else
|
|
echo " ⏳ ${PKG}@${VERSION} is not visible on npm yet"
|
|
PENDING=$((PENDING + 1))
|
|
fi
|
|
done
|
|
|
|
echo "✅ Dist-tag update attempts: ${FIXED} updated, ${FAILED} failed, ${PENDING} pending visibility"
|
|
|
|
if ! wait_for_dist_tag "${VERSION}" "${FIX_ATTEMPTS}" "post-fix"; then
|
|
echo "❌ dist-tag still not pointing to ${VERSION} after fix attempt"
|
|
exit 1
|
|
fi
|
|
|
|
# Always restore workspace:* references after publish (success or failure)
|
|
# This keeps the repository clean for development
|
|
- name: Restore workspace references
|
|
if: always() && steps.replace_workspace.outcome == 'success'
|
|
run: |
|
|
echo "🔄 Restoring workspace:* references..."
|
|
node packages/scripts/restore-workspace-refs.js
|
|
echo "✅ Workspace references restored"
|
|
|
|
# Create GitHub Release for beta (not for production, as it already exists)
|
|
- name: Create GitHub release
|
|
if: github.event_name != 'release' && steps.release_type.outputs.type != 'latest' && steps.tag.outputs.tag_created == 'true'
|
|
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
tag_name: ${{ steps.tag.outputs.tag_name }}
|
|
name: ${{ steps.tag.outputs.tag_name }}
|
|
body: |
|
|
🔵 Beta Release
|
|
|
|
**Version:** `${{ steps.tag.outputs.tag_name }}`
|
|
**Channel:** `${{ steps.release_type.outputs.dist_tag }}`
|
|
|
|
### Quick Start
|
|
|
|
Install the CLI globally to get started:
|
|
```bash
|
|
bun i -g elizaos@${{ steps.release_type.outputs.dist_tag }}
|
|
```
|
|
|
|
Or add packages to your project:
|
|
```bash
|
|
bun add @elizaos/core@${{ steps.release_type.outputs.dist_tag }}
|
|
```
|
|
|
|
---
|
|
|
|
> **Note:** This is a ${{ steps.release_type.outputs.type }} release. Production releases use the `latest` tag and are triggered by GitHub releases on tags matching `v*.*.*`.
|
|
draft: false
|
|
prerelease: true
|
|
|
|
- name: Summary
|
|
if: always()
|
|
run: |
|
|
echo "# 📦 Release Summary" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Version**: v${{ steps.version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Type**: ${{ steps.release_type.outputs.type }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Dist Tag**: ${{ steps.release_type.outputs.dist_tag }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Trigger**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
|
if [[ "${{ steps.commit.outputs.has_changes }}" == "true" ]]; then
|
|
echo "- **Commit SHA**: ${{ steps.commit.outputs.commit_sha }}" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
if [[ "${{ steps.tag.outputs.tag_created }}" == "true" ]]; then
|
|
echo "- **Tag**: ${{ steps.tag.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "## Quick Start" >> $GITHUB_STEP_SUMMARY
|
|
echo "Install the CLI globally:" >> $GITHUB_STEP_SUMMARY
|
|
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
|
echo "bun i -g elizaos@${{ steps.release_type.outputs.dist_tag }}" >> $GITHUB_STEP_SUMMARY
|
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "Or add to your project:" >> $GITHUB_STEP_SUMMARY
|
|
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
|
echo "bun add @elizaos/core@${{ steps.release_type.outputs.dist_tag }}" >> $GITHUB_STEP_SUMMARY
|
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
|
|
# Sync version back to develop after production release
|
|
- name: Sync version to develop branch
|
|
if: github.event_name == 'release' && success()
|
|
continue-on-error: true # Don't fail the release if sync fails
|
|
run: |
|
|
echo "📤 Syncing production release back to develop branch..."
|
|
|
|
# Get the released version
|
|
RELEASED_VERSION="${{ steps.version.outputs.version }}"
|
|
BASE_VERSION=$(echo "$RELEASED_VERSION" | sed 's/-.*$//')
|
|
|
|
echo "Released version: ${RELEASED_VERSION}"
|
|
echo "Base version: ${BASE_VERSION}"
|
|
|
|
# Fetch latest develop
|
|
git fetch origin develop:refs/remotes/origin/develop || {
|
|
echo "⚠️ Could not fetch develop branch, skipping sync"
|
|
exit 0
|
|
}
|
|
|
|
# Get current develop version
|
|
git checkout origin/develop -- lerna.json 2>/dev/null || true
|
|
DEVELOP_VERSION=$(node -p "require('./lerna.json').version" 2>/dev/null || echo "unknown")
|
|
|
|
# Restore our lerna.json
|
|
git checkout HEAD -- lerna.json
|
|
|
|
echo "Current develop version: ${DEVELOP_VERSION}"
|
|
|
|
# Extract base versions for comparison
|
|
DEVELOP_BASE=$(echo "$DEVELOP_VERSION" | sed 's/-.*$//')
|
|
|
|
# Compare versions and auto-advance if needed
|
|
if [[ "$DEVELOP_BASE" < "$BASE_VERSION" ]]; then
|
|
# Develop is behind - update it to match production with beta suffix
|
|
NEXT_BETA="${BASE_VERSION}-beta.0"
|
|
|
|
echo "Develop is behind: ${DEVELOP_VERSION} → ${NEXT_BETA}"
|
|
|
|
# Create a branch for the sync (using release/ prefix for lerna)
|
|
git checkout -b release/sync-develop-${BASE_VERSION} origin/develop
|
|
|
|
# Update version to match production base with beta suffix
|
|
bunx lerna version ${NEXT_BETA} \
|
|
--force-publish \
|
|
--yes \
|
|
--no-private \
|
|
--no-git-tag-version \
|
|
--no-push \
|
|
--allow-branch release/*
|
|
|
|
# Update lockfile (|| true: tolerate broken postinstall scripts)
|
|
bun install --no-frozen-lockfile || true
|
|
|
|
# Commit
|
|
git add -A
|
|
git commit -m "chore: sync to v${NEXT_BETA} after v${BASE_VERSION} release [skip ci]" \
|
|
-m "Automated version sync from production release"
|
|
|
|
# Push to develop
|
|
if git push origin HEAD:develop; then
|
|
echo "✅ Successfully synced develop to ${NEXT_BETA}"
|
|
else
|
|
echo "⚠️ Could not push to develop (may be protected or already updated)"
|
|
fi
|
|
elif [[ "$DEVELOP_BASE" == "$BASE_VERSION" ]]; then
|
|
# Develop is on same base as release - auto-advance to next patch
|
|
echo "Develop matches release base, auto-advancing to next patch version..."
|
|
|
|
# Calculate next patch version
|
|
IFS='.' read -r major minor patch <<< "$BASE_VERSION"
|
|
NEXT_PATCH="${major}.${minor}.$((patch + 1))"
|
|
NEXT_BETA="${NEXT_PATCH}-beta.0"
|
|
|
|
echo "Auto-advancing: ${DEVELOP_VERSION} → ${NEXT_BETA}"
|
|
|
|
# Create a branch for the sync (using release/ prefix for lerna)
|
|
git checkout -b release/sync-develop-${BASE_VERSION} origin/develop
|
|
|
|
# Update version to next patch with beta suffix
|
|
bunx lerna version ${NEXT_BETA} \
|
|
--force-publish \
|
|
--yes \
|
|
--no-private \
|
|
--no-git-tag-version \
|
|
--no-push \
|
|
--allow-branch release/*
|
|
|
|
# Update lockfile (|| true: tolerate broken postinstall scripts)
|
|
bun install --no-frozen-lockfile || true
|
|
|
|
# Commit
|
|
git add -A
|
|
git commit -m "chore: bump to v${NEXT_BETA} after v${BASE_VERSION} release [skip ci]" \
|
|
-m "Automated patch version bump from production release"
|
|
|
|
# Push to develop
|
|
if git push origin HEAD:develop; then
|
|
echo "✅ Successfully auto-advanced develop to ${NEXT_BETA}"
|
|
else
|
|
echo "⚠️ Could not push to develop (may be protected or already updated)"
|
|
fi
|
|
else
|
|
echo "✅ Develop (${DEVELOP_VERSION}) is already ahead of release (${RELEASED_VERSION})"
|
|
# Develop is ahead - this is fine, means a new version is being worked on
|
|
# Don't touch it - developer has manually set the next version
|
|
fi
|
|
|
|
# Also sync main branch to match production release
|
|
echo "🔄 Syncing main branch to production release..."
|
|
|
|
# Fetch latest main
|
|
git fetch origin main:refs/remotes/origin/main || {
|
|
echo "⚠️ Could not fetch main branch, skipping main sync"
|
|
exit 0
|
|
}
|
|
|
|
# Get current main version
|
|
git checkout origin/main -- lerna.json 2>/dev/null || true
|
|
MAIN_VERSION=$(node -p "require('./lerna.json').version" 2>/dev/null || echo "unknown")
|
|
|
|
# Restore our lerna.json
|
|
git checkout HEAD -- lerna.json
|
|
|
|
echo "Current main version: ${MAIN_VERSION}"
|
|
|
|
MAIN_BASE=$(echo "$MAIN_VERSION" | sed 's/-.*$//')
|
|
|
|
# Main should follow develop's base version
|
|
# First, get the new develop version that was just set
|
|
git fetch origin develop:refs/remotes/origin/develop || {
|
|
echo "⚠️ Could not fetch updated develop branch"
|
|
NEW_DEVELOP_BASE="$BASE_VERSION"
|
|
}
|
|
|
|
if [[ -z "${NEW_DEVELOP_BASE}" ]]; then
|
|
git checkout origin/develop -- lerna.json 2>/dev/null || true
|
|
NEW_DEVELOP_VERSION=$(node -p "require('./lerna.json').version" 2>/dev/null || echo "${BASE_VERSION}-beta.0")
|
|
NEW_DEVELOP_BASE=$(echo "$NEW_DEVELOP_VERSION" | sed 's/-.*$//')
|
|
git checkout HEAD -- lerna.json
|
|
fi
|
|
|
|
echo "New develop base: ${NEW_DEVELOP_BASE}"
|
|
echo "Current main base: ${MAIN_BASE}"
|
|
|
|
# Main should match develop's base version
|
|
if [[ "$MAIN_BASE" != "$NEW_DEVELOP_BASE" ]]; then
|
|
NEXT_BETA="${NEW_DEVELOP_BASE}-beta.0"
|
|
|
|
echo "Updating main to match develop base: ${MAIN_VERSION} → ${NEXT_BETA}"
|
|
|
|
# Create a branch for the sync (using release/ prefix for lerna)
|
|
git checkout -b release/sync-main-${BASE_VERSION} origin/main
|
|
|
|
# Update version
|
|
bunx lerna version ${NEXT_BETA} \
|
|
--force-publish \
|
|
--yes \
|
|
--no-private \
|
|
--no-git-tag-version \
|
|
--no-push \
|
|
--allow-branch release/*
|
|
|
|
# Update lockfile (|| true: tolerate broken postinstall scripts)
|
|
bun install --no-frozen-lockfile || true
|
|
|
|
# Commit
|
|
git add -A
|
|
git commit -m "chore: sync to v${NEXT_BETA} after v${BASE_VERSION} release [skip ci]" \
|
|
-m "Automated version sync from production release (following develop)"
|
|
|
|
# Push to main
|
|
if git push origin HEAD:main; then
|
|
echo "✅ Successfully synced main to ${NEXT_BETA}"
|
|
else
|
|
echo "⚠️ Could not push to main (may be protected or already updated)"
|
|
fi
|
|
else
|
|
echo "✅ Main base version (${MAIN_BASE}) already matches develop base (${NEW_DEVELOP_BASE})"
|
|
fi
|