From 0f64fc87a2c270e82f37c0dd43f8ebf157ece31f Mon Sep 17 00:00:00 2001 From: dillonstreator Date: Mon, 22 Dec 2025 13:21:00 -0600 Subject: [PATCH] release scripts and automations --- .github/workflows/release.yml | 119 +++++++ package.json | 9 +- scripts/RELEASE.md | 299 +++++++++++++++++ scripts/SETUP.md | 317 ++++++++++++++++++ ...gen-changelog.sh => generate-changelog.sh} | 127 +++---- scripts/release.sh | 308 +++++++++++++++++ scripts/validate-release.sh | 66 ++++ 7 files changed, 1162 insertions(+), 83 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/RELEASE.md create mode 100644 scripts/SETUP.md rename scripts/{gen-changelog.sh => generate-changelog.sh} (54%) create mode 100755 scripts/release.sh create mode 100755 scripts/validate-release.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7d7a9ff --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,119 @@ +name: Release + +on: + release: + types: [published] + +permissions: + contents: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Enable Corepack + run: corepack enable + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: '22.x' + cache: 'yarn' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build all packages + run: yarn build + + - name: Run tests + run: yarn test + + # Publish extension to Chrome Web Store + - name: Publish to Chrome Web Store + if: ${{ secrets.CHROME_EXTENSION_ID != '' && secrets.CHROME_CLIENT_ID != '' && secrets.CHROME_CLIENT_SECRET != '' && secrets.CHROME_REFRESH_TOKEN != '' }} + env: + EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + run: | + echo "Publishing extension to Chrome Web Store..." + + # Install chrome-webstore-upload-cli + npm install -g chrome-webstore-upload-cli + + # Create zip of extension + cd packages/extension/dist + zip -r extension.zip . + cd ../../.. + + # Upload to Chrome Web Store + chrome-webstore-upload upload \ + --source packages/extension/dist/extension.zip \ + --extension-id "$EXTENSION_ID" \ + --client-id "$CLIENT_ID" \ + --client-secret "$CLIENT_SECRET" \ + --refresh-token "$REFRESH_TOKEN" \ + --auto-publish + + # Publish packages to npm (if they are not private) + - name: Publish to npm + if: ${{ secrets.NPM_TOKEN != '' }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + echo "Publishing packages to npm..." + + # Publish core package if not private + if [ -d "packages/core" ]; then + cd packages/core + if [ "$(node -p "require('./package.json').private || false")" != "true" ]; then + npm publish --access public --provenance + else + echo "Skipping packages/core (marked as private)" + fi + cd ../.. + fi + + # Publish CLI package if not private + if [ -d "packages/cli" ]; then + cd packages/cli + if [ "$(node -p "require('./package.json').private || false")" != "true" ]; then + npm publish --access public --provenance + else + echo "Skipping packages/cli (marked as private)" + fi + cd ../.. + fi + + # Upload extension build as release asset + - name: Upload extension to release + run: | + cd packages/extension/dist + zip -r ../../../extension-${{ github.ref_name }}.zip . + cd ../../.. + gh release upload ${{ github.ref_name }} extension-${{ github.ref_name }}.zip + env: + GH_TOKEN: ${{ github.token }} + + - name: Release summary + run: | + echo "✅ Release ${{ github.ref_name }} published successfully!" + echo "" + echo "Published artifacts:" + + if [ "${{ secrets.CHROME_EXTENSION_ID }}" != "" ]; then + echo " - Chrome Web Store extension" + fi + + if [ "${{ secrets.NPM_TOKEN }}" != "" ]; then + echo " - npm packages (if public)" + fi + + echo " - Extension zip attached to GitHub release" diff --git a/package.json b/package.json index 268b2b7..d918fb4 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,14 @@ "typecheck": "turbo typecheck", "format": "prettier --write .", "format:check": "prettier --check .", - "redis": "docker compose run -p 6379:6379 -v ./redis-data:/data redis redis-server --save 1 1 --loglevel warning" + "redis": "docker compose run -p 6379:6379 -v ./redis-data:/data redis redis-server --save 1 1 --loglevel warning", + "release": "./scripts/release.sh", + "release:dry-run": "./scripts/release.sh --dry-run", + "release:major": "./scripts/release.sh major", + "release:minor": "./scripts/release.sh minor", + "release:patch": "./scripts/release.sh patch", + "validate": "./scripts/validate-release.sh", + "changelog": "./scripts/generate-changelog.sh" }, "devDependencies": { "@eslint/js": "^9.39.2", diff --git a/scripts/RELEASE.md b/scripts/RELEASE.md new file mode 100644 index 0000000..7c44fc6 --- /dev/null +++ b/scripts/RELEASE.md @@ -0,0 +1,299 @@ +# Release Process + +This document describes the automated release process for crypt.fyi. + +## Overview + +The release process is fully automated through scripts and GitHub Actions: + +1. **Local validation and tagging**: Run the release script locally to validate, version, and create a git tag +2. **Automated publishing**: When the GitHub release is published, GitHub Actions automatically publishes to npm and Chrome Web Store + +## Prerequisites + +### Required Tools + +- **Node.js** 22.x or higher +- **Yarn** 4.6.0 or higher +- **Git** with remote access to the repository +- **GitHub CLI** (`gh`) - for creating GitHub releases (optional but recommended) + - Install: `brew install gh` (macOS) or see https://cli.github.com/ + - Authenticate: `gh auth login` +- **LLM CLI** (optional) - for AI-generated changelogs + - Install: `pip install llm` + - See: https://llm.datasette.io/ + +### Required GitHub Secrets + +Configure these secrets in your GitHub repository settings for automated publishing: + +#### For npm Publishing + +- `NPM_TOKEN` - npm authentication token with publish permissions + - Generate at: https://www.npmjs.com/settings/[username]/tokens + - Use "Automation" token type + +#### For Chrome Web Store Publishing + +- `CHROME_EXTENSION_ID` - Your extension's ID from the Chrome Web Store +- `CHROME_CLIENT_ID` - OAuth2 client ID +- `CHROME_CLIENT_SECRET` - OAuth2 client secret +- `CHROME_REFRESH_TOKEN` - OAuth2 refresh token + +To get Chrome Web Store credentials: +1. Go to https://console.cloud.google.com/ +2. Create a project and enable the Chrome Web Store API +3. Create OAuth2 credentials +4. Use the Chrome Web Store API to get a refresh token + +See: https://developer.chrome.com/docs/webstore/using_webstore_api/ + +## Release Scripts + +### Available Commands + +```bash +# Interactive release (will prompt for version type) +yarn release + +# Non-interactive release with specific version bump +yarn release:major # 1.0.0 -> 2.0.0 +yarn release:minor # 1.0.0 -> 1.1.0 +yarn release:patch # 1.0.0 -> 1.0.1 + +# Dry run (preview what would happen) +yarn release:dry-run + +# Validate without releasing +yarn validate + +# Generate changelog only +yarn changelog +``` + +### Release Process Steps + +When you run `yarn release`, the script will: + +1. **Check git status** - Ensures working directory is clean +2. **Run validation** - Executes format, typecheck, lint, build, and test +3. **Generate changelog** - Creates changelog from commits since last tag +4. **Prompt for version** - Asks you to choose major/minor/patch (unless specified) +5. **Update versions** - Updates all `package.json` and `manifest.json` files +6. **Commit changes** - Commits version updates with message `chore: release vX.Y.Z` +7. **Create tag** - Creates git tag `vX.Y.Z` +8. **Push to remote** - Pushes commits and tags to GitHub +9. **Create draft release** - Creates a draft GitHub release with the changelog + +### What Happens Next + +After the draft release is created: + +1. **Review the release** on GitHub +2. **Edit if needed** - Adjust the changelog, add notes, etc. +3. **Publish the release** - Click "Publish release" button + +When you publish the release, GitHub Actions automatically: + +- Builds all packages +- Runs tests +- Publishes to npm (for public packages) +- Publishes to Chrome Web Store +- Attaches extension zip to the release + +## Manual Steps (if needed) + +### If GitHub CLI is not available + +If `gh` is not installed, the script will skip creating the GitHub release. You can create it manually: + +1. Go to: https://github.com/[org]/[repo]/releases/new +2. Select the tag created by the script (e.g., `v1.2.3`) +3. Paste the changelog as the release description +4. Mark as draft +5. Publish when ready + +### If validation fails + +If any validation step fails: + +```bash +# Run specific checks +yarn format:check # Check code formatting +yarn typecheck # Check TypeScript types +yarn lint # Run linting +yarn build # Build all packages +yarn test # Run tests + +# Fix formatting automatically +yarn format +``` + +### Skip validation (not recommended) + +```bash +./scripts/release.sh --skip-validation +``` + +### Skip git checks (use with caution) + +```bash +./scripts/release.sh --skip-git-check +``` + +## Changelog Generation + +The changelog is automatically generated from git commits since the last tag. + +### With LLM (recommended) + +If you have the `llm` CLI tool installed, the script will use AI to: + +- Categorize commits into sections (Features, Bug Fixes, etc.) +- Generate clear, user-friendly descriptions +- Filter out non-user-facing changes +- Group related changes + +### Without LLM + +Falls back to a simple conventional changelog format listing all commits. + +### Customize LLM model + +```bash +LLM_MODEL=claude-3.5-sonnet yarn changelog +``` + +## Version Synchronization + +All packages in the monorepo share the same version number. When you release: + +- All `packages/*/package.json` files are updated +- `packages/extension/manifest.json` is updated (if exists) +- `packages/web/public/manifest.json` is updated (if exists) + +## Troubleshooting + +### "No previous tags found" + +This is expected for the first release. The script will generate a changelog from all commits. + +### "Git working directory is not clean" + +Commit or stash your changes before releasing: + +```bash +git status +git add . +git commit -m "your changes" +# or +git stash +``` + +### "Failed to generate changelog" + +Check if commits exist since the last tag: + +```bash +git log $(git describe --tags --abbrev=0)..HEAD +``` + +### "gh: command not found" + +Install GitHub CLI: + +```bash +# macOS +brew install gh + +# Or download from https://cli.github.com/ +``` + +### Release workflow not running + +Make sure you **published** the release, not just created a draft. The workflow only runs when a release is published. + +### npm publish fails + +- Check that `NPM_TOKEN` secret is set correctly +- Verify the token has publish permissions +- Check that package names are available on npm +- For scoped packages (e.g., `@crypt.fyi/core`), ensure they're public or you have access + +### Chrome Web Store publish fails + +- Verify all four secrets are set correctly +- Check that the extension ID matches your extension +- Ensure the refresh token hasn't expired +- Test credentials with Chrome Web Store API directly + +## Development Tips + +### Test the release script + +Always test with dry run first: + +```bash +yarn release:dry-run +``` + +### Test locally without pushing + +```bash +# Run validation only +yarn validate + +# Generate changelog only +yarn changelog + +# Release with skip flags +./scripts/release.sh --skip-validation --dry-run +``` + +### Rollback a release + +If you need to rollback: + +```bash +# Delete the tag locally +git tag -d v1.2.3 + +# Delete the tag remotely +git push origin :refs/tags/v1.2.3 + +# Delete the GitHub release +gh release delete v1.2.3 + +# Revert the version commit +git revert HEAD +git push +``` + +## GitHub Actions Workflows + +### CI Workflow (`.github/workflows/ci.yml`) + +Runs on every push and pull request: +- Format check +- Build +- Lint +- Type check +- Test + +### Release Workflow (`.github/workflows/release.yml`) + +Runs when a GitHub release is published: +- Install dependencies +- Build packages +- Run tests +- Publish to Chrome Web Store +- Publish to npm +- Upload extension zip to release + +## Support + +For issues with the release process: +- Check the GitHub Actions logs +- Review script output for errors +- Ensure all prerequisites are met +- Verify secrets are configured correctly diff --git a/scripts/SETUP.md b/scripts/SETUP.md new file mode 100644 index 0000000..d59edf9 --- /dev/null +++ b/scripts/SETUP.md @@ -0,0 +1,317 @@ +# Release System Setup Guide + +This guide will help you configure the automated release system for crypt.fyi. + +## Quick Start + +```bash +# 1. Install GitHub CLI (if not already installed) +brew install gh # macOS +# or download from https://cli.github.com/ + +# 2. Authenticate with GitHub +gh auth login + +# 3. (Optional) Install LLM CLI for AI-generated changelogs +pip install llm + +# 4. Test the release script +yarn release:dry-run + +# 5. Configure GitHub secrets (see below) +``` + +## GitHub Secrets Configuration + +### Step 1: Configure npm Token (for npm publishing) + +1. Go to https://www.npmjs.com/settings/[your-username]/tokens +2. Click "Generate New Token" → "Classic Token" +3. Select "Automation" type +4. Give it a name like "crypt.fyi-release" +5. Copy the token + +Add to GitHub: +```bash +gh secret set NPM_TOKEN +# Paste your token when prompted +``` + +Or via GitHub UI: +1. Go to your repository on GitHub +2. Settings → Secrets and variables → Actions +3. Click "New repository secret" +4. Name: `NPM_TOKEN` +5. Value: [paste your token] + +### Step 2: Configure Chrome Web Store Credentials + +#### Get Chrome Web Store API Credentials + +1. **Create a Google Cloud Project** + - Go to https://console.cloud.google.com/ + - Create a new project (or select existing) + - Note your project ID + +2. **Enable Chrome Web Store API** + - In the Google Cloud Console, go to "APIs & Services" → "Library" + - Search for "Chrome Web Store API" + - Click "Enable" + +3. **Create OAuth2 Credentials** + - Go to "APIs & Services" → "Credentials" + - Click "Create Credentials" → "OAuth client ID" + - Application type: "Web application" + - Name: "crypt.fyi release automation" + - Authorized redirect URIs: `http://localhost:8080` + - Click "Create" + - Copy the **Client ID** and **Client Secret** + +4. **Get Your Extension ID** + - Go to https://chrome.google.com/webstore/devconsole + - Click on your extension + - The extension ID is in the URL or in the extension details + +5. **Get Refresh Token** + + Use this Node.js script to get a refresh token: + + ```javascript + // save as get-refresh-token.js + const http = require('http'); + const url = require('url'); + const open = require('open'); + + const CLIENT_ID = 'your-client-id'; + const CLIENT_SECRET = 'your-client-secret'; + const REDIRECT_URI = 'http://localhost:8080'; + + const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` + + `client_id=${CLIENT_ID}&` + + `redirect_uri=${REDIRECT_URI}&` + + `response_type=code&` + + `scope=https://www.googleapis.com/auth/chromewebstore&` + + `access_type=offline&` + + `prompt=consent`; + + const server = http.createServer(async (req, res) => { + const query = url.parse(req.url, true).query; + + if (query.code) { + // Exchange code for tokens + const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + code: query.code, + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + redirect_uri: REDIRECT_URI, + grant_type: 'authorization_code', + }), + }); + + const tokens = await tokenResponse.json(); + + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end('

Success!

Check your console for the refresh token.

'); + + console.log('\n=== REFRESH TOKEN ==='); + console.log(tokens.refresh_token); + console.log('=====================\n'); + + server.close(); + } + }); + + server.listen(8080, () => { + console.log('Opening browser for authentication...'); + open(authUrl); + }); + ``` + + Run it: + ```bash + node get-refresh-token.js + ``` + +#### Add Secrets to GitHub + +```bash +# Add all Chrome Web Store secrets +gh secret set CHROME_EXTENSION_ID +# Enter your extension ID + +gh secret set CHROME_CLIENT_ID +# Enter your OAuth client ID + +gh secret set CHROME_CLIENT_SECRET +# Enter your OAuth client secret + +gh secret set CHROME_REFRESH_TOKEN +# Enter your refresh token +``` + +Or via GitHub UI (Settings → Secrets and variables → Actions): +- `CHROME_EXTENSION_ID` +- `CHROME_CLIENT_ID` +- `CHROME_CLIENT_SECRET` +- `CHROME_REFRESH_TOKEN` + +## Verify Setup + +### Test Release Script Locally + +```bash +# Dry run to test without making changes +yarn release:dry-run + +# Test validation +yarn validate + +# Test changelog generation +yarn changelog +``` + +### Test GitHub Actions + +1. Create a test release: + ```bash + git tag v0.0.0-test + git push origin v0.0.0-test + gh release create v0.0.0-test --draft --notes "Test release" + ``` + +2. Publish the draft release on GitHub + +3. Watch the Actions tab to see the workflow run + +4. Clean up: + ```bash + gh release delete v0.0.0-test --yes + git push origin :refs/tags/v0.0.0-test + git tag -d v0.0.0-test + ``` + +## Optional: LLM Configuration + +For better changelog generation, install and configure the LLM CLI: + +```bash +# Install +pip install llm + +# Configure with your preferred AI model +# For OpenAI: +llm keys set openai +# Enter your API key + +# Test it +echo "test" | llm -m gpt-4o "Say hello" + +# Or use Claude: +llm keys set claude +# Enter your Anthropic API key + +# Test with Claude +echo "test" | llm -m claude-3.5-sonnet "Say hello" +``` + +The release script will automatically use LLM if available. You can specify the model: + +```bash +LLM_MODEL=claude-3.5-sonnet yarn release +``` + +## Package Configuration + +### For npm Publishing + +Make sure packages you want to publish are **not** marked as private: + +Edit each package's `package.json`: + +```json +{ + "name": "@crypt.fyi/core", + "version": "0.0.14", + "private": false, // Set to false to publish + // ... +} +``` + +### For Chrome Extension + +Make sure your extension manifest is valid: + +```json +// packages/extension/manifest.json +{ + "manifest_version": 3, + "name": "crypt.fyi", + "version": "0.0.14", // Will be auto-updated by release script + // ... +} +``` + +## Troubleshooting + +### "gh: command not found" + +Install GitHub CLI: +```bash +brew install gh # macOS +# or see https://cli.github.com/ +``` + +### "llm: command not found" + +LLM is optional. Install with: +```bash +pip install llm +``` + +Or the release will use a fallback changelog format. + +### "Not authenticated with GitHub" + +```bash +gh auth login +``` + +### Secrets not working in GitHub Actions + +1. Verify secrets are set: + ```bash + gh secret list + ``` + +2. Check that secret names match exactly (case-sensitive) + +3. Re-set the secret if needed: + ```bash + gh secret set SECRET_NAME + ``` + +### Chrome Web Store API errors + +- Check that all four secrets are set correctly +- Verify the extension ID matches +- Ensure the OAuth credentials have the correct scopes +- Test credentials manually with curl: + ```bash + curl -H "Authorization: Bearer $ACCESS_TOKEN" \ + "https://www.googleapis.com/chromewebstore/v1.1/items/$EXTENSION_ID" + ``` + +## Next Steps + +Once everything is configured: + +1. Make your changes +2. Commit them +3. Run `yarn release` +4. Review and publish the draft release on GitHub +5. GitHub Actions will handle the rest! + +See `RELEASE.md` for detailed release process documentation. diff --git a/scripts/gen-changelog.sh b/scripts/generate-changelog.sh similarity index 54% rename from scripts/gen-changelog.sh rename to scripts/generate-changelog.sh index 6a87f46..8590605 100755 --- a/scripts/gen-changelog.sh +++ b/scripts/generate-changelog.sh @@ -1,65 +1,34 @@ #!/bin/bash +# Generate changelog using LLM or fallback to conventional format +# This script generates a changelog from git commits since the last tag + +set -e + # Configuration LLM_MODEL=${LLM_MODEL:-"gpt-4o"} +OUTPUT_FILE=${OUTPUT_FILE:-"CHANGELOG.md"} + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color # Function to show usage show_usage() { - echo "Usage: $0 [OPTIONS] [commit-hash]" + echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " -m, --model MODEL LLM model to use (default: $LLM_MODEL)" - + echo " -o, --output FILE Output file (default: $OUTPUT_FILE)" echo " -h, --help Show this help message" echo "" - echo "If no commit-hash is provided, will try to find last release tag or fallback to 1 week ago" - echo "" echo "Environment variables:" echo " LLM_MODEL Override default LLM model" + echo " OUTPUT_FILE Override output file" exit 1 } -# Function to get the target commit -get_target_commit() { - if [ ! -z "$1" ]; then - # Verify if the provided hash exists - if git rev-parse --quiet --verify "$1^{commit}" >/dev/null; then - echo "$1" - return 0 - else - echo "Error: Invalid commit hash provided" >&2 - exit 1 - fi - fi - - # Try to find the latest release tag - latest_tag=$(git ls-remote --tags --sort="v:refname" | tail -n1 | awk -F" " '{ print $1 }' 2>/dev/null) - if [ $? -eq 0 ] && [ ! -z "$latest_tag" ]; then - echo "$latest_tag" - return 0 - fi - - # Fallback to 1 week ago - echo $(git rev-list -1 --before="1 week ago" HEAD) -} - -# Function to validate changelog format -validate_changelog() { - local changelog="$1" - - # Check if it contains expected sections - if ! echo "$changelog" | grep -q "### "; then - return 1 - fi - - # Check if it contains commit hashes - if ! echo "$changelog" | grep -q "\[[a-f0-9]\{7,\}\]"; then - return 1 - fi - - return 0 -} - # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in @@ -67,7 +36,10 @@ while [[ $# -gt 0 ]]; do LLM_MODEL="$2" shift 2 ;; - + -o|--output) + OUTPUT_FILE="$2" + shift 2 + ;; -h|--help) show_usage ;; @@ -76,7 +48,6 @@ while [[ $# -gt 0 ]]; do show_usage ;; *) - COMMIT_HASH="$1" shift ;; esac @@ -88,43 +59,38 @@ if ! git rev-parse --git-dir > /dev/null 2>&1; then exit 1 fi -# Check if llm is installed -if ! command -v llm &> /dev/null; then - echo "Error: llm CLI tool is not installed. Please install it first." >&2 - exit 1 -fi - -# Get the target commit -target_commit=$(get_target_commit "$COMMIT_HASH") +# Get the latest tag +latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") -if [ -z "$target_commit" ]; then - echo "Error: Could not determine target commit" >&2 - exit 1 +if [ -z "$latest_tag" ]; then + echo -e "${YELLOW}⚠ No previous tags found. Generating changelog from all commits...${NC}" + target_commit=$(git rev-list --max-parents=0 HEAD) +else + echo -e "${GREEN}📋 Generating changelog since tag: $latest_tag${NC}" + target_commit=$latest_tag fi -echo "Target commit: $target_commit" -echo "Using LLM model: $LLM_MODEL" - -# Get the commit log between target and HEAD with more context +# Get the commit log commit_log=$(git log --pretty=format:"%h|%s|%an|%ad" --date=short "$target_commit..HEAD") if [ -z "$commit_log" ]; then - echo "No changes found between $target_commit and HEAD" + echo "No changes found since $target_commit" exit 0 fi echo "Found $(echo "$commit_log" | wc -l | tr -d ' ') commits to process..." -# Generate the changelog using llm -echo "Generating changelog with $LLM_MODEL..." +# Try to use LLM if available +if command -v llm &> /dev/null; then + echo "Generating changelog with $LLM_MODEL..." -changelog=$(echo "$commit_log" | llm -m "$LLM_MODEL" "Transform these git commits into a structured changelog following these rules: + changelog=$(echo "$commit_log" | llm -m "$LLM_MODEL" "Transform these git commits into a structured changelog following these rules: COMMIT FORMAT: hash|subject|author|date OUTPUT FORMAT (omit empty categories): ### Features -### Improvements +### Improvements ### Bug Fixes ### Security ### Breaking Changes @@ -173,22 +139,19 @@ EXAMPLE OUTPUT: Input commits:") -# Check if llm command succeeded -if [ $? -ne 0 ]; then - echo "Error: Failed to generate changelog with llm" >&2 - exit 1 + if [ $? -eq 0 ] && [ ! -z "$changelog" ]; then + echo "$changelog" + exit 0 + else + echo -e "${YELLOW}⚠ LLM generation failed, falling back to conventional format...${NC}" + fi fi -# Validate the generated changelog -if ! validate_changelog "$changelog"; then - echo "Warning: Generated changelog may not be properly formatted" >&2 - echo "Raw output:" >&2 - echo "$changelog" >&2 - exit 1 -fi +# Fallback: Generate conventional changelog +echo "Generating conventional changelog..." -# Output the changelog +echo "### Changes" echo "" -echo "Generated changelog:" -echo "==================" -echo "$changelog" +while IFS='|' read -r hash subject author date; do + echo "- $subject [$hash]" +done <<< "$commit_log" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..04a59dd --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,308 @@ +#!/bin/bash + +# Comprehensive release script for crypt.fyi monorepo +# This script handles version bumping, validation, changelog, git tagging, and GitHub release + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +DRY_RUN=false +SKIP_VALIDATION=false +SKIP_GIT_CHECK=false + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS] [VERSION_TYPE]" + echo "" + echo "VERSION_TYPE: major, minor, patch (optional - will prompt if not provided)" + echo "" + echo "Options:" + echo " --dry-run Show what would be done without making changes" + echo " --skip-validation Skip pre-release validation checks" + echo " --skip-git-check Skip git working directory check" + echo " -h, --help Show this help message" + echo "" + echo "This script will:" + echo " 1. Check git working directory is clean" + echo " 2. Run pre-release validation (format, typecheck, lint, build, test)" + echo " 3. Generate changelog from commits since last tag" + echo " 4. Prompt for version bump type (major/minor/patch)" + echo " 5. Update all package.json versions" + echo " 6. Commit the version changes" + echo " 7. Create and push a git tag" + echo " 8. Create a draft GitHub release with the changelog" + echo "" + echo "Note: Publishing to npm and Chrome Web Store is handled by GitHub Actions" + exit 1 +} + +# Parse command line arguments +VERSION_TYPE="" +while [[ $# -gt 0 ]]; do + case $1 in + --dry-run) + DRY_RUN=true + shift + ;; + --skip-validation) + SKIP_VALIDATION=true + shift + ;; + --skip-git-check) + SKIP_GIT_CHECK=true + shift + ;; + -h|--help) + show_usage + ;; + major|minor|patch) + VERSION_TYPE=$1 + shift + ;; + *) + echo -e "${RED}Error: Unknown argument $1${NC}" >&2 + show_usage + ;; + esac +done + +cd "$REPO_ROOT" + +echo -e "${BLUE}╔═══════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ crypt.fyi Release Script ║${NC}" +echo -e "${BLUE}╚═══════════════════════════════════════════╝${NC}" +echo "" + +# Step 1: Check git working directory +if [ "$SKIP_GIT_CHECK" = false ]; then + echo -e "${YELLOW}📋 Step 1: Checking git working directory...${NC}" + if [ -n "$(git status --porcelain)" ]; then + echo -e "${RED}✗ Git working directory is not clean${NC}" + echo "Please commit or stash your changes before releasing." + git status --short + exit 1 + fi + echo -e "${GREEN}✓ Git working directory is clean${NC}" + echo "" +fi + +# Step 2: Run validation +if [ "$SKIP_VALIDATION" = false ]; then + echo -e "${YELLOW}🔍 Step 2: Running pre-release validation...${NC}" + if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would run: $SCRIPT_DIR/validate-release.sh" + else + "$SCRIPT_DIR/validate-release.sh" + fi + echo "" +fi + +# Step 3: Generate changelog +echo -e "${YELLOW}📝 Step 3: Generating changelog...${NC}" +if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would generate changelog from commits" + CHANGELOG="[DRY RUN] Sample changelog content" +else + CHANGELOG=$("$SCRIPT_DIR/generate-changelog.sh") + if [ -z "$CHANGELOG" ]; then + echo -e "${RED}✗ Failed to generate changelog${NC}" + exit 1 + fi +fi +echo "" +echo "Generated changelog:" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "$CHANGELOG" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "" + +# Step 4: Get version bump type +if [ -z "$VERSION_TYPE" ]; then + echo -e "${YELLOW}📦 Step 4: Select version bump type${NC}" + echo "" + echo "Current version: $(node -p "require('./packages/extension/package.json').version")" + echo "" + echo "Select version bump:" + echo " 1) patch - Bug fixes and minor changes (0.0.X)" + echo " 2) minor - New features, backwards compatible (0.X.0)" + echo " 3) major - Breaking changes (X.0.0)" + echo "" + read -p "Enter choice (1-3): " choice + + case $choice in + 1) VERSION_TYPE="patch" ;; + 2) VERSION_TYPE="minor" ;; + 3) VERSION_TYPE="major" ;; + *) + echo -e "${RED}Invalid choice${NC}" + exit 1 + ;; + esac +fi + +echo -e "${GREEN}Selected version bump: $VERSION_TYPE${NC}" +echo "" + +# Step 5: Bump versions +echo -e "${YELLOW}⬆️ Step 5: Bumping versions in package.json files...${NC}" + +# Get current version from extension package (our main versioned package) +CURRENT_VERSION=$(node -p "require('./packages/extension/package.json').version") + +# Calculate new version +NEW_VERSION=$(node -e " +const semver = require('semver'); +const current = '$CURRENT_VERSION'; +const bump = '$VERSION_TYPE'; +console.log(semver.inc(current, bump)); +" 2>/dev/null) + +# Fallback if semver is not available +if [ -z "$NEW_VERSION" ]; then + IFS='.' read -r major minor patch <<< "$CURRENT_VERSION" + case $VERSION_TYPE in + major) NEW_VERSION="$((major + 1)).0.0" ;; + minor) NEW_VERSION="$major.$((minor + 1)).0" ;; + patch) NEW_VERSION="$major.$minor.$((patch + 1))" ;; + esac +fi + +echo "Current version: $CURRENT_VERSION" +echo "New version: $NEW_VERSION" +echo "" + +if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would update package.json files to version $NEW_VERSION" +else + # Update all package.json files in packages/*/ + for pkg_file in packages/*/package.json; do + if [ -f "$pkg_file" ]; then + node -e " + const fs = require('fs'); + const path = '$pkg_file'; + const pkg = JSON.parse(fs.readFileSync(path, 'utf8')); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); + " + echo -e "${GREEN}✓ Updated $pkg_file${NC}" + fi + done + + # Update manifest.json if it exists (for browser extension) + if [ -f "packages/extension/manifest.json" ]; then + node -e " + const fs = require('fs'); + const path = './packages/extension/manifest.json'; + const manifest = JSON.parse(fs.readFileSync(path, 'utf8')); + manifest.version = '$NEW_VERSION'; + fs.writeFileSync(path, JSON.stringify(manifest, null, 2) + '\n'); + " + echo -e "${GREEN}✓ Updated packages/extension/manifest.json${NC}" + fi + + # Update web manifest.json if it exists + if [ -f "packages/web/public/manifest.json" ]; then + node -e " + const fs = require('fs'); + const path = './packages/web/public/manifest.json'; + const manifest = JSON.parse(fs.readFileSync(path, 'utf8')); + manifest.version = '$NEW_VERSION'; + fs.writeFileSync(path, JSON.stringify(manifest, null, 2) + '\n'); + " + echo -e "${GREEN}✓ Updated packages/web/public/manifest.json${NC}" + fi +fi +echo "" + +# Step 6: Commit version changes +echo -e "${YELLOW}💾 Step 6: Committing version changes...${NC}" +if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would commit with message: 'chore: release v$NEW_VERSION'" +else + # Add all package.json files + git add packages/*/package.json + + # Add manifest files if they exist + if [ -f "packages/extension/manifest.json" ]; then + git add packages/extension/manifest.json + fi + if [ -f "packages/web/public/manifest.json" ]; then + git add packages/web/public/manifest.json + fi + + git commit -m "chore: release v$NEW_VERSION" + echo -e "${GREEN}✓ Version changes committed${NC}" +fi +echo "" + +# Step 7: Create and push tag +echo -e "${YELLOW}🏷️ Step 7: Creating git tag...${NC}" +TAG_NAME="v$NEW_VERSION" + +if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would create tag: $TAG_NAME" + echo "[DRY RUN] Would push commits and tags to origin" +else + git tag -a "$TAG_NAME" -m "Release $TAG_NAME" + echo -e "${GREEN}✓ Created tag: $TAG_NAME${NC}" + + echo "Pushing commits and tags to origin..." + git push origin main + git push origin "$TAG_NAME" + echo -e "${GREEN}✓ Pushed to remote${NC}" +fi +echo "" + +# Step 8: Create GitHub release +echo -e "${YELLOW}🚀 Step 8: Creating GitHub draft release...${NC}" + +if ! command -v gh &> /dev/null; then + echo -e "${YELLOW}⚠ GitHub CLI (gh) not found. Skipping GitHub release creation.${NC}" + echo "Install it from: https://cli.github.com/" + echo "" + echo "You can manually create the release at:" + echo "https://github.com/$(git config --get remote.origin.url | sed 's/.*github.com[:/]\(.*\)\.git/\1/')/releases/new" +else + if [ "$DRY_RUN" = true ]; then + echo "[DRY RUN] Would create draft GitHub release for $TAG_NAME" + else + # Save changelog to temp file + CHANGELOG_FILE=$(mktemp) + echo "$CHANGELOG" > "$CHANGELOG_FILE" + + gh release create "$TAG_NAME" \ + --draft \ + --title "Release $TAG_NAME" \ + --notes-file "$CHANGELOG_FILE" + + rm "$CHANGELOG_FILE" + echo -e "${GREEN}✓ Created draft GitHub release${NC}" + echo "" + echo "View the draft release at:" + gh release view "$TAG_NAME" --web + fi +fi +echo "" + +# Success summary +echo -e "${GREEN}╔═══════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ Release Process Complete! ║${NC}" +echo -e "${GREEN}╚═══════════════════════════════════════════╝${NC}" +echo "" +echo "Next steps:" +echo " 1. Review the draft GitHub release" +echo " 2. Publish the release (this triggers the GitHub Actions workflow)" +echo " 3. The workflow will automatically:" +echo " - Publish to npm (if configured)" +echo " - Publish to Chrome Web Store (if configured)" +echo "" +echo "Release version: $TAG_NAME" diff --git a/scripts/validate-release.sh b/scripts/validate-release.sh new file mode 100755 index 0000000..2aabef9 --- /dev/null +++ b/scripts/validate-release.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Pre-release validation script +# Runs all checks before allowing a release: format, typecheck, lint, build, test + +set -e + +echo "🔍 Running pre-release validation checks..." +echo "" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Track if any check fails +FAILED=0 + +# Function to run a check +run_check() { + local name=$1 + local command=$2 + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "Running: $name" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + if eval "$command"; then + echo -e "${GREEN}✓ $name passed${NC}" + echo "" + return 0 + else + echo -e "${RED}✗ $name failed${NC}" + echo "" + FAILED=1 + return 1 + fi +} + +# Run all checks +echo "Starting validation pipeline..." +echo "" + +run_check "Format Check" "yarn format:check" || true +run_check "Type Check" "yarn typecheck" || true +run_check "Lint" "yarn lint" || true +run_check "Build" "yarn build" || true +run_check "Test" "yarn test" || true + +# Summary +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +echo "Validation Summary" +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +if [ $FAILED -eq 1 ]; then + echo -e "${RED}❌ Pre-release validation FAILED${NC}" + echo "" + echo "Please fix the errors above before releasing." + exit 1 +else + echo -e "${GREEN}✅ All checks passed!${NC}" + echo "" + echo "Ready to proceed with release." + exit 0 +fi