cli-release.yml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. name: CLI Release
  2. on:
  3. workflow_dispatch:
  4. inputs:
  5. version:
  6. description: 'Version to release (e.g., 0.1.0). Leave empty to use package.json version.'
  7. required: false
  8. type: string
  9. dry_run:
  10. description: 'Dry run (build and test but do not create release).'
  11. required: false
  12. type: boolean
  13. default: false
  14. jobs:
  15. # Build CLI for each platform.
  16. build:
  17. strategy:
  18. fail-fast: false
  19. matrix:
  20. include:
  21. - os: macos-latest
  22. platform: darwin-arm64
  23. runs-on: macos-latest
  24. - os: ubuntu-latest
  25. platform: linux-x64
  26. runs-on: ubuntu-latest
  27. - os: ubuntu-24.04-arm
  28. platform: linux-arm64
  29. runs-on: ubuntu-24.04-arm
  30. runs-on: ${{ matrix.runs-on }}
  31. steps:
  32. - name: Checkout code
  33. uses: actions/checkout@v4
  34. with:
  35. fetch-depth: 0
  36. - name: Setup Node.js and pnpm
  37. uses: ./.github/actions/setup-node-pnpm
  38. - name: Get version
  39. id: version
  40. run: |
  41. if [ -n "${{ inputs.version }}" ]; then
  42. VERSION="${{ inputs.version }}"
  43. else
  44. VERSION=$(node -p "require('./apps/cli/package.json').version")
  45. fi
  46. echo "version=$VERSION" >> $GITHUB_OUTPUT
  47. echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
  48. echo "Using version: $VERSION"
  49. - name: Build extension bundle
  50. run: pnpm bundle
  51. - name: Build CLI
  52. run: pnpm --filter @roo-code/cli build
  53. - name: Create release tarball
  54. id: tarball
  55. env:
  56. VERSION: ${{ steps.version.outputs.version }}
  57. PLATFORM: ${{ matrix.platform }}
  58. run: |
  59. RELEASE_DIR="roo-cli-${PLATFORM}"
  60. TARBALL="roo-cli-${PLATFORM}.tar.gz"
  61. # Clean up any previous build.
  62. rm -rf "$RELEASE_DIR"
  63. rm -f "$TARBALL"
  64. # Create directory structure.
  65. mkdir -p "$RELEASE_DIR/bin"
  66. mkdir -p "$RELEASE_DIR/lib"
  67. mkdir -p "$RELEASE_DIR/extension"
  68. # Copy CLI dist files.
  69. echo "Copying CLI files..."
  70. cp -r apps/cli/dist/* "$RELEASE_DIR/lib/"
  71. # Create package.json for npm install.
  72. echo "Creating package.json..."
  73. node -e "
  74. const pkg = require('./apps/cli/package.json');
  75. const newPkg = {
  76. name: '@roo-code/cli',
  77. version: '$VERSION',
  78. type: 'module',
  79. dependencies: {
  80. '@inkjs/ui': pkg.dependencies['@inkjs/ui'],
  81. '@trpc/client': pkg.dependencies['@trpc/client'],
  82. 'commander': pkg.dependencies.commander,
  83. 'fuzzysort': pkg.dependencies.fuzzysort,
  84. 'ink': pkg.dependencies.ink,
  85. 'p-wait-for': pkg.dependencies['p-wait-for'],
  86. 'react': pkg.dependencies.react,
  87. 'superjson': pkg.dependencies.superjson,
  88. 'zustand': pkg.dependencies.zustand
  89. }
  90. };
  91. console.log(JSON.stringify(newPkg, null, 2));
  92. " > "$RELEASE_DIR/package.json"
  93. # Copy extension bundle.
  94. echo "Copying extension bundle..."
  95. cp -r src/dist/* "$RELEASE_DIR/extension/"
  96. # Add package.json to extension directory for CommonJS.
  97. echo '{"type": "commonjs"}' > "$RELEASE_DIR/extension/package.json"
  98. # Find and copy ripgrep binary.
  99. echo "Looking for ripgrep binary..."
  100. RIPGREP_PATH=$(find node_modules -path "*/@vscode/ripgrep/bin/rg" -type f 2>/dev/null | head -1)
  101. if [ -n "$RIPGREP_PATH" ] && [ -f "$RIPGREP_PATH" ]; then
  102. echo "Found ripgrep at: $RIPGREP_PATH"
  103. mkdir -p "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin"
  104. cp "$RIPGREP_PATH" "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/"
  105. chmod +x "$RELEASE_DIR/node_modules/@vscode/ripgrep/bin/rg"
  106. mkdir -p "$RELEASE_DIR/bin"
  107. cp "$RIPGREP_PATH" "$RELEASE_DIR/bin/"
  108. chmod +x "$RELEASE_DIR/bin/rg"
  109. else
  110. echo "Warning: ripgrep binary not found"
  111. fi
  112. # Create the wrapper script
  113. echo "Creating wrapper script..."
  114. printf '%s\n' '#!/usr/bin/env node' \
  115. '' \
  116. "import { fileURLToPath } from 'url';" \
  117. "import { dirname, join } from 'path';" \
  118. '' \
  119. 'const __filename = fileURLToPath(import.meta.url);' \
  120. 'const __dirname = dirname(__filename);' \
  121. '' \
  122. '// Set environment variables for the CLI' \
  123. "process.env.ROO_CLI_ROOT = join(__dirname, '..');" \
  124. "process.env.ROO_EXTENSION_PATH = join(__dirname, '..', 'extension');" \
  125. "process.env.ROO_RIPGREP_PATH = join(__dirname, 'rg');" \
  126. '' \
  127. '// Import and run the actual CLI' \
  128. "await import(join(__dirname, '..', 'lib', 'index.js'));" \
  129. > "$RELEASE_DIR/bin/roo"
  130. chmod +x "$RELEASE_DIR/bin/roo"
  131. # Create empty .env file.
  132. touch "$RELEASE_DIR/.env"
  133. # Create tarball.
  134. echo "Creating tarball..."
  135. tar -czvf "$TARBALL" "$RELEASE_DIR"
  136. # Clean up release directory.
  137. rm -rf "$RELEASE_DIR"
  138. # Create checksum.
  139. if command -v sha256sum &> /dev/null; then
  140. sha256sum "$TARBALL" > "${TARBALL}.sha256"
  141. elif command -v shasum &> /dev/null; then
  142. shasum -a 256 "$TARBALL" > "${TARBALL}.sha256"
  143. fi
  144. echo "tarball=$TARBALL" >> $GITHUB_OUTPUT
  145. echo "Created: $TARBALL"
  146. ls -la "$TARBALL"
  147. - name: Verify tarball
  148. env:
  149. PLATFORM: ${{ matrix.platform }}
  150. run: |
  151. TARBALL="roo-cli-${PLATFORM}.tar.gz"
  152. # Create temp directory for verification.
  153. VERIFY_DIR=$(mktemp -d)
  154. # Extract and verify structure.
  155. tar -xzf "$TARBALL" -C "$VERIFY_DIR"
  156. echo "Verifying tarball contents..."
  157. ls -la "$VERIFY_DIR/roo-cli-${PLATFORM}/"
  158. # Check required files exist.
  159. test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/bin/roo" || { echo "Missing bin/roo"; exit 1; }
  160. test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/lib/index.js" || { echo "Missing lib/index.js"; exit 1; }
  161. test -f "$VERIFY_DIR/roo-cli-${PLATFORM}/package.json" || { echo "Missing package.json"; exit 1; }
  162. test -d "$VERIFY_DIR/roo-cli-${PLATFORM}/extension" || { echo "Missing extension directory"; exit 1; }
  163. echo "Tarball verification passed!"
  164. # Cleanup.
  165. rm -rf "$VERIFY_DIR"
  166. - name: Upload artifact
  167. uses: actions/upload-artifact@v4
  168. with:
  169. name: cli-${{ matrix.platform }}
  170. path: |
  171. roo-cli-${{ matrix.platform }}.tar.gz
  172. roo-cli-${{ matrix.platform }}.tar.gz.sha256
  173. retention-days: 7
  174. # Create GitHub release with all platform artifacts.
  175. release:
  176. needs: build
  177. runs-on: ubuntu-latest
  178. if: ${{ !inputs.dry_run }}
  179. permissions:
  180. contents: write
  181. steps:
  182. - name: Checkout code
  183. uses: actions/checkout@v4
  184. - name: Get version
  185. id: version
  186. run: |
  187. if [ -n "${{ inputs.version }}" ]; then
  188. VERSION="${{ inputs.version }}"
  189. else
  190. VERSION=$(node -p "require('./apps/cli/package.json').version")
  191. fi
  192. echo "version=$VERSION" >> $GITHUB_OUTPUT
  193. echo "tag=cli-v$VERSION" >> $GITHUB_OUTPUT
  194. - name: Download all artifacts
  195. uses: actions/download-artifact@v4
  196. with:
  197. path: artifacts
  198. - name: Prepare release files
  199. run: |
  200. mkdir -p release
  201. find artifacts -name "*.tar.gz" -exec cp {} release/ \;
  202. find artifacts -name "*.sha256" -exec cp {} release/ \;
  203. ls -la release/
  204. - name: Extract changelog
  205. id: changelog
  206. env:
  207. VERSION: ${{ steps.version.outputs.version }}
  208. run: |
  209. CHANGELOG_FILE="apps/cli/CHANGELOG.md"
  210. if [ -f "$CHANGELOG_FILE" ]; then
  211. # Extract content between version headers.
  212. CONTENT=$(awk -v version="$VERSION" '
  213. BEGIN { found = 0; content = ""; target = "[" version "]" }
  214. /^## \[/ {
  215. if (found) { exit }
  216. if (index($0, target) > 0) { found = 1; next }
  217. }
  218. found { content = content $0 "\n" }
  219. END { print content }
  220. ' "$CHANGELOG_FILE")
  221. if [ -n "$CONTENT" ]; then
  222. echo "Found changelog content"
  223. echo "content<<EOF" >> $GITHUB_OUTPUT
  224. echo "$CONTENT" >> $GITHUB_OUTPUT
  225. echo "EOF" >> $GITHUB_OUTPUT
  226. else
  227. echo "No changelog content found for version $VERSION"
  228. echo "content=" >> $GITHUB_OUTPUT
  229. fi
  230. else
  231. echo "No changelog file found"
  232. echo "content=" >> $GITHUB_OUTPUT
  233. fi
  234. - name: Generate checksums summary
  235. id: checksums
  236. run: |
  237. echo "checksums<<EOF" >> $GITHUB_OUTPUT
  238. cat release/*.sha256 >> $GITHUB_OUTPUT
  239. echo "EOF" >> $GITHUB_OUTPUT
  240. - name: Check for existing release
  241. id: check_release
  242. env:
  243. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  244. TAG: ${{ steps.version.outputs.tag }}
  245. run: |
  246. if gh release view "$TAG" &> /dev/null; then
  247. echo "exists=true" >> $GITHUB_OUTPUT
  248. else
  249. echo "exists=false" >> $GITHUB_OUTPUT
  250. fi
  251. - name: Delete existing release
  252. if: steps.check_release.outputs.exists == 'true'
  253. env:
  254. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  255. TAG: ${{ steps.version.outputs.tag }}
  256. run: |
  257. echo "Deleting existing release $TAG..."
  258. gh release delete "$TAG" --yes || true
  259. git push origin ":refs/tags/$TAG" || true
  260. - name: Create GitHub Release
  261. env:
  262. GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  263. VERSION: ${{ steps.version.outputs.version }}
  264. TAG: ${{ steps.version.outputs.tag }}
  265. CHANGELOG_CONTENT: ${{ steps.changelog.outputs.content }}
  266. CHECKSUMS: ${{ steps.checksums.outputs.checksums }}
  267. run: |
  268. NOTES_FILE=$(mktemp)
  269. if [ -n "$CHANGELOG_CONTENT" ]; then
  270. echo "## What's New" >> "$NOTES_FILE"
  271. echo "" >> "$NOTES_FILE"
  272. echo "$CHANGELOG_CONTENT" >> "$NOTES_FILE"
  273. echo "" >> "$NOTES_FILE"
  274. fi
  275. echo "## Installation" >> "$NOTES_FILE"
  276. echo "" >> "$NOTES_FILE"
  277. echo '```bash' >> "$NOTES_FILE"
  278. echo "curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh" >> "$NOTES_FILE"
  279. echo '```' >> "$NOTES_FILE"
  280. echo "" >> "$NOTES_FILE"
  281. echo "Or install a specific version:" >> "$NOTES_FILE"
  282. echo '```bash' >> "$NOTES_FILE"
  283. echo "ROO_VERSION=$VERSION curl -fsSL https://raw.githubusercontent.com/RooCodeInc/Roo-Code/main/apps/cli/install.sh | sh" >> "$NOTES_FILE"
  284. echo '```' >> "$NOTES_FILE"
  285. echo "" >> "$NOTES_FILE"
  286. echo "## Requirements" >> "$NOTES_FILE"
  287. echo "" >> "$NOTES_FILE"
  288. echo "- Node.js 20 or higher" >> "$NOTES_FILE"
  289. echo "- macOS Apple Silicon (M1/M2/M3/M4), Linux x64, or Linux ARM64" >> "$NOTES_FILE"
  290. echo "" >> "$NOTES_FILE"
  291. echo "## Usage" >> "$NOTES_FILE"
  292. echo "" >> "$NOTES_FILE"
  293. echo '```bash' >> "$NOTES_FILE"
  294. echo "# Run a task" >> "$NOTES_FILE"
  295. echo 'roo "What is this project?"' >> "$NOTES_FILE"
  296. echo "" >> "$NOTES_FILE"
  297. echo "# See all options" >> "$NOTES_FILE"
  298. echo "roo --help" >> "$NOTES_FILE"
  299. echo '```' >> "$NOTES_FILE"
  300. echo "" >> "$NOTES_FILE"
  301. echo "## Platform Support" >> "$NOTES_FILE"
  302. echo "" >> "$NOTES_FILE"
  303. echo "This release includes binaries for:" >> "$NOTES_FILE"
  304. echo '- `roo-cli-darwin-arm64.tar.gz` - macOS Apple Silicon (M1/M2/M3)' >> "$NOTES_FILE"
  305. echo '- `roo-cli-linux-x64.tar.gz` - Linux x64' >> "$NOTES_FILE"
  306. echo '- `roo-cli-linux-arm64.tar.gz` - Linux ARM64' >> "$NOTES_FILE"
  307. echo "" >> "$NOTES_FILE"
  308. echo "## Checksums" >> "$NOTES_FILE"
  309. echo "" >> "$NOTES_FILE"
  310. echo '```' >> "$NOTES_FILE"
  311. echo "$CHECKSUMS" >> "$NOTES_FILE"
  312. echo '```' >> "$NOTES_FILE"
  313. gh release create "$TAG" \
  314. --title "Roo Code CLI v$VERSION" \
  315. --notes-file "$NOTES_FILE" \
  316. --prerelease \
  317. release/*
  318. rm -f "$NOTES_FILE"
  319. echo "Release created: https://github.com/${{ github.repository }}/releases/tag/$TAG"
  320. # Summary job for dry runs
  321. summary:
  322. needs: build
  323. runs-on: ubuntu-latest
  324. if: ${{ inputs.dry_run }}
  325. steps:
  326. - name: Download all artifacts
  327. uses: actions/download-artifact@v4
  328. with:
  329. path: artifacts
  330. - name: Show build summary
  331. run: |
  332. echo "## Dry Run Complete" >> $GITHUB_STEP_SUMMARY
  333. echo "" >> $GITHUB_STEP_SUMMARY
  334. echo "The following artifacts were built:" >> $GITHUB_STEP_SUMMARY
  335. echo "" >> $GITHUB_STEP_SUMMARY
  336. find artifacts -name "*.tar.gz" | while read f; do
  337. SIZE=$(ls -lh "$f" | awk '{print $5}')
  338. echo "- $(basename $f) ($SIZE)" >> $GITHUB_STEP_SUMMARY
  339. done
  340. echo "" >> $GITHUB_STEP_SUMMARY
  341. echo "### Checksums" >> $GITHUB_STEP_SUMMARY
  342. echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
  343. cat artifacts/*/*.sha256 >> $GITHUB_STEP_SUMMARY
  344. echo "\`\`\`" >> $GITHUB_STEP_SUMMARY