release.yml 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. name: Release
  2. on:
  3. push:
  4. tags:
  5. - "v*"
  6. workflow_dispatch:
  7. inputs:
  8. version:
  9. description: "Release version (e.g., v1.0.0)"
  10. required: true
  11. type: string
  12. prerelease:
  13. description: "Mark as pre-release"
  14. required: false
  15. type: boolean
  16. default: false
  17. jobs:
  18. build-release:
  19. runs-on: ubuntu-latest
  20. permissions:
  21. contents: write
  22. steps:
  23. - name: Checkout
  24. uses: actions/checkout@v4
  25. - name: Set up Bun
  26. uses: ./.github/actions/setup-bun
  27. - name: Set up pnpm
  28. uses: pnpm/action-setup@v4
  29. with:
  30. version: 9
  31. - name: Set up Node.js
  32. uses: actions/setup-node@v4
  33. with:
  34. node-version: "20"
  35. cache: "pnpm"
  36. cache-dependency-path: |
  37. hosts/vscode-plugin/pnpm-lock.yaml
  38. - name: Set up Java
  39. uses: actions/setup-java@v4
  40. with:
  41. distribution: "temurin"
  42. java-version: "21"
  43. - name: Determine version
  44. id: version
  45. run: |
  46. if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
  47. echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
  48. echo "prerelease=${{ github.event.inputs.prerelease }}" >> $GITHUB_OUTPUT
  49. else
  50. echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
  51. if [[ "${GITHUB_REF#refs/tags/}" == *"-"* ]]; then
  52. echo "prerelease=true" >> $GITHUB_OUTPUT
  53. else
  54. echo "prerelease=false" >> $GITHUB_OUTPUT
  55. fi
  56. fi
  57. # version without leading 'v' for use as OPENCODE_VERSION
  58. VER="$(grep -oP '"version":\s*"\K[^"]+' packages/opencode/package.json)"
  59. echo "version_number=$VER" >> $GITHUB_OUTPUT
  60. - name: Build backend binaries
  61. run: ./hosts/scripts/build_opencode.sh
  62. env:
  63. OPENCODE_VERSION: ${{ steps.version.outputs.version_number }}
  64. - name: Install VSCode plugin dependencies (all)
  65. working-directory: hosts/vscode-plugin
  66. run: |
  67. if [ -f pnpm-lock.yaml ]; then
  68. pnpm install --frozen-lockfile
  69. else
  70. npm ci
  71. fi
  72. - name: Compile VSCode plugin TypeScript
  73. working-directory: hosts/vscode-plugin
  74. run: |
  75. if [ -f pnpm-lock.yaml ]; then
  76. pnpm run compile:production
  77. else
  78. npm run compile:production
  79. fi
  80. - name: Package VSCode plugin (standard) from staging
  81. working-directory: hosts/vscode-plugin
  82. run: |
  83. STAGE_DIR="../tmp_opencode_vscode_stage"
  84. rm -rf "$STAGE_DIR"
  85. mkdir -p "$STAGE_DIR"
  86. # Copy minimal required files
  87. cp package.json "$STAGE_DIR/"
  88. cp -R out "$STAGE_DIR/out"
  89. cp -R resources "$STAGE_DIR/resources"
  90. # Exclude gui-only artifacts from standard build
  91. rm -rf "$STAGE_DIR/resources/webgui-app"
  92. [ -f README.md ] && cp README.md "$STAGE_DIR/" || true
  93. [ -f CHANGELOG.md ] && cp CHANGELOG.md "$STAGE_DIR/" || true
  94. [ -f ../../LICENSE ] && cp ../../LICENSE "$STAGE_DIR/" || true
  95. # Sanitize package.json: remove scripts and devDependencies
  96. node -e '
  97. const fs=require("fs");
  98. const path=require("path");
  99. const pkgPath=path.resolve(process.cwd(), "../tmp_opencode_vscode_stage/package.json");
  100. const pkg=JSON.parse(fs.readFileSync(pkgPath, "utf8"));
  101. delete pkg.scripts;
  102. delete pkg.devDependencies;
  103. fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
  104. '
  105. # Package from staging directory without touching dependencies
  106. ( cd "$STAGE_DIR" && npx -y @vscode/vsce package --no-dependencies --out "opencode-standard.vsix" )
  107. # Move artifact back into plugin dir for later collection
  108. mv "$STAGE_DIR"/*.vsix .
  109. # Clean up staging
  110. rm -rf "$STAGE_DIR"
  111. - name: Build webgui for gui-only variant
  112. run: |
  113. # Use bun from repo root since this is a Bun workspace monorepo
  114. cd packages/opencode/webgui
  115. bun install --frozen-lockfile
  116. bun run build
  117. - name: Package VSCode plugin (gui-only) from staging
  118. working-directory: hosts/vscode-plugin
  119. run: |
  120. WEBGUI_DIST="../../packages/opencode/webgui-dist"
  121. if [ ! -d "$WEBGUI_DIST" ]; then
  122. echo "::error::webgui-dist not found at $WEBGUI_DIST"
  123. exit 1
  124. fi
  125. STAGE_DIR="../tmp_opencode_vscode_gui_only_stage"
  126. rm -rf "$STAGE_DIR"
  127. mkdir -p "$STAGE_DIR"
  128. # Copy minimal required files — NO binaries
  129. cp package.json "$STAGE_DIR/"
  130. cp -R out "$STAGE_DIR/out"
  131. mkdir -p "$STAGE_DIR/resources"
  132. # Copy resources except bin/
  133. for item in resources/*; do
  134. [ "$(basename "$item")" = "bin" ] && continue
  135. cp -R "$item" "$STAGE_DIR/resources/"
  136. done
  137. # Embed webgui-dist
  138. cp -R "$WEBGUI_DIST" "$STAGE_DIR/resources/webgui-app"
  139. [ -f README.md ] && cp README.md "$STAGE_DIR/" || true
  140. [ -f CHANGELOG.md ] && cp CHANGELOG.md "$STAGE_DIR/" || true
  141. [ -f ../../LICENSE ] && cp ../../LICENSE "$STAGE_DIR/" || true
  142. # Deep-merge gui-only overrides into package.json, remove scripts/devDependencies
  143. node -e '
  144. const fs=require("fs");
  145. const path=require("path");
  146. function deep(t,s){for(const k of Object.keys(s)){const sv=s[k],tv=t[k];if(Array.isArray(sv)&&Array.isArray(tv)){for(let i=0;i<sv.length;i++){if(i<tv.length&&typeof sv[i]==="object"&&typeof tv[i]==="object"){deep(tv[i],sv[i])}else{tv[i]=sv[i]}}}else if(sv&&typeof sv==="object"&&!Array.isArray(sv)&&tv&&typeof tv==="object"&&!Array.isArray(tv)){deep(tv,sv)}else{t[k]=sv}}return t}
  147. const pkgPath=path.resolve(process.cwd(), "../tmp_opencode_vscode_gui_only_stage/package.json");
  148. const pkg=JSON.parse(fs.readFileSync(pkgPath, "utf8"));
  149. const overrides=JSON.parse(fs.readFileSync("package.gui-only.json", "utf8"));
  150. deep(pkg, overrides);
  151. delete pkg.scripts;
  152. delete pkg.devDependencies;
  153. fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
  154. '
  155. # Package gui-only variant
  156. ( cd "$STAGE_DIR" && npx -y @vscode/vsce package --no-dependencies --out "opencode-gui-only.vsix" )
  157. # Move artifact back into plugin dir for later collection
  158. mv "$STAGE_DIR"/*.vsix .
  159. # Clean up staging
  160. rm -rf "$STAGE_DIR"
  161. - name: Build JetBrains plugin distribution (standard)
  162. uses: gradle/gradle-build-action@v2
  163. with:
  164. gradle-version: 8.7
  165. arguments: buildPlugin
  166. build-root-directory: hosts/jetbrains-plugin
  167. - name: Build JetBrains plugin distribution (gui-only)
  168. uses: gradle/gradle-build-action@v2
  169. with:
  170. gradle-version: 8.7
  171. arguments: buildPlugin -PguiOnly=true -PwebguiDist=../../packages/opencode/webgui-dist
  172. build-root-directory: hosts/jetbrains-plugin
  173. - name: Prepare release artifacts
  174. run: |
  175. mkdir -p release-artifacts
  176. # Copy and rename VSCode standard extension
  177. if [ -f hosts/vscode-plugin/opencode-standard.vsix ]; then
  178. cp hosts/vscode-plugin/opencode-standard.vsix \
  179. "release-artifacts/opencode-vscode-${{ steps.version.outputs.version }}.vsix"
  180. fi
  181. # Copy and rename VSCode gui-only extension
  182. if [ -f hosts/vscode-plugin/opencode-gui-only.vsix ]; then
  183. cp hosts/vscode-plugin/opencode-gui-only.vsix \
  184. "release-artifacts/opencode-vscode-gui-only-${{ steps.version.outputs.version }}.vsix"
  185. fi
  186. # Find and copy JetBrains plugins (both variants)
  187. find hosts/jetbrains-plugin/build/distributions -name "*.zip" -type f | while read -r zipfile; do
  188. if [[ "$(basename "$zipfile")" == *"gui-only"* ]]; then
  189. cp "$zipfile" "release-artifacts/opencode-jetbrains-gui-only-${{ steps.version.outputs.version }}.zip"
  190. else
  191. cp "$zipfile" "release-artifacts/opencode-jetbrains-${{ steps.version.outputs.version }}.zip"
  192. fi
  193. done
  194. ls -la
  195. - name: Create Release
  196. uses: softprops/action-gh-release@v1
  197. with:
  198. tag_name: ${{ steps.version.outputs.version }}
  199. name: OpenCode Release ${{ steps.version.outputs.version }}
  200. prerelease: ${{ steps.version.outputs.prerelease }}
  201. generate_release_notes: true
  202. files: |
  203. release-artifacts/*
  204. body: |
  205. ## OpenCode Release ${{ steps.version.outputs.version }}
  206. This release includes:
  207. - **VSCode Extension (standard)**: `opencode-vscode-${{ steps.version.outputs.version }}.vsix` — bundles opencode binaries
  208. - **VSCode Extension (gui-only)**: `opencode-vscode-gui-only-${{ steps.version.outputs.version }}.vsix` — lightweight, uses system `opencode` binary
  209. - **JetBrains Plugin (standard)**: `opencode-jetbrains-${{ steps.version.outputs.version }}.zip` — bundles opencode binaries
  210. - **JetBrains Plugin (gui-only)**: `opencode-jetbrains-gui-only-${{ steps.version.outputs.version }}.zip` — lightweight, uses system `opencode` binary
  211. ### Installation Instructions
  212. #### VSCode Extension (standard — includes binaries)
  213. 1. Download `opencode-vscode-${{ steps.version.outputs.version }}.vsix`
  214. 2. Install using: `code --install-extension opencode-vscode-${{ steps.version.outputs.version }}.vsix`
  215. #### VSCode Extension (gui-only — requires system opencode)
  216. 1. Ensure `opencode` is installed and available in your PATH
  217. 2. Download `opencode-vscode-gui-only-${{ steps.version.outputs.version }}.vsix`
  218. 3. Install using: `code --install-extension opencode-vscode-gui-only-${{ steps.version.outputs.version }}.vsix`
  219. #### JetBrains Plugin (standard — includes binaries)
  220. 1. Download `opencode-jetbrains-${{ steps.version.outputs.version }}.zip`
  221. 2. In your JetBrains IDE, go to Settings → Plugins → Install Plugin from Disk
  222. 3. Select the downloaded zip file
  223. #### JetBrains Plugin (gui-only — requires system opencode)
  224. 1. Ensure `opencode` is installed and available in your PATH
  225. 2. Download `opencode-jetbrains-gui-only-${{ steps.version.outputs.version }}.zip`
  226. 3. In your JetBrains IDE, go to Settings → Plugins → Install Plugin from Disk
  227. 4. Select the downloaded zip file
  228. ### What's Changed
  229. See the auto-generated release notes below for detailed changes.
  230. test-artifacts:
  231. runs-on: ubuntu-latest
  232. needs: build-release
  233. if: always()
  234. steps:
  235. - name: Download artifacts
  236. uses: actions/download-artifact@v4
  237. with:
  238. name: release-artifacts
  239. path: ./test-artifacts
  240. continue-on-error: true
  241. - name: Verify artifacts
  242. run: |
  243. echo "Checking release artifacts..."
  244. ls -la ./test-artifacts/ || echo "No artifacts found"
  245. # Check if VSCode standard extension exists
  246. if ls ./test-artifacts/opencode-vscode-v*.vsix 1> /dev/null 2>&1; then
  247. echo "✓ VSCode extension (standard) found"
  248. else
  249. echo "✗ VSCode extension (standard) missing"
  250. fi
  251. # Check if VSCode gui-only extension exists
  252. if ls ./test-artifacts/opencode-vscode-gui-only-*.vsix 1> /dev/null 2>&1; then
  253. echo "✓ VSCode extension (gui-only) found"
  254. else
  255. echo "✗ VSCode extension (gui-only) missing"
  256. fi
  257. # Check if JetBrains standard plugin exists
  258. if ls ./test-artifacts/opencode-jetbrains-*.zip 1> /dev/null 2>&1 && ! ls ./test-artifacts/opencode-jetbrains-gui-only-*.zip 1> /dev/null 2>&1; then
  259. echo "✓ JetBrains plugin (standard) found"
  260. else
  261. echo "✗ JetBrains plugin (standard) missing"
  262. fi
  263. # Check if JetBrains gui-only plugin exists
  264. if ls ./test-artifacts/opencode-jetbrains-gui-only-*.zip 1> /dev/null 2>&1; then
  265. echo "✓ JetBrains plugin (gui-only) found"
  266. else
  267. echo "✗ JetBrains plugin (gui-only) missing"
  268. fi