mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Add cli support for linux (#11167)
This commit is contained in:
parent
e90e6178e3
commit
4592133624
5 changed files with 786 additions and 739 deletions
397
.github/workflows/cli-release.yml
vendored
Normal file
397
.github/workflows/cli-release.yml
vendored
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
name: CLI Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (e.g., 0.1.0). Leave empty to use package.json version.'
|
||||
required: false
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run (build and test but do not create release).'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
# Build CLI for each platform.
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
platform: darwin-arm64
|
||||
runs-on: macos-latest
|
||||
- os: macos-13
|
||||
platform: darwin-x64
|
||||
runs-on: macos-13
|
||||
- os: ubuntu-latest
|
||||
platform: linux-x64
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: |
|
||||
if [ -n "${{ inputs.version }}" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION=$(node -p "require('./apps/cli/package.json').version")
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Using version: $VERSION"
|
||||
|
||||
- name: Build extension bundle
|
||||
run: pnpm bundle
|
||||
|
||||
- name: Build CLI
|
||||
run: pnpm --filter @roo-code/cli build
|
||||
|
||||
- name: Create release tarball
|
||||
id: tarball
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
run: |
|
||||
RELEASE_DIR="roo-cli-${PLATFORM}"
|
||||
TARBALL="roo-cli-${PLATFORM}.tar.gz"
|
||||
|
||||
# Clean up any previous build.
|
||||
rm -rf "$RELEASE_DIR"
|
||||
rm -f "$TARBALL"
|
||||
|
||||
# Create directory structure.
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
mkdir -p "$RELEASE_DIR/lib"
|
||||
mkdir -p "$RELEASE_DIR/extension"
|
||||
|
||||
# Copy CLI dist files.
|
||||
echo "Copying CLI files..."
|
||||
cp -r apps/cli/dist/* "$RELEASE_DIR/lib/"
|
||||
|
||||
# Create package.json for npm install.
|
||||
echo "Creating package.json..."
|
||||
node -e "
|
||||
const pkg = require('./apps/cli/package.json');
|
||||
const newPkg = {
|
||||
name: '@roo-code/cli',
|
||||
version: '$VERSION',
|
||||
type: 'module',
|
||||
dependencies: {
|
||||
'@inkjs/ui': pkg.dependencies['@inkjs/ui'],
|
||||
'@trpc/client': pkg.dependencies['@trpc/client'],
|
||||
'commander': pkg.dependencies.commander,
|
||||
'fuzzysort': pkg.dependencies.fuzzysort,
|
||||
'ink': pkg.dependencies.ink,
|
||||
'p-wait-for': pkg.dependencies['p-wait-for'],
|
||||
'react': pkg.dependencies.react,
|
||||
'superjson': pkg.dependencies.superjson,
|
||||
'zustand': pkg.dependencies.zustand
|
||||
}
|
||||
};
|
||||
console.log(JSON.stringify(newPkg, null, 2));
|
||||
" > "$RELEASE_DIR/package.json"
|
||||
|
||||
# Copy extension bundle.
|
||||
echo "Copying extension bundle..."
|
||||
cp -r src/dist/* "$RELEASE_DIR/extension/"
|
||||
|
||||
# Add package.json to extension directory for CommonJS.
|
||||
echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
|
||||
|
||||
# Find and copy ripgrep binary.
|
||||
echo "Looking for ripgrep binary..."
|
||||
RIPGREP_PATH=$(find node_modules -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
|
||||
echo "Found ripgrep at: $RIPGREP_PATH"
|
||||
mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
|
||||
chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
|
||||
chmod +x "$RELEASE_DIR/bin/rg"
|
||||
else
|
||||
echo "Warning: ripgrep binary not found"
|
||||
fi
|
||||
|
||||
# Create the wrapper script
|
||||
echo "Creating wrapper script..."
|
||||
cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Set environment variables for the CLI
|
||||
process.env.ROO_CLI_ROOT = join(__dirname, '..');
|
||||
process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
|
||||
process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
|
||||
|
||||
// Import and run the actual CLI
|
||||
await import(join(__dirname, '..', 'lib', 'index.js'));
|
||||
WRAPPER_EOF
|
||||
|
||||
chmod +x "$RELEASE_DIR/bin/roo"
|
||||
|
||||
# Create empty .env file.
|
||||
touch "$RELEASE_DIR/.env"
|
||||
|
||||
# Create tarball.
|
||||
echo "Creating tarball..."
|
||||
tar -czvf "$TARBALL" "$RELEASE_DIR"
|
||||
|
||||
# Clean up release directory.
|
||||
rm -rf "$RELEASE_DIR"
|
||||
|
||||
# Create checksum.
|
||||
if command -v sha256sum &> /dev/null; then
|
||||
sha256sum "$TARBALL" > "${TARBALL}.sha256"
|
||||
elif command -v shasum &> /dev/null; then
|
||||
shasum -a 256 "$TARBALL" > "${TARBALL}.sha256"
|
||||
fi
|
||||
|
||||
echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
|
||||
echo "Created: $TARBALL"
|
||||
ls -la "$TARBALL"
|
||||
|
||||
- name: Verify tarball
|
||||
env:
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
run: |
|
||||
TARBALL="roo-cli-${PLATFORM}.tar.gz"
|
||||
|
||||
# Create temp directory for verification.
|
||||
VERIFY_DIR=$(mktemp -d)
|
||||
|
||||
# Extract and verify structure.
|
||||
tar -xzf "$TARBALL" -C "$VERIFY_DIR"
|
||||
|
||||
echo "Verifying tarball contents..."
|
||||
ls -la "$VERIFY_DIR/roo-cli-${PLATFORM}/"
|
||||
|
||||
# Check required files exist.
|
||||
test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/bin/roo" || { echo "Missing bin/roo"; exit 1; }
|
||||
test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/lib/index.js" || { echo "Missing lib/index.js"; exit 1; }
|
||||
test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/package.json" || { echo "Missing package.json"; exit 1; }
|
||||
test -d "$VERIFY_DIR/roo-cli-${PLATFORM}/extension" || { echo "Missing extension directory"; exit 1; }
|
||||
|
||||
echo "Tarball verification passed!"
|
||||
|
||||
# Cleanup.
|
||||
rm -rf "$VERIFY_DIR"
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cli-${{ matrix.platform }}
|
||||
path: |
|
||||
roo-cli-${{ matrix.platform }}.tar.gz
|
||||
roo-cli-${{ matrix.platform }}.tar.gz.sha256
|
||||
retention-days: 7
|
||||
|
||||
# Create GitHub release with all platform artifacts.
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !inputs.dry_run }}
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: |
|
||||
if [ -n "${{ inputs.version }}" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
VERSION=$(node -p "require('./apps/cli/package.json').version")
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Prepare release files
|
||||
run: |
|
||||
mkdir -p release
|
||||
find artifacts -name "*.tar.gz" -exec cp {} release/ \;
|
||||
find artifacts -name "*.sha256" -exec cp {} release/ \;
|
||||
ls -la release/
|
||||
|
||||
- name: Extract changelog
|
||||
id: changelog
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
CHANGELOG_FILE="apps/cli/CHANGELOG.md"
|
||||
|
||||
if [ -f "$CHANGELOG_FILE" ]; then
|
||||
# Extract content between version headers.
|
||||
CONTENT=$(awk -v version="$VERSION" '
|
||||
BEGIN { found = 0; content = ""; target = "[" version "]" }
|
||||
/^## \[/ {
|
||||
if (found) { exit }
|
||||
if (index($0, target) > 0) { found = 1; next }
|
||||
}
|
||||
found { content = content $0 "\n" }
|
||||
END { print content }
|
||||
' "$CHANGELOG_FILE")
|
||||
|
||||
if [ -n "$CONTENT" ]; then
|
||||
echo "Found changelog content"
|
||||
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CONTENT" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "No changelog content found for version $VERSION"
|
||||
echo "content=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "No changelog file found"
|
||||
echo "content=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Generate checksums summary
|
||||
id: checksums
|
||||
run: |
|
||||
echo "checksums<<EOF" >> $GITHUB_OUTPUT
|
||||
cat release/*.sha256 >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Check for existing release
|
||||
id: check_release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
if gh release view "$TAG" &> /dev/null; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Delete existing release
|
||||
if: steps.check_release.outputs.exists == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
echo "Deleting existing release $TAG..."
|
||||
gh release delete "$TAG" --yes || true
|
||||
git push origin ":refs/tags/$TAG" || true
|
||||
|
||||
- name: Create GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
CHANGELOG_CONTENT: ${{ steps.changelog.outputs.content }}
|
||||
CHECKSUMS: ${{ steps.checksums.outputs.checksums }}
|
||||
run: |
|
||||
WHATS_NEW=""
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
WHATS_NEW="## What's New
|
||||
|
||||
$CHANGELOG_CONTENT
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
RELEASE_NOTES=$(cat << EOF
|
||||
${WHATS_NEW}## Installation
|
||||
|
||||
\`\`\`bash
|
||||
curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
|
||||
\`\`\`
|
||||
|
||||
Or install a specific version:
|
||||
\`\`\`bash
|
||||
ROO_VERSION=$VERSION curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
|
||||
\`\`\`
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 20 or higher
|
||||
- macOS (Intel or Apple Silicon) or Linux x64
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`bash
|
||||
# Run a task
|
||||
roo "What is this project?"
|
||||
|
||||
# See all options
|
||||
roo --help
|
||||
\`\`\`
|
||||
|
||||
## Platform Support
|
||||
|
||||
This release includes binaries for:
|
||||
- \`roo-cli-darwin-arm64.tar.gz\` - macOS Apple Silicon (M1/M2/M3)
|
||||
- \`roo-cli-darwin-x64.tar.gz\` - macOS Intel
|
||||
- \`roo-cli-linux-x64.tar.gz\` - Linux x64
|
||||
|
||||
## Checksums
|
||||
|
||||
\`\`\`
|
||||
${CHECKSUMS}
|
||||
\`\`\`
|
||||
EOF
|
||||
)
|
||||
|
||||
gh release create "$TAG" \
|
||||
--title "Roo Code CLI v$VERSION" \
|
||||
--notes "$RELEASE_NOTES" \
|
||||
--prerelease \
|
||||
release/*
|
||||
|
||||
echo "Release created: https://github.com/${{ github.repository }}/releases/tag/$TAG"
|
||||
|
||||
# Summary job for dry runs
|
||||
summary:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ inputs.dry_run }}
|
||||
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Show build summary
|
||||
run: |
|
||||
echo "## Dry Run Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The following artifacts were built:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
find artifacts -name "*.tar.gz" | while read f; do
|
||||
SIZE=$(ls -lh "$f" | awk '{print $5}')
|
||||
echo "- $(basename $f) ($SIZE)" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Checksums" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
cat artifacts/*/*.sha256 >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
|
@ -19,7 +19,7 @@ curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/i
|
|||
**Requirements:**
|
||||
|
||||
- Node.js 20 or higher
|
||||
- macOS (Intel or Apple Silicon) or Linux (x64 or ARM64)
|
||||
- macOS (Intel or Apple Silicon) or Linux x64
|
||||
|
||||
**Custom installation directory:**
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ roo "What is this project?" -w ~/Documents/my-project
|
|||
You can also run without a prompt and enter it interactively in TUI mode:
|
||||
|
||||
```bash
|
||||
roo ~/Documents/my-project
|
||||
roo -w ~/Documents/my-project
|
||||
```
|
||||
|
||||
In interactive mode:
|
||||
|
|
@ -147,21 +147,23 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
|
|||
|
||||
## Options
|
||||
|
||||
| Option | Description | Default |
|
||||
| --------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------- |
|
||||
| `[prompt]` | Your prompt (positional argument, optional) | None |
|
||||
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
|
||||
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
|
||||
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
|
||||
| `-x, --exit-on-complete` | Exit the process when task completes (useful for testing) | `false` |
|
||||
| `-y, --yes` | Non-interactive mode: auto-approve all actions | `false` |
|
||||
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
|
||||
| `-p, --provider <provider>` | API provider (anthropic, openai, openrouter, etc.) | `openrouter` |
|
||||
| `-m, --model <model>` | Model to use | `anthropic/claude-sonnet-4.5` |
|
||||
| `-M, --mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
|
||||
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
|
||||
| `--ephemeral` | Run without persisting state (uses temporary storage) | `false` |
|
||||
| `--no-tui` | Disable TUI, use plain text output | `false` |
|
||||
| Option | Description | Default |
|
||||
| ------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| `[prompt]` | Your prompt (positional argument, optional) | None |
|
||||
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
|
||||
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
|
||||
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
|
||||
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
|
||||
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
|
||||
| `-y, --yes, --dangerously-skip-permissions` | Auto-approve all actions (use with caution) | `false` |
|
||||
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
|
||||
| `--provider <provider>` | API provider (roo, anthropic, openai, openrouter, etc.) | `openrouter` (or `roo` if authenticated) |
|
||||
| `-m, --model <model>` | Model to use | `anthropic/claude-opus-4.5` |
|
||||
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
|
||||
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
|
||||
| `--ephemeral` | Run without persisting state (uses temporary storage) | `false` |
|
||||
| `--oneshot` | Exit upon task completion | `false` |
|
||||
| `--output-format <format>` | Output format with `--print`: `text`, `json`, or `stream-json` | `text` |
|
||||
|
||||
## Auth Commands
|
||||
|
||||
|
|
@ -246,17 +248,33 @@ pnpm lint
|
|||
|
||||
## Releasing
|
||||
|
||||
To create a new release, execute the /cli-release slash command:
|
||||
Official releases are created via the GitHub Actions workflow at `.github/workflows/cli-release.yml`.
|
||||
|
||||
```bash
|
||||
roo "/cli-release" -w ~/Documents/Roo-Code -y
|
||||
```
|
||||
To trigger a release:
|
||||
|
||||
1. Go to **Actions** → **CLI Release**
|
||||
2. Click **Run workflow**
|
||||
3. Optionally specify a version (defaults to `package.json` version)
|
||||
4. Click **Run workflow**
|
||||
|
||||
The workflow will:
|
||||
|
||||
1. Bump the version
|
||||
2. Update the CHANGELOG
|
||||
3. Build the extension and CLI
|
||||
4. Create a platform-specific tarball (for your current OS/architecture)
|
||||
5. Test the install script
|
||||
6. Create a GitHub release with the tarball attached
|
||||
1. Build the CLI on all platforms (macOS Intel, macOS ARM, Linux x64)
|
||||
2. Create platform-specific tarballs with bundled ripgrep
|
||||
3. Verify each tarball
|
||||
4. Create a GitHub release with all tarballs attached
|
||||
|
||||
### Local Builds
|
||||
|
||||
For local development and testing, use the build script:
|
||||
|
||||
```bash
|
||||
# Build tarball for your current platform
|
||||
./apps/cli/scripts/build.sh
|
||||
|
||||
# Build and install locally
|
||||
./apps/cli/scripts/build.sh --install
|
||||
|
||||
# Fast build (skip verification)
|
||||
./apps/cli/scripts/build.sh --skip-verify
|
||||
```
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
"dev": "tsup --watch",
|
||||
"start": "ROO_AUTH_BASE_URL=http://localhost:3000 ROO_SDK_BASE_URL=http://localhost:3001 ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy node dist/index.js",
|
||||
"start:production": "node dist/index.js",
|
||||
"release": "scripts/release.sh",
|
||||
"build:local": "scripts/build.sh",
|
||||
"clean": "rimraf dist .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
343
apps/cli/scripts/build.sh
Executable file
343
apps/cli/scripts/build.sh
Executable file
|
|
@ -0,0 +1,343 @@
|
|||
#!/bin/bash
|
||||
# Roo Code CLI Local Build Script
|
||||
#
|
||||
# Usage:
|
||||
# ./apps/cli/scripts/build.sh [options]
|
||||
#
|
||||
# Options:
|
||||
# --install Install locally after building
|
||||
# --skip-verify Skip end-to-end verification tests (faster builds)
|
||||
#
|
||||
# Examples:
|
||||
# ./apps/cli/scripts/build.sh # Build for local testing
|
||||
# ./apps/cli/scripts/build.sh --install # Build and install locally
|
||||
# ./apps/cli/scripts/build.sh --skip-verify # Fast local build
|
||||
#
|
||||
# This script builds the CLI for your current platform. For official releases
|
||||
# with multi-platform support, use the GitHub Actions workflow instead:
|
||||
# .github/workflows/cli-release.yml
|
||||
#
|
||||
# Prerequisites:
|
||||
# - pnpm installed
|
||||
# - Run from the monorepo root directory
|
||||
|
||||
set -e
|
||||
|
||||
# Parse arguments
|
||||
LOCAL_INSTALL=false
|
||||
SKIP_VERIFY=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--install)
|
||||
LOCAL_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--skip-verify)
|
||||
SKIP_VERIFY=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { printf "${GREEN}==>${NC} %s\n" "$1"; }
|
||||
warn() { printf "${YELLOW}Warning:${NC} %s\n" "$1"; }
|
||||
error() { printf "${RED}Error:${NC} %s\n" "$1" >&2; exit 1; }
|
||||
step() { printf "${BLUE}${BOLD}[%s]${NC} %s\n" "$1" "$2"; }
|
||||
|
||||
# Get script directory and repo root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
CLI_DIR="$REPO_ROOT/apps/cli"
|
||||
|
||||
# Detect current platform
|
||||
detect_platform() {
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
case "$OS" in
|
||||
darwin) OS="darwin" ;;
|
||||
linux) OS="linux" ;;
|
||||
*) error "Unsupported OS: $OS" ;;
|
||||
esac
|
||||
|
||||
case "$ARCH" in
|
||||
x86_64|amd64) ARCH="x64" ;;
|
||||
arm64|aarch64) ARCH="arm64" ;;
|
||||
*) error "Unsupported architecture: $ARCH" ;;
|
||||
esac
|
||||
|
||||
PLATFORM="${OS}-${ARCH}"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
step "1/6" "Checking prerequisites..."
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
error "pnpm is not installed."
|
||||
fi
|
||||
|
||||
if ! command -v node &> /dev/null; then
|
||||
error "Node.js is not installed."
|
||||
fi
|
||||
|
||||
info "Prerequisites OK"
|
||||
}
|
||||
|
||||
# Get version
|
||||
get_version() {
|
||||
VERSION=$(node -p "require('$CLI_DIR/package.json').version")
|
||||
GIT_SHORT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
VERSION="${VERSION}-local.${GIT_SHORT_HASH}"
|
||||
|
||||
info "Version: $VERSION"
|
||||
}
|
||||
|
||||
# Build everything
|
||||
build() {
|
||||
step "2/6" "Building extension bundle..."
|
||||
cd "$REPO_ROOT"
|
||||
pnpm bundle
|
||||
|
||||
step "3/6" "Building CLI..."
|
||||
pnpm --filter @roo-code/cli build
|
||||
|
||||
info "Build complete"
|
||||
}
|
||||
|
||||
# Create release tarball
|
||||
create_tarball() {
|
||||
step "4/6" "Creating release tarball for $PLATFORM..."
|
||||
|
||||
RELEASE_DIR="$REPO_ROOT/roo-cli-${PLATFORM}"
|
||||
TARBALL="roo-cli-${PLATFORM}.tar.gz"
|
||||
|
||||
# Clean up any previous build
|
||||
rm -rf "$RELEASE_DIR"
|
||||
rm -f "$REPO_ROOT/$TARBALL"
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
mkdir -p "$RELEASE_DIR/lib"
|
||||
mkdir -p "$RELEASE_DIR/extension"
|
||||
|
||||
# Copy CLI dist files
|
||||
info "Copying CLI files..."
|
||||
cp -r "$CLI_DIR/dist/"* "$RELEASE_DIR/lib/"
|
||||
|
||||
# Create package.json for npm install
|
||||
info "Creating package.json..."
|
||||
node -e "
|
||||
const pkg = require('$CLI_DIR/package.json');
|
||||
const newPkg = {
|
||||
name: '@roo-code/cli',
|
||||
version: '$VERSION',
|
||||
type: 'module',
|
||||
dependencies: {
|
||||
'@inkjs/ui': pkg.dependencies['@inkjs/ui'],
|
||||
'@trpc/client': pkg.dependencies['@trpc/client'],
|
||||
'commander': pkg.dependencies.commander,
|
||||
'fuzzysort': pkg.dependencies.fuzzysort,
|
||||
'ink': pkg.dependencies.ink,
|
||||
'p-wait-for': pkg.dependencies['p-wait-for'],
|
||||
'react': pkg.dependencies.react,
|
||||
'superjson': pkg.dependencies.superjson,
|
||||
'zustand': pkg.dependencies.zustand
|
||||
}
|
||||
};
|
||||
console.log(JSON.stringify(newPkg, null, 2));
|
||||
" > "$RELEASE_DIR/package.json"
|
||||
|
||||
# Copy extension bundle
|
||||
info "Copying extension bundle..."
|
||||
cp -r "$REPO_ROOT/src/dist/"* "$RELEASE_DIR/extension/"
|
||||
|
||||
# Add package.json to extension directory for CommonJS
|
||||
echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
|
||||
|
||||
# Find and copy ripgrep binary
|
||||
info "Looking for ripgrep binary..."
|
||||
RIPGREP_PATH=$(find "$REPO_ROOT/node_modules" -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
|
||||
info "Found ripgrep at: $RIPGREP_PATH"
|
||||
mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
|
||||
chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
|
||||
chmod +x "$RELEASE_DIR/bin/rg"
|
||||
else
|
||||
warn "ripgrep binary not found - users will need ripgrep installed"
|
||||
fi
|
||||
|
||||
# Create the wrapper script
|
||||
info "Creating wrapper script..."
|
||||
cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Set environment variables for the CLI
|
||||
process.env.ROO_CLI_ROOT = join(__dirname, '..');
|
||||
process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
|
||||
process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
|
||||
|
||||
// Import and run the actual CLI
|
||||
await import(join(__dirname, '..', 'lib', 'index.js'));
|
||||
WRAPPER_EOF
|
||||
|
||||
chmod +x "$RELEASE_DIR/bin/roo"
|
||||
|
||||
# Create empty .env file
|
||||
touch "$RELEASE_DIR/.env"
|
||||
|
||||
# Create tarball
|
||||
info "Creating tarball..."
|
||||
cd "$REPO_ROOT"
|
||||
tar -czvf "$TARBALL" "$(basename "$RELEASE_DIR")"
|
||||
|
||||
# Clean up release directory
|
||||
rm -rf "$RELEASE_DIR"
|
||||
|
||||
# Show size
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
TARBALL_SIZE=$(ls -lh "$TARBALL_PATH" | awk '{print $5}')
|
||||
info "Created: $TARBALL ($TARBALL_SIZE)"
|
||||
}
|
||||
|
||||
# Verify local installation
|
||||
verify_local_install() {
|
||||
if [ "$SKIP_VERIFY" = true ]; then
|
||||
step "5/6" "Skipping verification (--skip-verify)"
|
||||
return
|
||||
fi
|
||||
|
||||
step "5/6" "Verifying installation..."
|
||||
|
||||
VERIFY_DIR="$REPO_ROOT/.verify-release"
|
||||
VERIFY_INSTALL_DIR="$VERIFY_DIR/cli"
|
||||
VERIFY_BIN_DIR="$VERIFY_DIR/bin"
|
||||
|
||||
rm -rf "$VERIFY_DIR"
|
||||
mkdir -p "$VERIFY_DIR"
|
||||
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
|
||||
ROO_LOCAL_TARBALL="$TARBALL_PATH" \
|
||||
ROO_INSTALL_DIR="$VERIFY_INSTALL_DIR" \
|
||||
ROO_BIN_DIR="$VERIFY_BIN_DIR" \
|
||||
ROO_VERSION="$VERSION" \
|
||||
"$CLI_DIR/install.sh" || {
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "Installation verification failed!"
|
||||
}
|
||||
|
||||
# Test --help
|
||||
if ! "$VERIFY_BIN_DIR/roo" --help > /dev/null 2>&1; then
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "CLI --help check failed!"
|
||||
fi
|
||||
info "CLI --help check passed"
|
||||
|
||||
# Test --version
|
||||
if ! "$VERIFY_BIN_DIR/roo" --version > /dev/null 2>&1; then
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "CLI --version check failed!"
|
||||
fi
|
||||
info "CLI --version check passed"
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
rm -rf "$VERIFY_DIR"
|
||||
|
||||
info "Verification passed!"
|
||||
}
|
||||
|
||||
# Install locally
|
||||
install_local() {
|
||||
if [ "$LOCAL_INSTALL" = false ]; then
|
||||
step "6/6" "Skipping install (use --install to auto-install)"
|
||||
return
|
||||
fi
|
||||
|
||||
step "6/6" "Installing locally..."
|
||||
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
|
||||
ROO_LOCAL_TARBALL="$TARBALL_PATH" \
|
||||
ROO_VERSION="$VERSION" \
|
||||
"$CLI_DIR/install.sh" || {
|
||||
error "Local installation failed!"
|
||||
}
|
||||
|
||||
info "Local installation complete!"
|
||||
}
|
||||
|
||||
# Print summary
|
||||
print_summary() {
|
||||
echo ""
|
||||
printf "${GREEN}${BOLD}✓ Local build complete for v$VERSION${NC}\n"
|
||||
echo ""
|
||||
echo " Tarball: $REPO_ROOT/$TARBALL"
|
||||
echo ""
|
||||
|
||||
if [ "$LOCAL_INSTALL" = true ]; then
|
||||
echo " Installed to: ~/.roo/cli"
|
||||
echo " Binary: ~/.local/bin/roo"
|
||||
echo ""
|
||||
echo " Test it out:"
|
||||
echo " roo --version"
|
||||
echo " roo --help"
|
||||
else
|
||||
echo " To install manually:"
|
||||
echo " ROO_LOCAL_TARBALL=$REPO_ROOT/$TARBALL ./apps/cli/install.sh"
|
||||
echo ""
|
||||
echo " Or re-run with --install:"
|
||||
echo " ./apps/cli/scripts/build.sh --install"
|
||||
fi
|
||||
echo ""
|
||||
echo " For official multi-platform releases, use the GitHub Actions workflow:"
|
||||
echo " .github/workflows/cli-release.yml"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
echo ""
|
||||
printf "${BLUE}${BOLD}"
|
||||
echo " ╭─────────────────────────────────╮"
|
||||
echo " │ Roo Code CLI Local Build │"
|
||||
echo " ╰─────────────────────────────────╯"
|
||||
printf "${NC}"
|
||||
echo ""
|
||||
|
||||
detect_platform
|
||||
check_prerequisites
|
||||
get_version
|
||||
build
|
||||
create_tarball
|
||||
verify_local_install
|
||||
install_local
|
||||
print_summary
|
||||
}
|
||||
|
||||
main
|
||||
|
|
@ -1,711 +0,0 @@
|
|||
#!/bin/bash
|
||||
# Roo Code CLI Release Script
|
||||
#
|
||||
# Usage:
|
||||
# ./apps/cli/scripts/release.sh [options] [version]
|
||||
#
|
||||
# Options:
|
||||
# --dry-run Run all steps except creating the GitHub release
|
||||
# --local Build for local testing only (no GitHub checks, no changelog prompts)
|
||||
# --install Install locally after building (only with --local)
|
||||
# --skip-verify Skip end-to-end verification tests (faster local builds)
|
||||
#
|
||||
# Examples:
|
||||
# ./apps/cli/scripts/release.sh # Use version from package.json
|
||||
# ./apps/cli/scripts/release.sh 0.1.0 # Specify version
|
||||
# ./apps/cli/scripts/release.sh --dry-run # Test the release flow without pushing
|
||||
# ./apps/cli/scripts/release.sh --dry-run 0.1.0 # Dry run with specific version
|
||||
# ./apps/cli/scripts/release.sh --local # Build for local testing
|
||||
# ./apps/cli/scripts/release.sh --local --install # Build and install locally
|
||||
# ./apps/cli/scripts/release.sh --local --skip-verify # Fast local build
|
||||
#
|
||||
# This script:
|
||||
# 1. Builds the extension and CLI
|
||||
# 2. Creates a tarball for the current platform
|
||||
# 3. Creates a GitHub release and uploads the tarball (unless --dry-run or --local)
|
||||
#
|
||||
# Prerequisites:
|
||||
# - GitHub CLI (gh) installed and authenticated (not needed for --local)
|
||||
# - pnpm installed
|
||||
# - Run from the monorepo root directory
|
||||
|
||||
set -e
|
||||
|
||||
# Parse arguments
|
||||
DRY_RUN=false
|
||||
LOCAL_BUILD=false
|
||||
LOCAL_INSTALL=false
|
||||
SKIP_VERIFY=false
|
||||
VERSION_ARG=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--local)
|
||||
LOCAL_BUILD=true
|
||||
shift
|
||||
;;
|
||||
--install)
|
||||
LOCAL_INSTALL=true
|
||||
shift
|
||||
;;
|
||||
--skip-verify)
|
||||
SKIP_VERIFY=true
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
VERSION_ARG="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate option combinations
|
||||
if [ "$LOCAL_INSTALL" = true ] && [ "$LOCAL_BUILD" = false ]; then
|
||||
echo "Error: --install can only be used with --local" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { printf "${GREEN}==>${NC} %s\n" "$1"; }
|
||||
warn() { printf "${YELLOW}Warning:${NC} %s\n" "$1"; }
|
||||
error() { printf "${RED}Error:${NC} %s\n" "$1" >&2; exit 1; }
|
||||
step() { printf "${BLUE}${BOLD}[%s]${NC} %s\n" "$1" "$2"; }
|
||||
|
||||
# Get script directory and repo root
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
CLI_DIR="$REPO_ROOT/apps/cli"
|
||||
|
||||
# Detect current platform
|
||||
detect_platform() {
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
|
||||
case "$OS" in
|
||||
darwin) OS="darwin" ;;
|
||||
linux) OS="linux" ;;
|
||||
*) error "Unsupported OS: $OS" ;;
|
||||
esac
|
||||
|
||||
case "$ARCH" in
|
||||
x86_64|amd64) ARCH="x64" ;;
|
||||
arm64|aarch64) ARCH="arm64" ;;
|
||||
*) error "Unsupported architecture: $ARCH" ;;
|
||||
esac
|
||||
|
||||
PLATFORM="${OS}-${ARCH}"
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
check_prerequisites() {
|
||||
step "1/8" "Checking prerequisites..."
|
||||
|
||||
# Skip GitHub CLI checks for local builds
|
||||
if [ "$LOCAL_BUILD" = false ]; then
|
||||
if ! command -v gh &> /dev/null; then
|
||||
error "GitHub CLI (gh) is not installed. Install it with: brew install gh"
|
||||
fi
|
||||
|
||||
if ! gh auth status &> /dev/null; then
|
||||
error "GitHub CLI is not authenticated. Run: gh auth login"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v pnpm &> /dev/null; then
|
||||
error "pnpm is not installed."
|
||||
fi
|
||||
|
||||
if ! command -v node &> /dev/null; then
|
||||
error "Node.js is not installed."
|
||||
fi
|
||||
|
||||
info "Prerequisites OK"
|
||||
}
|
||||
|
||||
# Get version
|
||||
get_version() {
|
||||
if [ -n "$VERSION_ARG" ]; then
|
||||
VERSION="$VERSION_ARG"
|
||||
else
|
||||
VERSION=$(node -p "require('$CLI_DIR/package.json').version")
|
||||
fi
|
||||
|
||||
# For local builds, append a local suffix with git short hash
|
||||
# This creates versions like: 0.1.0-local.abc1234
|
||||
if [ "$LOCAL_BUILD" = true ]; then
|
||||
GIT_SHORT_HASH=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
# Only append suffix if not already a local version
|
||||
if ! echo "$VERSION" | grep -qE '\-local\.'; then
|
||||
VERSION="${VERSION}-local.${GIT_SHORT_HASH}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate semver format (allow -local.hash suffix)
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
|
||||
error "Invalid version format: $VERSION (expected semver like 0.1.0)"
|
||||
fi
|
||||
|
||||
TAG="cli-v$VERSION"
|
||||
info "Version: $VERSION (tag: $TAG)"
|
||||
}
|
||||
|
||||
# Extract changelog content for a specific version
|
||||
# Returns the content between the version header and the next version header (or EOF)
|
||||
get_changelog_content() {
|
||||
CHANGELOG_FILE="$CLI_DIR/CHANGELOG.md"
|
||||
|
||||
if [ ! -f "$CHANGELOG_FILE" ]; then
|
||||
warn "No CHANGELOG.md found at $CHANGELOG_FILE"
|
||||
CHANGELOG_CONTENT=""
|
||||
return
|
||||
fi
|
||||
|
||||
# Try to find the version section (handles both "[0.0.43]" and "[0.0.43] - date" formats)
|
||||
# Also handles "Unreleased" marker
|
||||
VERSION_PATTERN="^\#\# \[${VERSION}\]"
|
||||
|
||||
# Check if the version exists in the changelog
|
||||
if ! grep -qE "$VERSION_PATTERN" "$CHANGELOG_FILE"; then
|
||||
warn "No changelog entry found for version $VERSION"
|
||||
# Skip prompts for local builds
|
||||
if [ "$LOCAL_BUILD" = true ]; then
|
||||
info "Skipping changelog prompt for local build"
|
||||
CHANGELOG_CONTENT=""
|
||||
return
|
||||
fi
|
||||
warn "Please add an entry to $CHANGELOG_FILE before releasing"
|
||||
echo ""
|
||||
echo "Expected format:"
|
||||
echo " ## [$VERSION] - $(date +%Y-%m-%d)"
|
||||
echo " "
|
||||
echo " ### Added"
|
||||
echo " - Your changes here"
|
||||
echo ""
|
||||
read -p "Continue without changelog content? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
error "Aborted. Please add a changelog entry and try again."
|
||||
fi
|
||||
CHANGELOG_CONTENT=""
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract content between this version and the next version header (or EOF)
|
||||
# Uses awk to capture everything between ## [VERSION] and the next ## [
|
||||
# Using index() with "[VERSION]" ensures exact matching (1.0.1 won't match 1.0.10)
|
||||
CHANGELOG_CONTENT=$(awk -v version="$VERSION" '
|
||||
BEGIN { found = 0; content = ""; target = "[" version "]" }
|
||||
/^## \[/ {
|
||||
if (found) { exit }
|
||||
if (index($0, target) > 0) { found = 1; next }
|
||||
}
|
||||
found { content = content $0 "\n" }
|
||||
END { print content }
|
||||
' "$CHANGELOG_FILE")
|
||||
|
||||
# Trim leading/trailing whitespace
|
||||
CHANGELOG_CONTENT=$(echo "$CHANGELOG_CONTENT" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
||||
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
info "Found changelog content for version $VERSION"
|
||||
else
|
||||
warn "Changelog entry for $VERSION appears to be empty"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build everything
|
||||
build() {
|
||||
step "2/8" "Building extension bundle..."
|
||||
cd "$REPO_ROOT"
|
||||
pnpm bundle
|
||||
|
||||
step "3/8" "Building CLI..."
|
||||
pnpm --filter @roo-code/cli build
|
||||
|
||||
info "Build complete"
|
||||
}
|
||||
|
||||
# Create release tarball
|
||||
create_tarball() {
|
||||
step "4/8" "Creating release tarball for $PLATFORM..."
|
||||
|
||||
RELEASE_DIR="$REPO_ROOT/roo-cli-${PLATFORM}"
|
||||
TARBALL="roo-cli-${PLATFORM}.tar.gz"
|
||||
|
||||
# Clean up any previous build
|
||||
rm -rf "$RELEASE_DIR"
|
||||
rm -f "$REPO_ROOT/$TARBALL"
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
mkdir -p "$RELEASE_DIR/lib"
|
||||
mkdir -p "$RELEASE_DIR/extension"
|
||||
|
||||
# Copy CLI dist files
|
||||
info "Copying CLI files..."
|
||||
cp -r "$CLI_DIR/dist/"* "$RELEASE_DIR/lib/"
|
||||
|
||||
# Create package.json for npm install (runtime dependencies that can't be bundled)
|
||||
info "Creating package.json..."
|
||||
node -e "
|
||||
const pkg = require('$CLI_DIR/package.json');
|
||||
const newPkg = {
|
||||
name: '@roo-code/cli',
|
||||
version: '$VERSION',
|
||||
type: 'module',
|
||||
dependencies: {
|
||||
'@inkjs/ui': pkg.dependencies['@inkjs/ui'],
|
||||
'@trpc/client': pkg.dependencies['@trpc/client'],
|
||||
'commander': pkg.dependencies.commander,
|
||||
'fuzzysort': pkg.dependencies.fuzzysort,
|
||||
'ink': pkg.dependencies.ink,
|
||||
'p-wait-for': pkg.dependencies['p-wait-for'],
|
||||
'react': pkg.dependencies.react,
|
||||
'superjson': pkg.dependencies.superjson,
|
||||
'zustand': pkg.dependencies.zustand
|
||||
}
|
||||
};
|
||||
console.log(JSON.stringify(newPkg, null, 2));
|
||||
" > "$RELEASE_DIR/package.json"
|
||||
|
||||
# Copy extension bundle
|
||||
info "Copying extension bundle..."
|
||||
cp -r "$REPO_ROOT/src/dist/"* "$RELEASE_DIR/extension/"
|
||||
|
||||
# Add package.json to extension directory to mark it as CommonJS
|
||||
# This is necessary because the main package.json has "type": "module"
|
||||
# but the extension bundle is CommonJS
|
||||
echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
|
||||
|
||||
# Find and copy ripgrep binary
|
||||
# The extension looks for ripgrep at: appRoot/node_modules/@vscode/ripgrep/bin/rg
|
||||
# The CLI sets appRoot to the CLI package root, so we need to put ripgrep there
|
||||
info "Looking for ripgrep binary..."
|
||||
RIPGREP_PATH=$(find "$REPO_ROOT/node_modules" -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
|
||||
if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
|
||||
info "Found ripgrep at: $RIPGREP_PATH"
|
||||
# Create the expected directory structure for the extension to find ripgrep
|
||||
mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
|
||||
chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
|
||||
# Also keep a copy in bin/ for direct access
|
||||
mkdir -p "$RELEASE_DIR/bin"
|
||||
cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
|
||||
chmod +x "$RELEASE_DIR/bin/rg"
|
||||
else
|
||||
warn "ripgrep binary not found - users will need ripgrep installed"
|
||||
fi
|
||||
|
||||
# Create the wrapper script
|
||||
info "Creating wrapper script..."
|
||||
cat > "$RELEASE_DIR/bin/roo" << 'WRAPPER_EOF'
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Set environment variables for the CLI
|
||||
// ROO_CLI_ROOT is the installed CLI package root (where node_modules/@vscode/ripgrep is)
|
||||
process.env.ROO_CLI_ROOT = join(__dirname, '..');
|
||||
process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');
|
||||
process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');
|
||||
|
||||
// Import and run the actual CLI
|
||||
await import(join(__dirname, '..', 'lib', 'index.js'));
|
||||
WRAPPER_EOF
|
||||
|
||||
chmod +x "$RELEASE_DIR/bin/roo"
|
||||
|
||||
# Create empty .env file to suppress dotenvx warnings
|
||||
touch "$RELEASE_DIR/.env"
|
||||
|
||||
# Create empty .env file to suppress dotenvx warnings
|
||||
touch "$RELEASE_DIR/.env"
|
||||
|
||||
# Create tarball
|
||||
info "Creating tarball..."
|
||||
cd "$REPO_ROOT"
|
||||
tar -czvf "$TARBALL" "$(basename "$RELEASE_DIR")"
|
||||
|
||||
# Clean up release directory
|
||||
rm -rf "$RELEASE_DIR"
|
||||
|
||||
# Show size
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
TARBALL_SIZE=$(ls -lh "$TARBALL_PATH" | awk '{print $5}')
|
||||
info "Created: $TARBALL ($TARBALL_SIZE)"
|
||||
}
|
||||
|
||||
# Verify local installation
|
||||
verify_local_install() {
|
||||
if [ "$SKIP_VERIFY" = true ]; then
|
||||
step "5/8" "Skipping verification (--skip-verify)"
|
||||
return
|
||||
fi
|
||||
|
||||
step "5/8" "Verifying local installation..."
|
||||
|
||||
VERIFY_DIR="$REPO_ROOT/.verify-release"
|
||||
VERIFY_INSTALL_DIR="$VERIFY_DIR/cli"
|
||||
VERIFY_BIN_DIR="$VERIFY_DIR/bin"
|
||||
|
||||
# Clean up any previous verification directory
|
||||
rm -rf "$VERIFY_DIR"
|
||||
mkdir -p "$VERIFY_DIR"
|
||||
|
||||
# Run the actual install script with the local tarball
|
||||
info "Running install script with local tarball..."
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
|
||||
ROO_LOCAL_TARBALL="$TARBALL_PATH" \
|
||||
ROO_INSTALL_DIR="$VERIFY_INSTALL_DIR" \
|
||||
ROO_BIN_DIR="$VERIFY_BIN_DIR" \
|
||||
ROO_VERSION="$VERSION" \
|
||||
"$CLI_DIR/install.sh" || {
|
||||
echo ""
|
||||
warn "Install script failed. Showing tarball contents:"
|
||||
tar -tzf "$TARBALL_PATH" 2>&1 || true
|
||||
echo ""
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "Installation verification failed! The install script could not complete successfully."
|
||||
}
|
||||
|
||||
# Verify the CLI runs correctly with basic commands
|
||||
info "Testing installed CLI..."
|
||||
|
||||
# Test --help
|
||||
if ! "$VERIFY_BIN_DIR/roo" --help > /dev/null 2>&1; then
|
||||
echo ""
|
||||
warn "CLI --help output:"
|
||||
"$VERIFY_BIN_DIR/roo" --help 2>&1 || true
|
||||
echo ""
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "CLI --help check failed! The release tarball may have missing dependencies."
|
||||
fi
|
||||
info "CLI --help check passed"
|
||||
|
||||
# Test --version
|
||||
if ! "$VERIFY_BIN_DIR/roo" --version > /dev/null 2>&1; then
|
||||
echo ""
|
||||
warn "CLI --version output:"
|
||||
"$VERIFY_BIN_DIR/roo" --version 2>&1 || true
|
||||
echo ""
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "CLI --version check failed! The release tarball may have missing dependencies."
|
||||
fi
|
||||
info "CLI --version check passed"
|
||||
|
||||
# Run a simple end-to-end test to verify the CLI actually works
|
||||
info "Running end-to-end verification test..."
|
||||
|
||||
# Create a temporary workspace for the test
|
||||
VERIFY_WORKSPACE="$VERIFY_DIR/workspace"
|
||||
mkdir -p "$VERIFY_WORKSPACE"
|
||||
|
||||
# Run the CLI with a simple prompt
|
||||
if timeout 60 "$VERIFY_BIN_DIR/roo" --yes --oneshot -w "$VERIFY_WORKSPACE" "1+1=?" > "$VERIFY_DIR/test-output.log" 2>&1; then
|
||||
info "End-to-end test passed"
|
||||
else
|
||||
EXIT_CODE=$?
|
||||
echo ""
|
||||
warn "End-to-end test failed (exit code: $EXIT_CODE). Output:"
|
||||
cat "$VERIFY_DIR/test-output.log" 2>&1 || true
|
||||
echo ""
|
||||
rm -rf "$VERIFY_DIR"
|
||||
error "CLI end-to-end test failed! The CLI may be broken."
|
||||
fi
|
||||
|
||||
# Clean up verification directory
|
||||
cd "$REPO_ROOT"
|
||||
rm -rf "$VERIFY_DIR"
|
||||
|
||||
info "Local verification passed!"
|
||||
}
|
||||
|
||||
# Create checksum
|
||||
create_checksum() {
|
||||
step "6/8" "Creating checksum..."
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if command -v sha256sum &> /dev/null; then
|
||||
sha256sum "$TARBALL" > "${TARBALL}.sha256"
|
||||
elif command -v shasum &> /dev/null; then
|
||||
shasum -a 256 "$TARBALL" > "${TARBALL}.sha256"
|
||||
else
|
||||
warn "No sha256sum or shasum found, skipping checksum"
|
||||
return
|
||||
fi
|
||||
|
||||
info "Checksum: $(cat "${TARBALL}.sha256")"
|
||||
}
|
||||
|
||||
# Check if release already exists
|
||||
check_existing_release() {
|
||||
step "7/8" "Checking for existing release..."
|
||||
|
||||
if gh release view "$TAG" &> /dev/null; then
|
||||
warn "Release $TAG already exists"
|
||||
read -p "Do you want to delete it and create a new one? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
info "Deleting existing release..."
|
||||
gh release delete "$TAG" --yes
|
||||
# Also delete the tag if it exists
|
||||
git tag -d "$TAG" 2>/dev/null || true
|
||||
git push origin ":refs/tags/$TAG" 2>/dev/null || true
|
||||
else
|
||||
error "Aborted. Use a different version or delete the existing release manually."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Create GitHub release
|
||||
create_release() {
|
||||
step "8/8" "Creating GitHub release..."
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Get the current commit SHA for the release target
|
||||
COMMIT_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Verify the commit exists on GitHub before attempting to create the release
|
||||
# This prevents the "Release.target_commitish is invalid" error
|
||||
info "Verifying commit ${COMMIT_SHA:0:8} exists on GitHub..."
|
||||
git fetch origin 2>/dev/null || true
|
||||
if ! git branch -r --contains "$COMMIT_SHA" 2>/dev/null | grep -q "origin/"; then
|
||||
warn "Commit ${COMMIT_SHA:0:8} has not been pushed to GitHub"
|
||||
echo ""
|
||||
echo "The release script needs to create a release at your current commit,"
|
||||
echo "but this commit hasn't been pushed to GitHub yet."
|
||||
echo ""
|
||||
read -p "Push current branch to origin now? [Y/n] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||
info "Pushing to origin..."
|
||||
git push origin HEAD || error "Failed to push to origin. Please push manually and try again."
|
||||
else
|
||||
error "Aborted. Please push your commits to GitHub and try again."
|
||||
fi
|
||||
fi
|
||||
info "Commit verified on GitHub"
|
||||
|
||||
# Build the What's New section from changelog content
|
||||
WHATS_NEW_SECTION=""
|
||||
if [ -n "$CHANGELOG_CONTENT" ]; then
|
||||
WHATS_NEW_SECTION="## What's New
|
||||
|
||||
$CHANGELOG_CONTENT
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
RELEASE_NOTES=$(cat << EOF
|
||||
${WHATS_NEW_SECTION}## Installation
|
||||
|
||||
\`\`\`bash
|
||||
curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
|
||||
\`\`\`
|
||||
|
||||
Or install a specific version:
|
||||
\`\`\`bash
|
||||
ROO_VERSION=$VERSION curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh
|
||||
\`\`\`
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js 20 or higher
|
||||
- macOS (Intel or Apple Silicon) or Linux (x64 or ARM64)
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`bash
|
||||
# Run a task
|
||||
roo "What is this project?"
|
||||
|
||||
# See all options
|
||||
roo --help
|
||||
\`\`\`
|
||||
|
||||
## Platform Support
|
||||
|
||||
This release includes:
|
||||
- \`roo-cli-${PLATFORM}.tar.gz\` - Built on $(uname -s) $(uname -m)
|
||||
|
||||
> **Note:** Additional platforms will be added as needed. If you need a different platform, please open an issue.
|
||||
|
||||
## Checksum
|
||||
|
||||
\`\`\`
|
||||
$(cat "${TARBALL}.sha256" 2>/dev/null || echo "N/A")
|
||||
\`\`\`
|
||||
EOF
|
||||
)
|
||||
|
||||
info "Creating release at commit: ${COMMIT_SHA:0:8}"
|
||||
|
||||
# Create release (gh will create the tag automatically)
|
||||
info "Creating release..."
|
||||
RELEASE_FILES="$TARBALL"
|
||||
if [ -f "${TARBALL}.sha256" ]; then
|
||||
RELEASE_FILES="$RELEASE_FILES ${TARBALL}.sha256"
|
||||
fi
|
||||
|
||||
gh release create "$TAG" \
|
||||
--title "Roo Code CLI v$VERSION" \
|
||||
--notes "$RELEASE_NOTES" \
|
||||
--prerelease \
|
||||
--target "$COMMIT_SHA" \
|
||||
$RELEASE_FILES
|
||||
|
||||
info "Release created!"
|
||||
}
|
||||
|
||||
# Cleanup
|
||||
cleanup() {
|
||||
info "Cleaning up..."
|
||||
cd "$REPO_ROOT"
|
||||
rm -f "$TARBALL" "${TARBALL}.sha256"
|
||||
}
|
||||
|
||||
# Print summary
|
||||
print_summary() {
|
||||
echo ""
|
||||
printf "${GREEN}${BOLD}✓ Release v$VERSION created successfully!${NC}\n"
|
||||
echo ""
|
||||
echo " Release URL: https://github.com/RooCodeInc/Roo-Code/releases/tag/$TAG"
|
||||
echo ""
|
||||
echo " Install with:"
|
||||
echo " curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Print dry-run summary
|
||||
print_dry_run_summary() {
|
||||
echo ""
|
||||
printf "${YELLOW}${BOLD}✓ Dry run complete for v$VERSION${NC}\n"
|
||||
echo ""
|
||||
echo " The following artifacts were created:"
|
||||
echo " - $TARBALL"
|
||||
if [ -f "${TARBALL}.sha256" ]; then
|
||||
echo " - ${TARBALL}.sha256"
|
||||
fi
|
||||
echo ""
|
||||
echo " To complete the release, run without --dry-run:"
|
||||
echo " ./apps/cli/scripts/release.sh $VERSION"
|
||||
echo ""
|
||||
echo " Or manually upload the tarball to a new GitHub release."
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Print local build summary
|
||||
print_local_summary() {
|
||||
echo ""
|
||||
printf "${GREEN}${BOLD}✓ Local build complete for v$VERSION${NC}\n"
|
||||
echo ""
|
||||
echo " Tarball: $REPO_ROOT/$TARBALL"
|
||||
if [ -f "${TARBALL}.sha256" ]; then
|
||||
echo " Checksum: $REPO_ROOT/${TARBALL}.sha256"
|
||||
fi
|
||||
echo ""
|
||||
echo " To install manually:"
|
||||
echo " ROO_LOCAL_TARBALL=$REPO_ROOT/$TARBALL ./apps/cli/install.sh"
|
||||
echo ""
|
||||
echo " Or re-run with --install to install automatically:"
|
||||
echo " ./apps/cli/scripts/release.sh --local --install"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Install locally using the install script
|
||||
install_local() {
|
||||
step "7/8" "Installing locally..."
|
||||
|
||||
TARBALL_PATH="$REPO_ROOT/$TARBALL"
|
||||
|
||||
ROO_LOCAL_TARBALL="$TARBALL_PATH" \
|
||||
ROO_VERSION="$VERSION" \
|
||||
"$CLI_DIR/install.sh" || {
|
||||
error "Local installation failed!"
|
||||
}
|
||||
|
||||
info "Local installation complete!"
|
||||
}
|
||||
|
||||
# Print local install summary
|
||||
print_local_install_summary() {
|
||||
echo ""
|
||||
printf "${GREEN}${BOLD}✓ Local build installed for v$VERSION${NC}\n"
|
||||
echo ""
|
||||
echo " Tarball: $REPO_ROOT/$TARBALL"
|
||||
echo " Installed to: ~/.roo/cli"
|
||||
echo " Binary: ~/.local/bin/roo"
|
||||
echo ""
|
||||
echo " Test it out:"
|
||||
echo " roo --version"
|
||||
echo " roo --help"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
echo ""
|
||||
printf "${BLUE}${BOLD}"
|
||||
echo " ╭─────────────────────────────────╮"
|
||||
echo " │ Roo Code CLI Release Script │"
|
||||
echo " ╰─────────────────────────────────╯"
|
||||
printf "${NC}"
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
printf "${YELLOW} (DRY RUN MODE)${NC}\n"
|
||||
elif [ "$LOCAL_BUILD" = true ]; then
|
||||
printf "${YELLOW} (LOCAL BUILD MODE)${NC}\n"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
detect_platform
|
||||
check_prerequisites
|
||||
get_version
|
||||
get_changelog_content
|
||||
build
|
||||
create_tarball
|
||||
verify_local_install
|
||||
create_checksum
|
||||
|
||||
if [ "$LOCAL_BUILD" = true ]; then
|
||||
step "7/8" "Skipping GitHub checks (local build)"
|
||||
if [ "$LOCAL_INSTALL" = true ]; then
|
||||
install_local
|
||||
print_local_install_summary
|
||||
else
|
||||
step "8/8" "Skipping installation (use --install to auto-install)"
|
||||
print_local_summary
|
||||
fi
|
||||
elif [ "$DRY_RUN" = true ]; then
|
||||
step "7/8" "Skipping existing release check (dry run)"
|
||||
step "8/8" "Skipping GitHub release creation (dry run)"
|
||||
print_dry_run_summary
|
||||
else
|
||||
check_existing_release
|
||||
create_release
|
||||
cleanup
|
||||
print_summary
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
Loading…
Add table
Reference in a new issue