2026-02-08 23:23:27 -08:00
|
|
|
#!/usr/bin/env bash
|
2026-02-12 00:29:39 -08:00
|
|
|
# deploy.sh — build and publish ironclaw to npm
|
2026-02-08 23:23:27 -08:00
|
|
|
#
|
|
|
|
|
# Versioning convention (mirrors upstream openclaw tags):
|
|
|
|
|
# --upstream <ver> Sync to an upstream release version.
|
|
|
|
|
# If that version is already published, appends .1, .2, …
|
|
|
|
|
# (or -1, -2, … when the base has no prerelease).
|
|
|
|
|
# --bump Increment the local fork suffix on the current version.
|
|
|
|
|
# 2026.2.6-3 → 2026.2.6-3.1
|
|
|
|
|
# 2026.2.6-3.1 → 2026.2.6-3.2
|
|
|
|
|
# 2026.2.7 → 2026.2.7-1
|
|
|
|
|
# (no flag) Publish whatever version is already in package.json.
|
|
|
|
|
#
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
# Flags:
|
|
|
|
|
# --skip-tests Skip running tests before build/publish.
|
|
|
|
|
#
|
2026-02-08 23:23:27 -08:00
|
|
|
# Environment:
|
|
|
|
|
# NPM_TOKEN Required. npm auth token for publishing.
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
2026-02-12 00:29:39 -08:00
|
|
|
PACKAGE_NAME="ironclaw"
|
2026-02-08 23:23:27 -08:00
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
|
|
|
|
|
|
cd "$ROOT_DIR"
|
|
|
|
|
|
|
|
|
|
# ── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
die() { echo "error: $*" >&2; exit 1; }
|
|
|
|
|
|
|
|
|
|
current_version() {
|
|
|
|
|
node -p "require('./package.json').version"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Check whether a specific version is already on the npm registry.
|
|
|
|
|
npm_version_exists() {
|
|
|
|
|
local v="$1"
|
|
|
|
|
npm view "${PACKAGE_NAME}@${v}" version 2>/dev/null | grep -q "${v}" 2>/dev/null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Given a base version, return it if available on npm, otherwise find the
|
|
|
|
|
# next free slot by appending a dot-suffix (.1, .2, …) for versions that
|
|
|
|
|
# already contain a prerelease, or a hyphen-suffix (-1, -2, …) otherwise.
|
|
|
|
|
find_available_version() {
|
|
|
|
|
local base="$1"
|
|
|
|
|
if ! npm_version_exists "$base"; then
|
|
|
|
|
echo "$base"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local n=1
|
|
|
|
|
if [[ "$base" == *-* ]]; then
|
|
|
|
|
# Has prerelease already → append .N
|
|
|
|
|
while npm_version_exists "${base}.${n}"; do
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
done
|
|
|
|
|
echo "${base}.${n}"
|
|
|
|
|
else
|
|
|
|
|
# No prerelease → append -N
|
|
|
|
|
while npm_version_exists "${base}-${n}"; do
|
|
|
|
|
n=$((n + 1))
|
|
|
|
|
done
|
|
|
|
|
echo "${base}-${n}"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Increment the local fork suffix on a version string.
|
|
|
|
|
# 2026.2.6-3 → 2026.2.6-3.1 (upstream prerelease, add .1)
|
|
|
|
|
# 2026.2.6-3.1 → 2026.2.6-3.2 (increment last dot segment)
|
|
|
|
|
# 2026.2.7 → 2026.2.7-1 (no prerelease, add -1)
|
|
|
|
|
# 2026.2.7-1 → 2026.2.7-1.1 (treat -1 as upstream-like, add .1)
|
|
|
|
|
bump_version() {
|
|
|
|
|
local current="$1"
|
|
|
|
|
|
|
|
|
|
# If the prerelease already has a dot (e.g. 3.1 in 2026.2.6-3.1),
|
|
|
|
|
# increment the last numeric segment after the final dot.
|
|
|
|
|
local prerelease="${current#*-}"
|
|
|
|
|
if [[ "$current" == *-* ]] && [[ "$prerelease" == *.* ]]; then
|
|
|
|
|
if [[ "$current" =~ ^(.*\.)([0-9]+)$ ]]; then
|
|
|
|
|
echo "${BASH_REMATCH[1]}$((BASH_REMATCH[2] + 1))"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Has a prerelease but no dot-suffix yet → append .1
|
|
|
|
|
if [[ "$current" == *-* ]]; then
|
|
|
|
|
echo "${current}.1"
|
|
|
|
|
return
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Plain semver with no prerelease → append -1
|
|
|
|
|
echo "${current}-1"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# ── parse args ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
MODE=""
|
|
|
|
|
UPSTREAM_VERSION=""
|
|
|
|
|
DRY_RUN=false
|
|
|
|
|
SKIP_BUILD=false
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
SKIP_TESTS=false
|
2026-02-08 23:23:27 -08:00
|
|
|
|
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
|
|
|
case $1 in
|
|
|
|
|
--upstream)
|
|
|
|
|
MODE="upstream"
|
|
|
|
|
UPSTREAM_VERSION="${2:?--upstream requires a version argument}"
|
|
|
|
|
shift 2
|
|
|
|
|
;;
|
|
|
|
|
--bump)
|
|
|
|
|
MODE="bump"
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
--dry-run)
|
|
|
|
|
DRY_RUN=true
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
--skip-build)
|
|
|
|
|
SKIP_BUILD=true
|
|
|
|
|
shift
|
|
|
|
|
;;
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
--skip-tests)
|
|
|
|
|
SKIP_TESTS=true
|
|
|
|
|
shift
|
|
|
|
|
;;
|
2026-02-08 23:23:27 -08:00
|
|
|
--help|-h)
|
|
|
|
|
sed -n '2,/^[^#]/{ /^#/s/^# \{0,1\}//p; }' "$0"
|
|
|
|
|
exit 0
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
die "unknown argument: $1 (see --help)"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# ── auth ─────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
if [[ -z "${NPM_TOKEN:-}" ]]; then
|
|
|
|
|
die "NPM_TOKEN environment variable is required"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Write a temporary .npmrc for auth (npm_config_ env vars can't encode
|
|
|
|
|
# registry-scoped keys because they contain slashes and colons).
|
|
|
|
|
NPMRC_TEMP="${ROOT_DIR}/.npmrc.deploy"
|
|
|
|
|
trap 'rm -f "$NPMRC_TEMP"' EXIT
|
|
|
|
|
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > "$NPMRC_TEMP"
|
|
|
|
|
NPM_FLAGS=(--userconfig "$NPMRC_TEMP")
|
|
|
|
|
|
|
|
|
|
# ── compute version ─────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
CURRENT="$(current_version)"
|
|
|
|
|
|
|
|
|
|
case "$MODE" in
|
|
|
|
|
upstream)
|
|
|
|
|
VERSION="$(find_available_version "$UPSTREAM_VERSION")"
|
|
|
|
|
echo "upstream sync: $UPSTREAM_VERSION → publishing as $VERSION"
|
|
|
|
|
;;
|
|
|
|
|
bump)
|
|
|
|
|
NEXT="$(bump_version "$CURRENT")"
|
|
|
|
|
VERSION="$(find_available_version "$NEXT")"
|
|
|
|
|
echo "local bump: $CURRENT → $VERSION"
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
if npm_version_exists "$CURRENT"; then
|
|
|
|
|
die "version $CURRENT already exists on npm. Use --bump or --upstream <ver>."
|
|
|
|
|
fi
|
|
|
|
|
VERSION="$CURRENT"
|
|
|
|
|
echo "publishing current version: $VERSION"
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
if [[ "$DRY_RUN" == true ]]; then
|
|
|
|
|
echo "[dry-run] would publish ${PACKAGE_NAME}@${VERSION}"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── set version ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
npm version "$VERSION" --no-git-tag-version --allow-same-version "${NPM_FLAGS[@]}"
|
|
|
|
|
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
# ── pre-flight: tests ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
if [[ "$SKIP_TESTS" != true ]] && [[ "$SKIP_BUILD" != true ]]; then
|
|
|
|
|
echo "running tests..."
|
|
|
|
|
pnpm test
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-08 23:23:27 -08:00
|
|
|
# ── build ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
2026-02-12 20:02:17 -08:00
|
|
|
# The `prepack` script (triggered by `npm publish`) runs the full build chain:
|
|
|
|
|
# pnpm build && pnpm ui:build && pnpm web:build && pnpm web:prepack
|
|
|
|
|
# Running `pnpm build` here is a redundant fail-fast: catch CLI build errors
|
|
|
|
|
# before committing to a publish attempt.
|
2026-02-08 23:23:27 -08:00
|
|
|
if [[ "$SKIP_BUILD" != true ]]; then
|
|
|
|
|
echo "building..."
|
|
|
|
|
pnpm build
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
|
|
|
|
|
echo "building web app (standalone verification)..."
|
|
|
|
|
pnpm web:build
|
2026-02-08 23:23:27 -08:00
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ── publish ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
# Always tag as "latest" — npm skips the latest tag for prerelease versions
|
2026-02-12 00:29:39 -08:00
|
|
|
# by default, but we want `npm i -g ironclaw` to always resolve to
|
2026-02-08 23:23:27 -08:00
|
|
|
# the most recently published version.
|
|
|
|
|
echo "publishing ${PACKAGE_NAME}@${VERSION}..."
|
|
|
|
|
npm publish --access public --tag latest "${NPM_FLAGS[@]}"
|
|
|
|
|
|
2026-02-12 20:02:17 -08:00
|
|
|
# Verify the standalone web app was included in the published package.
|
|
|
|
|
# `prepack` should have built it; if this file is missing, the web UI
|
|
|
|
|
# won't work for users who install globally.
|
|
|
|
|
STANDALONE_SERVER="apps/web/.next/standalone/apps/web/server.js"
|
|
|
|
|
if [[ ! -f "$STANDALONE_SERVER" ]]; then
|
|
|
|
|
echo "warning: standalone web app build not found after publish ($STANDALONE_SERVER)"
|
|
|
|
|
echo " users may not get a working Web UI — check the prepack step"
|
|
|
|
|
fi
|
|
|
|
|
|
test: add comprehensive workspace test suite and deploy pre-flight checks
- Profile management: discoverProfiles, getEffectiveProfile precedence,
setUIActiveProfile, resolveWebChatDir, workspace registry (32 tests)
- Workspace init API: creation, bootstrap seeding, custom paths,
validation, idempotency (13 tests)
- Profile switch API: GET/POST profiles, validation, default reset (10 tests)
- Chat isolation: profile-scoped chat dirs, session isolation (7 tests)
- LLM context awareness: bootstrap loading, subagent filtering,
resolveBootstrapContextForRun content isolation (15 unit + 5 live)
- Subagent streaming: registerSubagent, event replay, persistence,
ensureRegisteredFromDisk, fan-out (24 unit + 5 live)
- deploy.sh: add --skip-tests flag, pnpm test + web:build pre-flight,
auto git commit/push of version bump after publish
- package.json: add test:workspace and test:workspace:live scripts
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:37:59 -08:00
|
|
|
# ── post-publish: commit + push version bump ─────────────────────────────────
|
|
|
|
|
|
|
|
|
|
if git diff --quiet package.json 2>/dev/null; then
|
|
|
|
|
echo "package.json unchanged — skipping git commit"
|
|
|
|
|
else
|
|
|
|
|
echo "committing version bump..."
|
|
|
|
|
git add package.json
|
|
|
|
|
git commit -m "release: v${VERSION}"
|
|
|
|
|
git push
|
|
|
|
|
fi
|
|
|
|
|
|
2026-02-08 23:23:27 -08:00
|
|
|
echo ""
|
|
|
|
|
echo "published ${PACKAGE_NAME}@${VERSION}"
|
|
|
|
|
echo "install: npm i -g ${PACKAGE_NAME}"
|