feat(release): add --canary and --promote flags to release.sh
Support two-phase canary release flow: - --canary: publishes packages under @canary npm tag, skips git commit/tag - --promote <version>: moves canary packages to @latest, then commits and tags Both flags work with --dry-run. Existing behavior unchanged when neither flag is passed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,16 @@ set -euo pipefail
|
|||||||
# release.sh — One-command version bump, build, and publish via Changesets.
|
# release.sh — One-command version bump, build, and publish via Changesets.
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/release.sh patch # 0.2.0 → 0.2.1
|
# ./scripts/release.sh patch # 0.2.0 → 0.2.1
|
||||||
# ./scripts/release.sh minor # 0.2.0 → 0.3.0
|
# ./scripts/release.sh minor # 0.2.0 → 0.3.0
|
||||||
# ./scripts/release.sh major # 0.2.0 → 1.0.0
|
# ./scripts/release.sh major # 0.2.0 → 1.0.0
|
||||||
# ./scripts/release.sh patch --dry-run # everything except npm publish
|
# ./scripts/release.sh patch --dry-run # everything except npm publish
|
||||||
|
# ./scripts/release.sh patch --canary # publish under @canary tag, no commit/tag
|
||||||
|
# ./scripts/release.sh patch --canary --dry-run
|
||||||
|
# ./scripts/release.sh --promote 0.2.8 # promote canary to @latest + commit/tag
|
||||||
|
# ./scripts/release.sh --promote 0.2.8 --dry-run
|
||||||
#
|
#
|
||||||
# Steps:
|
# Steps (normal):
|
||||||
# 1. Preflight checks (clean tree, npm login)
|
# 1. Preflight checks (clean tree, npm login)
|
||||||
# 2. Auto-create a changeset for all public packages
|
# 2. Auto-create a changeset for all public packages
|
||||||
# 3. Run changeset version (bumps versions, generates CHANGELOGs)
|
# 3. Run changeset version (bumps versions, generates CHANGELOGs)
|
||||||
@@ -17,6 +21,9 @@ set -euo pipefail
|
|||||||
# 5. Build CLI bundle (esbuild)
|
# 5. Build CLI bundle (esbuild)
|
||||||
# 6. Publish to npm via changeset publish (unless --dry-run)
|
# 6. Publish to npm via changeset publish (unless --dry-run)
|
||||||
# 7. Commit and tag
|
# 7. Commit and tag
|
||||||
|
#
|
||||||
|
# --canary: Steps 1-5 unchanged, Step 6 publishes with --tag canary, Step 7 skipped.
|
||||||
|
# --promote: Skips Steps 1-6, promotes canary to latest, then commits and tags.
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
CLI_DIR="$REPO_ROOT/cli"
|
CLI_DIR="$REPO_ROOT/cli"
|
||||||
@@ -24,23 +31,130 @@ CLI_DIR="$REPO_ROOT/cli"
|
|||||||
# ── Parse args ────────────────────────────────────────────────────────────────
|
# ── Parse args ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
dry_run=false
|
dry_run=false
|
||||||
|
canary=false
|
||||||
|
promote=false
|
||||||
|
promote_version=""
|
||||||
bump_type=""
|
bump_type=""
|
||||||
|
|
||||||
for arg in "$@"; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$arg" in
|
case "$1" in
|
||||||
--dry-run) dry_run=true ;;
|
--dry-run) dry_run=true ;;
|
||||||
*) bump_type="$arg" ;;
|
--canary) canary=true ;;
|
||||||
|
--promote)
|
||||||
|
promote=true
|
||||||
|
shift
|
||||||
|
if [ $# -eq 0 ] || [[ "$1" == --* ]]; then
|
||||||
|
echo "Error: --promote requires a version argument (e.g. --promote 0.2.8)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
promote_version="$1"
|
||||||
|
;;
|
||||||
|
*) bump_type="$1" ;;
|
||||||
esac
|
esac
|
||||||
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$bump_type" ]; then
|
if [ "$promote" = true ] && [ "$canary" = true ]; then
|
||||||
echo "Usage: $0 <patch|minor|major> [--dry-run]"
|
echo "Error: --canary and --promote cannot be used together"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! "$bump_type" =~ ^(patch|minor|major)$ ]]; then
|
if [ "$promote" = false ]; then
|
||||||
echo "Error: bump type must be patch, minor, or major (got '$bump_type')"
|
if [ -z "$bump_type" ]; then
|
||||||
exit 1
|
echo "Usage: $0 <patch|minor|major> [--dry-run] [--canary]"
|
||||||
|
echo " $0 --promote <version> [--dry-run]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! "$bump_type" =~ ^(patch|minor|major)$ ]]; then
|
||||||
|
echo "Error: bump type must be patch, minor, or major (got '$bump_type')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Promote mode (skips Steps 1-6) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
if [ "$promote" = true ]; then
|
||||||
|
NEW_VERSION="$promote_version"
|
||||||
|
echo ""
|
||||||
|
echo "==> Promote mode: promoting v$NEW_VERSION from canary to latest..."
|
||||||
|
|
||||||
|
# Get all publishable package names
|
||||||
|
PACKAGES=$(node -e "
|
||||||
|
const { readFileSync } = require('fs');
|
||||||
|
const { resolve } = require('path');
|
||||||
|
const root = '$REPO_ROOT';
|
||||||
|
const dirs = ['packages/shared', 'packages/adapter-utils', 'packages/db',
|
||||||
|
'packages/adapters/claude-local', 'packages/adapters/codex-local', 'packages/adapters/openclaw',
|
||||||
|
'server', 'cli'];
|
||||||
|
const names = [];
|
||||||
|
for (const d of dirs) {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(resolve(root, d, 'package.json'), 'utf8'));
|
||||||
|
if (!pkg.private) names.push(pkg.name);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
console.log(names.join('\n'));
|
||||||
|
")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo " Promoting packages to @latest:"
|
||||||
|
while IFS= read -r pkg; do
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo " [dry-run] npm dist-tag add ${pkg}@${NEW_VERSION} latest"
|
||||||
|
else
|
||||||
|
npm dist-tag add "${pkg}@${NEW_VERSION}" latest
|
||||||
|
echo " ✓ ${pkg}@${NEW_VERSION} → latest"
|
||||||
|
fi
|
||||||
|
done <<< "$PACKAGES"
|
||||||
|
|
||||||
|
# Restore CLI dev package.json if present
|
||||||
|
if [ -f "$CLI_DIR/package.dev.json" ]; then
|
||||||
|
mv "$CLI_DIR/package.dev.json" "$CLI_DIR/package.json"
|
||||||
|
echo " ✓ Restored workspace dependencies in cli/package.json"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove the README copied for npm publishing
|
||||||
|
if [ -f "$CLI_DIR/README.md" ]; then
|
||||||
|
rm "$CLI_DIR/README.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove temporary build artifacts
|
||||||
|
rm -rf "$REPO_ROOT/server/ui-dist"
|
||||||
|
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
|
||||||
|
rm -rf "$REPO_ROOT/$pkg_dir/skills"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Stage release files, commit, and tag
|
||||||
|
echo ""
|
||||||
|
echo " Committing and tagging v$NEW_VERSION..."
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo " [dry-run] git add + commit + tag v$NEW_VERSION"
|
||||||
|
else
|
||||||
|
git add \
|
||||||
|
.changeset/ \
|
||||||
|
'**/CHANGELOG.md' \
|
||||||
|
'**/package.json' \
|
||||||
|
cli/src/index.ts
|
||||||
|
git commit -m "chore: release v$NEW_VERSION"
|
||||||
|
git tag "v$NEW_VERSION"
|
||||||
|
echo " ✓ Committed and tagged v$NEW_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo "Dry run complete for promote v$NEW_VERSION."
|
||||||
|
echo " - Would promote all packages to @latest"
|
||||||
|
echo " - Would commit and tag v$NEW_VERSION"
|
||||||
|
else
|
||||||
|
echo "Promoted all packages to @latest at v$NEW_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Verify: npm view paperclipai@latest version"
|
||||||
|
echo ""
|
||||||
|
echo "To push:"
|
||||||
|
echo " git push && git push origin v$NEW_VERSION"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 1: Preflight checks ─────────────────────────────────────────────────
|
# ── Step 1: Preflight checks ─────────────────────────────────────────────────
|
||||||
@@ -158,7 +272,11 @@ echo " ✓ CLI bundled"
|
|||||||
|
|
||||||
if [ "$dry_run" = true ]; then
|
if [ "$dry_run" = true ]; then
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Step 6/7: Skipping publish (--dry-run)"
|
if [ "$canary" = true ]; then
|
||||||
|
echo "==> Step 6/7: Skipping publish (--dry-run, --canary)"
|
||||||
|
else
|
||||||
|
echo "==> Step 6/7: Skipping publish (--dry-run)"
|
||||||
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo " Preview what would be published:"
|
echo " Preview what would be published:"
|
||||||
for dir in packages/shared packages/adapter-utils packages/db \
|
for dir in packages/shared packages/adapter-utils packages/db \
|
||||||
@@ -169,18 +287,33 @@ if [ "$dry_run" = true ]; then
|
|||||||
npm pack --dry-run 2>&1 | tail -3
|
npm pack --dry-run 2>&1 | tail -3
|
||||||
done
|
done
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
if [ "$canary" = true ]; then
|
||||||
|
echo ""
|
||||||
|
echo " [dry-run] Would publish with: npx changeset publish --tag canary"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Step 6/7: Publishing to npm..."
|
if [ "$canary" = true ]; then
|
||||||
cd "$REPO_ROOT"
|
echo "==> Step 6/7: Publishing to npm (canary)..."
|
||||||
npx changeset publish
|
cd "$REPO_ROOT"
|
||||||
echo " ✓ Published all packages"
|
npx changeset publish --tag canary
|
||||||
|
echo " ✓ Published all packages under @canary tag"
|
||||||
|
else
|
||||||
|
echo "==> Step 6/7: Publishing to npm..."
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
npx changeset publish
|
||||||
|
echo " ✓ Published all packages"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Step 7: Restore CLI dev package.json and commit ──────────────────────────
|
# ── Step 7: Restore CLI dev package.json and commit ──────────────────────────
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Step 7/7: Restoring dev package.json, committing, and tagging..."
|
if [ "$canary" = true ]; then
|
||||||
|
echo "==> Step 7/7: Skipping commit and tag (canary mode — promote later)..."
|
||||||
|
else
|
||||||
|
echo "==> Step 7/7: Restoring dev package.json, committing, and tagging..."
|
||||||
|
fi
|
||||||
cd "$REPO_ROOT"
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
# Restore the dev package.json (build-npm.sh backs it up)
|
# Restore the dev package.json (build-npm.sh backs it up)
|
||||||
@@ -200,20 +333,39 @@ for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-loc
|
|||||||
rm -rf "$REPO_ROOT/$pkg_dir/skills"
|
rm -rf "$REPO_ROOT/$pkg_dir/skills"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Stage only release-related files (avoid sweeping unrelated changes with -A)
|
if [ "$canary" = false ]; then
|
||||||
git add \
|
# Stage only release-related files (avoid sweeping unrelated changes with -A)
|
||||||
.changeset/ \
|
git add \
|
||||||
'**/CHANGELOG.md' \
|
.changeset/ \
|
||||||
'**/package.json' \
|
'**/CHANGELOG.md' \
|
||||||
cli/src/index.ts
|
'**/package.json' \
|
||||||
git commit -m "chore: release v$NEW_VERSION"
|
cli/src/index.ts
|
||||||
git tag "v$NEW_VERSION"
|
git commit -m "chore: release v$NEW_VERSION"
|
||||||
echo " ✓ Committed and tagged v$NEW_VERSION"
|
git tag "v$NEW_VERSION"
|
||||||
|
echo " ✓ Committed and tagged v$NEW_VERSION"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Done ──────────────────────────────────────────────────────────────────────
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
if [ "$dry_run" = true ]; then
|
if [ "$canary" = true ]; then
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo "Dry run complete for canary v$NEW_VERSION."
|
||||||
|
echo " - Versions bumped, built, and previewed"
|
||||||
|
echo " - Dev package.json restored"
|
||||||
|
echo " - No commit or tag (canary mode)"
|
||||||
|
echo ""
|
||||||
|
echo "To actually publish canary, run:"
|
||||||
|
echo " ./scripts/release.sh $bump_type --canary"
|
||||||
|
else
|
||||||
|
echo "Published canary at v$NEW_VERSION"
|
||||||
|
echo ""
|
||||||
|
echo "Verify: npm view paperclipai@canary version"
|
||||||
|
echo ""
|
||||||
|
echo "To promote to latest:"
|
||||||
|
echo " ./scripts/release.sh --promote $NEW_VERSION"
|
||||||
|
fi
|
||||||
|
elif [ "$dry_run" = true ]; then
|
||||||
echo "Dry run complete for v$NEW_VERSION."
|
echo "Dry run complete for v$NEW_VERSION."
|
||||||
echo " - Versions bumped, built, and previewed"
|
echo " - Versions bumped, built, and previewed"
|
||||||
echo " - Dev package.json restored"
|
echo " - Dev package.json restored"
|
||||||
|
|||||||
Reference in New Issue
Block a user