mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-07 16:17:25 +03:00
chore(ci): speed up the beta desktop publish (#55919)
This commit is contained in:
committed by
GitHub
Unverified
parent
deb5cba571
commit
8ba78414d2
@@ -58,6 +58,9 @@ runs:
|
||||
with:
|
||||
path: ${{ steps.cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ steps.cache.outputs.version }}-${{ hashFiles('bun.lock', 'patches/**') }}
|
||||
# Each dev merge changes beta's bun.lock. Beta falls back to the newest cache for the same Bun version
|
||||
# (Bun's cache stores every package version separately) instead of installing everything from npm.
|
||||
restore-keys: ${{ (github.ref_name == 'beta' && format('{0}-{1}-bun-{2}-', runner.os, runner.arch, steps.cache.outputs.version)) || '' }}
|
||||
|
||||
- name: Install setuptools for distutils compatibility
|
||||
run: python3 -m pip install setuptools || pip install setuptools || true
|
||||
|
||||
+173
-13
@@ -28,12 +28,18 @@ on:
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}-${{ (github.ref_name == 'dev' && (inputs.version || inputs.bump) && 'release') || inputs.version || inputs.bump }}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ (github.ref_name == 'dev' && (inputs.version || inputs.bump) && 'release') || inputs.version || inputs.bump }}
|
||||
# Beta ships only the newest commit to the team. A cancelled beta run can leave unreferenced files in R2, but only
|
||||
# its publish job records a release, in one request.
|
||||
cancel-in-progress: ${{ github.ref_name == 'beta' }}
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
packages: write
|
||||
# Beta desktop jobs poll this run's artifacts while the CLI builds.
|
||||
actions: read
|
||||
|
||||
env:
|
||||
OPENCODE_CHANNEL: ${{ (github.ref_name == 'dev' && !inputs.bump && !inputs.version && 'dev') || '' }}
|
||||
@@ -42,15 +48,35 @@ jobs:
|
||||
version:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
env:
|
||||
BETA: ${{ github.ref_name == 'beta' }}
|
||||
steps:
|
||||
# A beta version only depends on the run number, so beta computes it without a checkout or an install.
|
||||
- name: Beta version
|
||||
id: beta
|
||||
if: env.BETA == 'true'
|
||||
run: |
|
||||
# Matches packages/script: 0.0.0-<channel>-<run number>[.<attempt>] for preview channels.
|
||||
version="0.0.0-beta-${GITHUB_RUN_NUMBER}"
|
||||
if [ "$GITHUB_RUN_ATTEMPT" != "1" ]; then version="${version}.${GITHUB_RUN_ATTEMPT}"; fi
|
||||
{
|
||||
echo "version=$version"
|
||||
echo "release=true"
|
||||
echo "tag=v$version"
|
||||
echo "repo=anomalyco/opencode-beta"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
if: env.BETA != 'true'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
if: env.BETA != 'true'
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
if: env.BETA != 'true'
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
@@ -61,6 +87,7 @@ jobs:
|
||||
run: bun i -g opencode-ai
|
||||
|
||||
- id: version
|
||||
if: env.BETA != 'true'
|
||||
run: |
|
||||
./script/version.ts
|
||||
env:
|
||||
@@ -70,16 +97,17 @@ jobs:
|
||||
OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }}
|
||||
GH_REPO: ${{ (github.ref_name == 'beta' && 'anomalyco/opencode-beta') || github.repository }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo }}
|
||||
version: ${{ steps.version.outputs.version || steps.beta.outputs.version }}
|
||||
release: ${{ steps.version.outputs.release || steps.beta.outputs.release }}
|
||||
tag: ${{ steps.version.outputs.tag || steps.beta.outputs.tag }}
|
||||
repo: ${{ steps.version.outputs.repo || steps.beta.outputs.repo }}
|
||||
|
||||
build-cli:
|
||||
needs: version
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
if: github.repository == 'anomalyco/opencode'
|
||||
# Beta builds only the CLIs its desktop app bundles, in build-cli-beta.
|
||||
if: github.repository == 'anomalyco/opencode' && github.ref_name != 'beta'
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
with:
|
||||
@@ -138,6 +166,48 @@ jobs:
|
||||
outputs:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
|
||||
# Beta publishes only the desktop app, so it builds just the 6 CLIs the desktop bundles, each in its own job and
|
||||
# artifact. Desktop jobs start alongside and wait for their CLI only when they need it.
|
||||
build-cli-beta:
|
||||
needs: version
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
if: github.repository == 'anomalyco/opencode' && github.ref_name == 'beta'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# macOS first: notarization makes those desktop jobs the longest.
|
||||
target:
|
||||
- darwin-x64-baseline
|
||||
- darwin-arm64
|
||||
- linux-arm64
|
||||
- windows-arm64
|
||||
- windows-x64-baseline
|
||||
- linux-x64-baseline
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
with:
|
||||
bun-version: 1.4.2
|
||||
|
||||
- name: Build CLI
|
||||
run: ./packages/cli/script/build.ts --target=opencode-${{ matrix.target }}
|
||||
env:
|
||||
BUN_COMPILE_RELEASE: bun-v1.4.2
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_RELEASE: ${{ needs.version.outputs.release }}
|
||||
GH_REPO: ${{ needs.version.outputs.repo }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: opencode-cli-unsigned-cli-${{ matrix.target }}
|
||||
path: packages/cli/dist/cli-${{ matrix.target }}
|
||||
# Deflate level 1 compresses a CLI binary twice as fast as the default for 7% more bytes.
|
||||
compression-level: 1
|
||||
if-no-files-found: error
|
||||
|
||||
sign-cli-macos:
|
||||
needs: build-cli
|
||||
runs-on: macos-26
|
||||
@@ -344,10 +414,11 @@ jobs:
|
||||
|
||||
build-electron:
|
||||
# Desktop packaging re-signs the bundled CLI on macOS and Windows, so it starts from the unsigned build.
|
||||
# Beta skips build-cli and waits for its own CLI from build-cli-beta just before Prepare.
|
||||
needs:
|
||||
- version
|
||||
- build-cli
|
||||
if: github.repository == 'anomalyco/opencode' && ((github.ref_name == 'dev' && needs.version.outputs.release != '') || github.ref_name == 'beta')
|
||||
if: ${{ !cancelled() && !failure() && github.repository == 'anomalyco/opencode' && ((github.ref_name == 'dev' && needs.version.outputs.release != '') || github.ref_name == 'beta') }}
|
||||
continue-on-error: false
|
||||
env:
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
@@ -359,34 +430,44 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Beta packages macOS x64 on Apple Silicon and Windows arm64 on Blacksmith. electron-builder's afterPack rejects
|
||||
# a bundle whose CLI or node-pty does not match the target CPU.
|
||||
settings:
|
||||
- host: macos-26-intel
|
||||
- host: ${{ (github.ref_name == 'beta' && 'macos-26') || 'macos-26-intel' }}
|
||||
target: x86_64-apple-darwin
|
||||
cli: darwin-x64-baseline
|
||||
platform_flag: --mac --x64
|
||||
bun_install_flags: --os=darwin --cpu=x64
|
||||
bun_install_flags: ${{ (github.ref_name == 'beta' && '--os=darwin --cpu=*') || '--os=darwin --cpu=x64' }}
|
||||
- host: macos-26
|
||||
target: aarch64-apple-darwin
|
||||
cli: darwin-arm64
|
||||
platform_flag: --mac --arm64
|
||||
bun_install_flags: --os=darwin --cpu=arm64
|
||||
# github-hosted: blacksmith lacks ARM64 MSVC cross-compilation toolchain
|
||||
- host: "windows-2025"
|
||||
# Releases keep github-hosted Windows from the Tauri build, which needed an ARM64 MSVC toolchain; Electron
|
||||
# packaging compiles nothing.
|
||||
- host: ${{ (github.ref_name == 'beta' && 'blacksmith-4vcpu-windows-2025') || 'windows-2025' }}
|
||||
target: aarch64-pc-windows-msvc
|
||||
cli: windows-arm64
|
||||
platform_flag: --win --arm64
|
||||
bun_install_flags: --cpu=*
|
||||
- host: "blacksmith-4vcpu-windows-2025"
|
||||
target: x86_64-pc-windows-msvc
|
||||
cli: windows-x64-baseline
|
||||
platform_flag: --win
|
||||
- host: "blacksmith-4vcpu-ubuntu-2404"
|
||||
target: x86_64-unknown-linux-gnu
|
||||
cli: linux-x64-baseline
|
||||
platform_flag: --linux
|
||||
- host: "blacksmith-4vcpu-ubuntu-2404-arm"
|
||||
target: aarch64-unknown-linux-gnu
|
||||
cli: linux-arm64
|
||||
platform_flag: --linux --arm64
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: github.ref_name != 'beta'
|
||||
with:
|
||||
name: opencode-preview-cli-unsigned
|
||||
path: packages/cli/dist
|
||||
@@ -442,6 +523,48 @@ jobs:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
# Beta trades larger installers for faster packaging. These variables must stay unset elsewhere:
|
||||
# electron-builder rejects an empty ELECTRON_BUILDER_COMPRESSION_LEVEL.
|
||||
- name: Beta packaging settings
|
||||
if: github.ref_name == 'beta'
|
||||
shell: bash
|
||||
run: |
|
||||
# fpm sets XZ_OPT for the deb and rpm level, but xz still reads XZ_DEFAULTS, so it uses every CPU.
|
||||
if [ "$RUNNER_OS" = "Linux" ]; then echo "XZ_DEFAULTS=-T0" >> "$GITHUB_ENV"; fi
|
||||
# The macOS zip (7z deflate, default -mx=7) and DMG (UDZO zlib, default level 9) at level 1.
|
||||
if [ "$RUNNER_OS" = "macOS" ]; then echo "ELECTRON_BUILDER_COMPRESSION_LEVEL=1" >> "$GITHUB_ENV"; fi
|
||||
|
||||
# Beta builds the app before its CLI exists. `bun run build` would run the prebuild hook, which copies the CLI;
|
||||
# Prepare runs it once the CLI is here.
|
||||
- name: Build (beta)
|
||||
if: github.ref_name == 'beta'
|
||||
run: bun run electron-vite build
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=4096
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
OPENCODE_CHANNEL: beta
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
SENTRY_ORG: ${{ vars.SENTRY_ORG }}
|
||||
SENTRY_PROJECT: ${{ vars.WEB_SENTRY_PROJECT }}
|
||||
SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
VITE_SENTRY_DSN: ${{ vars.WEB_SENTRY_DSN }}
|
||||
VITE_SENTRY_ENVIRONMENT: beta
|
||||
VITE_SENTRY_RELEASE: desktop@${{ needs.version.outputs.version }}
|
||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Wait for the CLI (beta)
|
||||
if: github.ref_name == 'beta'
|
||||
run: bun ./script/wait-for-artifact.ts opencode-cli-unsigned-cli-${{ matrix.settings.cli }} "build-cli-beta (${{ matrix.settings.cli }})"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: github.ref_name == 'beta'
|
||||
with:
|
||||
name: opencode-cli-unsigned-cli-${{ matrix.settings.cli }}
|
||||
path: packages/cli/dist/cli-${{ matrix.settings.cli }}
|
||||
|
||||
- name: Prepare
|
||||
run: bun ./scripts/prepare.ts
|
||||
working-directory: packages/desktop
|
||||
@@ -452,6 +575,7 @@ jobs:
|
||||
OPENCODE_CLI_DIST: ${{ github.workspace }}/packages/cli/dist
|
||||
|
||||
- name: Build
|
||||
if: github.ref_name != 'beta'
|
||||
run: bun run build
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
@@ -492,7 +616,8 @@ jobs:
|
||||
OPENCODE_CLI_TARGET: ${{ matrix.settings.target }}
|
||||
|
||||
- name: Create macOS .app.tar.gz
|
||||
if: runner.os == 'macOS' && needs.version.outputs.release
|
||||
# Only the retired Tauri updater feed read these archives.
|
||||
if: runner.os == 'macOS' && needs.version.outputs.release && github.ref_name != 'beta'
|
||||
working-directory: packages/desktop/dist
|
||||
run: |
|
||||
if [[ "${{ matrix.settings.target }}" == "x86_64-apple-darwin" ]]; then
|
||||
@@ -529,10 +654,29 @@ jobs:
|
||||
}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: github.ref_name != 'beta'
|
||||
with:
|
||||
name: opencode-desktop-${{ matrix.settings.target }}
|
||||
path: packages/desktop/dist/*
|
||||
|
||||
# Beta uploads its installers to R2 here instead of passing them through an artifact to publish, which only
|
||||
# records the release from the returned file list.
|
||||
- name: Upload to R2 (beta)
|
||||
if: github.ref_name == 'beta'
|
||||
run: bun ./scripts/upload.ts dist dist/uploaded-${{ matrix.settings.target }}.json
|
||||
working-directory: packages/desktop
|
||||
env:
|
||||
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
|
||||
CLOUDFLARE_ACCOUNT_ID: 15d29c8639fd3733b1b5486a2acfd968
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: github.ref_name == 'beta'
|
||||
with:
|
||||
name: desktop-uploaded-${{ matrix.settings.target }}
|
||||
path: packages/desktop/dist/uploaded-${{ matrix.settings.target }}.json
|
||||
if-no-files-found: error
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
if: needs.version.outputs.release
|
||||
with:
|
||||
@@ -543,6 +687,7 @@ jobs:
|
||||
needs:
|
||||
- version
|
||||
- build-cli
|
||||
- build-cli-beta
|
||||
- sign-cli-macos
|
||||
- build-node-app-archive
|
||||
- build-node-cli
|
||||
@@ -555,7 +700,9 @@ jobs:
|
||||
|
||||
- uses: ./.github/actions/setup-bun
|
||||
|
||||
# Beta only records the desktop release, so it skips the Docker, git and AUR setup.
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.ref_name != 'beta'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -563,9 +710,11 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
if: github.ref_name != 'beta'
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: github.ref_name != 'beta'
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
@@ -611,20 +760,29 @@ jobs:
|
||||
path: /tmp/latest-yml
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: needs.version.outputs.release
|
||||
if: needs.version.outputs.release && github.ref_name != 'beta'
|
||||
with:
|
||||
pattern: opencode-desktop-*
|
||||
path: /tmp/desktop
|
||||
merge-multiple: true
|
||||
|
||||
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
if: github.ref_name == 'beta'
|
||||
with:
|
||||
pattern: desktop-uploaded-*
|
||||
path: /tmp/desktop-uploaded
|
||||
merge-multiple: true
|
||||
|
||||
- name: Setup git committer
|
||||
id: committer
|
||||
if: github.ref_name != 'beta'
|
||||
uses: ./.github/actions/setup-git-committer
|
||||
with:
|
||||
opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
|
||||
opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
|
||||
|
||||
- name: Cache apt packages (AUR)
|
||||
if: github.ref_name != 'beta'
|
||||
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
||||
with:
|
||||
path: /var/cache/apt/archives
|
||||
@@ -633,6 +791,7 @@ jobs:
|
||||
${{ runner.os }}-apt-aur-
|
||||
|
||||
- name: Setup SSH for AUR
|
||||
if: github.ref_name != 'beta'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pacman-package-manager
|
||||
@@ -656,6 +815,7 @@ jobs:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
OPENCODE_DESKTOP_DIST: /tmp/desktop
|
||||
OPENCODE_DESKTOP_UPLOADED: ${{ (github.ref_name == 'beta' && '/tmp/desktop-uploaded') || '' }}
|
||||
WINGET_GITHUB_TOKEN: ${{ secrets.WINGET_GITHUB_TOKEN }}
|
||||
CLOUDFLARE_ACCOUNT_ID: 15d29c8639fd3733b1b5486a2acfd968
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
@@ -20,16 +20,20 @@ export async function buildAppArchive(channel: string, options?: { skipBuild?: b
|
||||
.map(async (key) => {
|
||||
const body = Buffer.from(await Bun.file(path.join(root, "dist", key)).arrayBuffer())
|
||||
// Independent entries let the server materialize only assets the browser requests.
|
||||
return [key, await compress(body)] as const
|
||||
return [key, await compress(body, channel)] as const
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// The assets compress once per release, so they take brotli's best quality: about 11% smaller than quality 6 for the
|
||||
// web UI. Each runs on the thread pool, which keeps the build to about a minute.
|
||||
function compress(body: Buffer) {
|
||||
// web UI. Each runs on the thread pool, which keeps the build to about a minute. Beta builds on every push and only
|
||||
// bundles the CLI into the desktop app, so it keeps quality 6, which takes a few seconds.
|
||||
function compress(body: Buffer, channel: string) {
|
||||
return promisify(brotliCompress)(body, {
|
||||
params: { [constants.BROTLI_PARAM_QUALITY]: 11, [constants.BROTLI_PARAM_SIZE_HINT]: body.length },
|
||||
params: {
|
||||
[constants.BROTLI_PARAM_QUALITY]: channel === "beta" ? 6 : 11,
|
||||
[constants.BROTLI_PARAM_SIZE_HINT]: body.length,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -266,7 +266,11 @@ async function compileExecutable(item: (typeof allTargets)[number]) {
|
||||
function compileReleaseAssets(release: string) {
|
||||
const existing = releaseAssets.get(release)
|
||||
if (existing) return existing
|
||||
const pending = fetch(`https://api.github.com/repos/oven-sh/bun/releases/tags/${release}?cache=${Date.now()}`)
|
||||
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN
|
||||
// Beta builds each CLI in its own job; authenticated requests avoid the per-IP limit on shared runners.
|
||||
const pending = fetch(`https://api.github.com/repos/oven-sh/bun/releases/tags/${release}?cache=${Date.now()}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
.then(async (response) => {
|
||||
if (!response.ok) throw new Error(`Failed to resolve Bun release ${release}: ${response.status}`)
|
||||
const data: unknown = await response.json()
|
||||
|
||||
@@ -27,6 +27,10 @@ const legacyDesktopEntryFpm = `${legacyDesktopEntry}=/usr/share/applications/ope
|
||||
const metainfoFpm = (appId: string) =>
|
||||
`${path.join(packageDir, "resources", `${appId}.metainfo.xml`)}=/usr/share/metainfo/${appId}.metainfo.xml`
|
||||
|
||||
// Beta packages every commit for the team, so it trades about 5% (deb) and 17% (rpm) larger files for xz -1, which
|
||||
// compresses the payload about 3x faster than the deb default (-3) and 5x faster than the rpm default (-9).
|
||||
const betaCompressionFpm = (target: "deb" | "rpm") => [`--${target}-compression-level`, "1"]
|
||||
|
||||
async function signWindows(configuration: { path: string }) {
|
||||
if (process.platform !== "win32") return
|
||||
|
||||
@@ -238,8 +242,8 @@ export function getConfig() {
|
||||
url: "https://opencode.ai/update/api/beta/desktop/opencode/",
|
||||
channel: "latest",
|
||||
},
|
||||
deb: { fpm: [metainfoFpm(appId)] },
|
||||
rpm: { packageName: "opencode-beta", fpm: [metainfoFpm(appId)] },
|
||||
deb: { fpm: [...betaCompressionFpm("deb"), metainfoFpm(appId)] },
|
||||
rpm: { packageName: "opencode-beta", fpm: [...betaCompressionFpm("rpm"), metainfoFpm(appId)] },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { Script } from "@opencode/script"
|
||||
import { Schema } from "effect"
|
||||
import { UpdateArtifact } from "../../../script/update-artifact"
|
||||
|
||||
const dryRun = process.argv.includes("--dry-run")
|
||||
@@ -10,19 +11,14 @@ if (!Script.release) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const directory = process.env.OPENCODE_DESKTOP_DIST
|
||||
const UploadedFiles = Schema.fromJsonString(
|
||||
Schema.Record(Schema.String, Schema.Struct({ url: Schema.String, sha256: Schema.String, size: Schema.Number })),
|
||||
)
|
||||
|
||||
if (!directory) throw new Error("OPENCODE_DESKTOP_DIST is required")
|
||||
// Beta build jobs upload their own platform's files (scripts/upload.ts) and pass only the returned file lists here.
|
||||
const uploadedDirectory = process.env.OPENCODE_DESKTOP_UPLOADED
|
||||
|
||||
const files = (
|
||||
await Array.fromAsync(
|
||||
new Bun.Glob("*.{exe,blockmap,dmg,zip,AppImage,deb,rpm,app.tar.gz}").scan({ cwd: directory, absolute: true }),
|
||||
)
|
||||
).sort()
|
||||
|
||||
if (!files.length) throw new Error("No desktop release files found")
|
||||
|
||||
const uploaded = await UpdateArtifact.upload({ version: Script.version, files, dryRun })
|
||||
const uploaded = uploadedDirectory ? await uploadedByBuildJobs(uploadedDirectory) : await uploadFiles()
|
||||
|
||||
const artifact = {
|
||||
channel: Script.channel,
|
||||
@@ -36,6 +32,34 @@ if (dryRun) console.log(`dry-run artifact: ${JSON.stringify(artifact)}`)
|
||||
|
||||
if (!dryRun) await UpdateArtifact.publish(artifact)
|
||||
|
||||
async function uploadFiles() {
|
||||
const directory = process.env.OPENCODE_DESKTOP_DIST
|
||||
|
||||
if (!directory) throw new Error("OPENCODE_DESKTOP_DIST is required")
|
||||
|
||||
const files = (
|
||||
await Array.fromAsync(
|
||||
new Bun.Glob("*.{exe,blockmap,dmg,zip,AppImage,deb,rpm,app.tar.gz}").scan({ cwd: directory, absolute: true }),
|
||||
)
|
||||
).sort()
|
||||
|
||||
if (!files.length) throw new Error("No desktop release files found")
|
||||
|
||||
return UpdateArtifact.upload({ version: Script.version, files, dryRun })
|
||||
}
|
||||
|
||||
async function uploadedByBuildJobs(directory: string) {
|
||||
const lists = await Array.fromAsync(new Bun.Glob("*.json").scan({ cwd: directory, absolute: true }))
|
||||
|
||||
if (!lists.length) throw new Error(`No uploaded desktop file lists found in ${directory}`)
|
||||
|
||||
const entries = await Promise.all(
|
||||
lists.map(async (file) => Object.entries(Schema.decodeUnknownSync(UploadedFiles)(await Bun.file(file).text()))),
|
||||
)
|
||||
|
||||
return Object.fromEntries(entries.flat())
|
||||
}
|
||||
|
||||
type DesktopFile = {
|
||||
url: string
|
||||
sha512: string
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// Uploads one platform's desktop release files to R2 from its build job and writes the returned file list, which
|
||||
// publish.ts reads through OPENCODE_DESKTOP_UPLOADED instead of uploading every platform itself.
|
||||
// Usage: bun scripts/upload.ts <dist directory> <output json>
|
||||
import path from "node:path"
|
||||
import { UpdateArtifact } from "../../../script/update-artifact"
|
||||
|
||||
const [directory, output] = process.argv.slice(2)
|
||||
|
||||
if (!directory || !output) throw new Error("Usage: bun scripts/upload.ts <dist directory> <output json>")
|
||||
|
||||
const version = process.env.OPENCODE_VERSION
|
||||
|
||||
if (!version) throw new Error("OPENCODE_VERSION is required")
|
||||
|
||||
const files = (
|
||||
await Array.fromAsync(
|
||||
new Bun.Glob("*.{exe,blockmap,dmg,zip,AppImage,deb,rpm,app.tar.gz}").scan({
|
||||
cwd: path.resolve(directory),
|
||||
absolute: true,
|
||||
}),
|
||||
)
|
||||
).sort()
|
||||
|
||||
if (!files.length) throw new Error(`No desktop release files found in ${directory}`)
|
||||
|
||||
await Bun.write(output, JSON.stringify(await UpdateArtifact.upload({ version, files }), null, 2))
|
||||
@@ -131,7 +131,8 @@ async function prepareCli(dest: string) {
|
||||
|
||||
if (process.platform !== "win32") await chmod(dest, 0o755)
|
||||
|
||||
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") {
|
||||
// electron-builder signs the bundled CLI again in Package, so beta signs it only there.
|
||||
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true" && resolveChannel() !== "beta") {
|
||||
await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// Waits until another job of this workflow run uploads an artifact, so a job can do its other work while that job
|
||||
// runs. Fails as soon as the producing job ends without the artifact.
|
||||
// Usage: bun script/wait-for-artifact.ts <artifact name> <producing job name>
|
||||
|
||||
const [artifact, producer] = process.argv.slice(2)
|
||||
|
||||
if (!artifact || !producer) throw new Error("Usage: bun script/wait-for-artifact.ts <artifact> <job>")
|
||||
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
|
||||
if (!token) throw new Error("GITHUB_TOKEN is required")
|
||||
|
||||
const run = `https://api.github.com/repos/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
||||
const started = Date.now()
|
||||
|
||||
while (Date.now() - started < 30 * 60_000) {
|
||||
if (await uploaded()) {
|
||||
console.log(`${artifact} is ready after ${Math.round((Date.now() - started) / 1000)}s`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const conclusion = await producerConclusion()
|
||||
|
||||
if (conclusion && conclusion !== "success") throw new Error(`${producer} finished with ${conclusion}`)
|
||||
// A job can finish just before its artifact appears in the listing.
|
||||
if (conclusion === "success" && (await uploaded())) process.exit(0)
|
||||
if (conclusion === "success") throw new Error(`${producer} succeeded without uploading ${artifact}`)
|
||||
|
||||
await Bun.sleep(5_000)
|
||||
}
|
||||
|
||||
throw new Error(`Timed out waiting for ${artifact}`)
|
||||
|
||||
async function uploaded() {
|
||||
const body = await get(`${run}/artifacts?name=${encodeURIComponent(artifact)}`)
|
||||
|
||||
return isRecord(body) && typeof body.total_count === "number" && body.total_count > 0
|
||||
}
|
||||
|
||||
async function producerConclusion() {
|
||||
const body = await get(`${run}/attempts/${process.env.GITHUB_RUN_ATTEMPT ?? "1"}/jobs?per_page=100`)
|
||||
const jobs = isRecord(body) && Array.isArray(body.jobs) ? body.jobs : []
|
||||
const job = jobs.find((item) => isRecord(item) && item.name === producer)
|
||||
|
||||
if (!isRecord(job) || job.status !== "completed") return undefined
|
||||
|
||||
return typeof job.conclusion === "string" ? job.conclusion : "unknown"
|
||||
}
|
||||
|
||||
async function get(url: string): Promise<unknown> {
|
||||
const response = await fetch(url, {
|
||||
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" },
|
||||
})
|
||||
|
||||
if (!response.ok) throw new Error(`GitHub API ${response.status} for ${url}`)
|
||||
|
||||
return response.json()
|
||||
}
|
||||
|
||||
function isRecord(input: unknown): input is Record<string, unknown> {
|
||||
return typeof input === "object" && input !== null && !Array.isArray(input)
|
||||
}
|
||||
Reference in New Issue
Block a user