brainy/scripts/release.sh
David Snelling 415e824a1d
All checks were successful
CI / Node 22 (push) Successful in 2m57s
CI / Node 24 (push) Successful in 2m51s
CI / Bun (latest) (push) Successful in 3m2s
chore: the forge is the address — retire the archived mirror from every live surface
Ruled today: the project's one public home is source.soulcraft.com. The
old public repo is archived history and no longer part of any release.

- package.json repository/homepage/bugs now point at the forge (this is
  what the npm page links as Repository/Homepage/Issues)
- README CI badge reads the forge pipeline; CONTRIBUTING drops the
  mirror paragraph (forge account or email patch were already the ruled
  contribution paths)
- release.sh: mirror push + external release step removed; publishes go
  forge-first (box-held write token, temp userconfig so the token never
  hits argv; a forge-publish failure aborts before the storefront so the
  pair can never diverge), then npmjs with the scope-override pin (the
  fleet npmrc maps @soulcraft to the forge and scope mappings beat
  --registry); release page created via forge API when a token is
  present, loud skip otherwise; changelog compare links point home
- dead external CI workflow removed (.forgejo/workflows/ci.yml is the
  live pipeline)

Historical CHANGELOG links to the archive stay as written - history is
history and the archive serves them read-only.
2026-07-23 11:09:43 -07:00

249 lines
9 KiB
Bash
Executable file
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -e # Exit on error
# Brainy Release Script
# Simple, reliable release workflow: build → test → commit → push → publish → release
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Parse arguments
RELEASE_TYPE="${1:-patch}" # patch, minor, or major
SKIP_TESTS=false
DRY_RUN=false
for arg in "$@"; do
case $arg in
--skip-tests)
SKIP_TESTS=true
;;
--dry-run)
DRY_RUN=true
;;
esac
done
# An explicit version (e.g. "8.0.0-rc.1") may be passed in place of patch/minor/major.
EXPLICIT_VERSION=""
if [[ "$RELEASE_TYPE" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
EXPLICIT_VERSION="$RELEASE_TYPE"
fi
# Release on whatever branch we're on — RC lines live on feature branches, not main.
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
echo -e "${BLUE}🚀 Brainy Release Script${NC}"
echo -e "${BLUE}Release type: ${RELEASE_TYPE}${NC}"
echo -e "${BLUE}Branch: ${CURRENT_BRANCH}${NC}\n"
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}⚠️ DRY RUN MODE - No changes will be made${NC}\n"
fi
if [ "$SKIP_TESTS" = true ]; then
echo -e "${YELLOW}⚠️ SKIPPING TESTS - Use with caution!${NC}\n"
fi
# Step 1: Verify clean git state
echo -e "${BLUE}1⃣ Checking git status...${NC}"
if [ -n "$(git status --porcelain)" ]; then
echo -e "${RED}❌ Working directory not clean. Commit or stash changes first.${NC}"
exit 1
fi
echo -e "${GREEN}✅ Working directory clean${NC}\n"
# Step 2: Build
echo -e "${BLUE}2⃣ Building project...${NC}"
if [ "$DRY_RUN" = false ]; then
npm run build
fi
echo -e "${GREEN}✅ Build successful${NC}\n"
# Step 3: Test
if [ "$SKIP_TESTS" = false ]; then
echo -e "${BLUE}3⃣ Running tests...${NC}"
if [ "$DRY_RUN" = false ]; then
# Full CI gate: unit + integration (test:ci = test:ci-unit && test:ci-integration).
npm run test:ci
fi
echo -e "${GREEN}✅ Tests passed${NC}\n"
else
echo -e "${YELLOW}3⃣ Skipping tests...${NC}\n"
fi
# Step 4: Get current and new version
CURRENT_VERSION=$(node -p "require('./package.json').version")
echo -e "${BLUE}Current version: ${CURRENT_VERSION}${NC}"
# Calculate new version
IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR="${VERSION_PARTS[0]}"
MINOR="${VERSION_PARTS[1]}"
PATCH="${VERSION_PARTS[2]}"
if [ -n "$EXPLICIT_VERSION" ]; then
NEW_VERSION="$EXPLICIT_VERSION"
else
case $RELEASE_TYPE in
major)
NEW_VERSION="$((MAJOR + 1)).0.0"
;;
minor)
NEW_VERSION="${MAJOR}.$((MINOR + 1)).0"
;;
patch)
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
;;
*)
echo -e "${RED}❌ Invalid release type: ${RELEASE_TYPE}${NC}"
echo "Usage: ./scripts/release.sh [patch|minor|major|<explicit-version>] [--dry-run]"
exit 1
;;
esac
fi
# A version with a hyphen (e.g. 8.0.0-rc.1) is a prerelease: publish under the 'rc'
# dist-tag (NOT 'latest') and mark the GitHub release as a prerelease.
PRERELEASE=false
NPM_TAG="latest"
if [[ "$NEW_VERSION" == *"-"* ]]; then
PRERELEASE=true
NPM_TAG="rc"
fi
echo -e "${BLUE}New version: ${NEW_VERSION}${NC}"
if [ "$PRERELEASE" = true ]; then
echo -e "${YELLOW}⚠️ Prerelease → npm dist-tag '${NPM_TAG}', GitHub prerelease${NC}"
fi
echo ""
if [ "$DRY_RUN" = true ]; then
echo -e "${YELLOW}DRY RUN: Would release version ${NEW_VERSION}${NC}"
exit 0
fi
# Step 5: Bump version in package files
echo -e "${BLUE}4⃣ Bumping version to ${NEW_VERSION}...${NC}"
npm version $NEW_VERSION --no-git-tag-version
echo -e "${GREEN}✅ Version bumped${NC}\n"
# Step 6: Update CHANGELOG
echo -e "${BLUE}5⃣ Updating CHANGELOG...${NC}"
# Get commits since last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
COMMITS=$(git log --oneline --pretty=format:"- %s (%h)")
else
COMMITS=$(git log ${LAST_TAG}..HEAD --oneline --pretty=format:"- %s (%h)")
fi
# Create new changelog entry
CHANGELOG_ENTRY="### [${NEW_VERSION}](https://source.soulcraft.com/soulcraft/brainy/compare/v${CURRENT_VERSION}...v${NEW_VERSION}) ($(date +%Y-%m-%d))
${COMMITS}
"
# Prepend to CHANGELOG.md after header
if [ -f "CHANGELOG.md" ]; then
# Read header (first 4 lines)
HEADER=$(head -n 4 CHANGELOG.md)
# Read rest of file
REST=$(tail -n +5 CHANGELOG.md)
# Write new CHANGELOG
echo "$HEADER" > CHANGELOG.md
echo "" >> CHANGELOG.md
echo "$CHANGELOG_ENTRY" >> CHANGELOG.md
echo "" >> CHANGELOG.md
echo "$REST" >> CHANGELOG.md
fi
echo -e "${GREEN}✅ CHANGELOG updated${NC}\n"
# Step 7: Create release commit
echo -e "${BLUE}6⃣ Creating release commit...${NC}"
git add package.json package-lock.json CHANGELOG.md
git commit -m "chore(release): ${NEW_VERSION}"
echo -e "${GREEN}✅ Release commit created${NC}\n"
# Step 8: Create git tag
# Annotated (-a) so `git push --follow-tags` below actually pushes it. A lightweight tag is
# skipped by --follow-tags, which leaves the tag local-only and makes `gh release create` fail.
echo -e "${BLUE}7⃣ Creating git tag v${NEW_VERSION}...${NC}"
git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}"
echo -e "${GREEN}✅ Tag created${NC}\n"
# Step 9: Push to origin — the forge is the one home (ruled 2026-07-23; the
# old public GitHub repo is archived history, no longer part of any release).
echo -e "${BLUE}8⃣ Pushing to origin...${NC}"
git push --follow-tags origin "$CURRENT_BRANCH"
echo -e "${GREEN}✅ Pushed to origin${NC}\n"
# Step 10: Publish — forge FIRST (home), npmjs second (the world's storefront).
# The fleet-wide ~/.npmrc maps the @soulcraft scope to the forge registry, and
# a scope mapping BEATS `--registry` on the command line — so each publish
# names its registry via the scope override explicitly. Nothing implicit.
FORGE_NPM_REG="https://source.soulcraft.com/api/packages/soulcraft/npm/"
FORGE_NPM_TOKEN_FILE="$HOME/.config/soulcraft/npm-publish-brainy.token"
echo -e "${BLUE}9⃣ Publishing to the forge registry (home)...${NC}"
if [ -f "$FORGE_NPM_TOKEN_FILE" ]; then
TMPRC="$(mktemp)"
chmod 600 "$TMPRC"
{
echo "@soulcraft:registry=${FORGE_NPM_REG}"
echo "//source.soulcraft.com/api/packages/soulcraft/npm/:_authToken=$(cat "$FORGE_NPM_TOKEN_FILE")"
} > "$TMPRC"
if npm publish --tag "$NPM_TAG" --userconfig "$TMPRC"; then
echo -e "${GREEN}✅ Published to the forge${NC}\n"
else
rm -f "$TMPRC"
echo -e "${RED}❌ Forge publish FAILED — aborting before npmjs so the pair never diverges. Fix and re-run.${NC}"
exit 1
fi
rm -f "$TMPRC"
else
echo -e "${RED}❌ Forge publish token missing (${FORGE_NPM_TOKEN_FILE}) — aborting. The forge is home; publish it first or restage the token.${NC}"
exit 1
fi
echo -e "${BLUE}9⃣½ Publishing to npmjs (storefront, dist-tag: ${NPM_TAG})...${NC}"
npm publish --tag "$NPM_TAG" "--@soulcraft:registry=https://registry.npmjs.org/"
# Brainy is the only PUBLIC @soulcraft package — verify visibility after every publish.
npm access get status @soulcraft/brainy "--@soulcraft:registry=https://registry.npmjs.org/" || true
echo -e "${GREEN}✅ Published to npmjs${NC}\n"
# Step 11: Release object on the forge (presentational — the tag, CHANGELOG,
# and RELEASES.md are the record; this just gives the forge UI a release page).
echo -e "${BLUE}🔟 Creating forge release...${NC}"
if [ -n "${FORGEJO_RELEASE_TOKEN:-}" ]; then
if curl -sf -X POST "https://source.soulcraft.com/api/v1/repos/soulcraft/brainy/releases" \
-H "Authorization: token ${FORGEJO_RELEASE_TOKEN}" -H "Content-Type: application/json" \
-d "{\"tag_name\":\"v${NEW_VERSION}\",\"name\":\"v${NEW_VERSION}\",\"prerelease\":${PRERELEASE}}" >/dev/null; then
echo -e "${GREEN}✅ Forge release created${NC}\n"
else
echo -e "${RED}⚠️ Forge release API call failed — tag + CHANGELOG remain the record; create the release page via the forge UI if wanted${NC}\n"
fi
else
echo -e "${RED}⚠️ FORGEJO_RELEASE_TOKEN unset — no release page created; tag + CHANGELOG remain the record${NC}\n"
fi
# Step 12: Push public docs to the soulcraft.com docs ingest door
# (VENUE-DOCS-RELEASE-PUSH). Skips with a loud warning when
# DOCS_INGEST_SECRET is unset; fails loudly (without undoing the publish —
# that already happened) when a push errors, so the docs site never
# silently trails npm.
echo -e "${BLUE}1⃣2⃣ Pushing public docs to soulcraft.com/docs...${NC}"
if node scripts/push-docs.js; then
echo -e "${GREEN}✅ Docs push step done${NC}\n"
else
echo -e "${RED}❌ Docs push FAILED — soulcraft.com/docs trails npm until re-run or interim sync${NC}\n"
fi
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}🎉 Release ${NEW_VERSION} complete!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "📦 npm: ${BLUE}https://www.npmjs.com/package/@soulcraft/brainy/v/${NEW_VERSION}${NC}"
echo -e "🏠 Forge: ${BLUE}https://source.soulcraft.com/soulcraft/brainy/releases/tag/v${NEW_VERSION}${NC}"