brainy/scripts/release.sh
David Snelling d02e522a3e feat(8.0): id-normalization (#18) + aggregation min/max delete-safety + RC-safe release
- id-normalization (#18): `brain.newId()` (UUID v7) + v7 default ids; non-UUID string ids are
  transparently normalized to a stable UUID v5 on creation AND every lookup — get/update/remove,
  relate(from,to), related, find({connected}), getMany, removeMany, the transact op-handler, and
  db.with() overlays — with the caller's original key preserved under `_originalId`. The engine only
  ever sees a UUID; real UUIDs pass through untouched. universal/uuid.ts gains v5/v7/isUUID +
  BRAINY_ID_NAMESPACE; new utils/idNormalization.ts (coerceNewEntityId / resolveEntityId).
- aggregation min/max delete-safety: `queryAggregate` no longer leaves a stale min/max after
  deleting the current extreme — min/max now track a value multiset and recompute in-memory (no
  entity scan; that scan was the prior delete-then-hang a consumer reported). Other metrics were
  already exact across deletes. Regression test proves resolve-after-delete + correct new min/max.
- release.sh RC-safe: explicit version arg, prerelease detection (npm `--tag rc` + GitHub
  `--prerelease`), full `test:ci` gate (unit + integration), current-branch push, npm-access
  verification after publish.
- native-engine references point at `@soulcraft/cor` (8.0's partner) in user-facing strings/docs.

Unit 1461/0 · integration 599/0 · tsc clean.
2026-06-20 14:46:40 -07:00

204 lines
6.4 KiB
Bash
Executable file
Raw Permalink 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://github.com/soulcraftlabs/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 GitHub
echo -e "${BLUE}8⃣ Pushing to GitHub...${NC}"
git push --follow-tags origin "$CURRENT_BRANCH"
echo -e "${GREEN}✅ Pushed to GitHub${NC}\n"
# Step 10: Publish to npm
echo -e "${BLUE}9⃣ Publishing to npm (dist-tag: ${NPM_TAG})...${NC}"
npm publish --tag "$NPM_TAG"
# Brainy is the only PUBLIC @soulcraft package — verify visibility after every publish.
npm access get status @soulcraft/brainy || true
echo -e "${GREEN}✅ Published to npm${NC}\n"
# Step 11: Create GitHub release
echo -e "${BLUE}🔟 Creating GitHub release...${NC}"
if [ "$PRERELEASE" = true ]; then
gh release create "v${NEW_VERSION}" --generate-notes --prerelease
else
gh release create "v${NEW_VERSION}" --generate-notes
fi
echo -e "${GREEN}✅ GitHub release created${NC}\n"
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 "🐙 GitHub: ${BLUE}https://github.com/soulcraftlabs/brainy/releases/tag/v${NEW_VERSION}${NC}"