Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 244 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
group: ${{ github.ref == 'refs/heads/dev' && format('ci-{0}', github.run_id) || format('ci-{0}', github.event.pull_request.number || github.ref) }}
cancel-in-progress: true

permissions:
Expand Down Expand Up @@ -129,7 +129,7 @@ jobs:
- name: typecheck
run: bun turbo typecheck

unit:
unit-app:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -165,10 +165,227 @@ jobs:
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-unit-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-${{ github.sha }}
key: turbo-${{ runner.os }}-unit-app-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-unit-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-
turbo-${{ runner.os }}-unit-
turbo-${{ runner.os }}-unit-app-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-
turbo-${{ runner.os }}-unit-app-

- run: bun install --frozen-lockfile

- name: unit
run: bun turbo test:ci --filter=@opencode-ai/app

- name: Publish unit reports
if: >
always() &&
(
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
)
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # mikepenz/action-junit-report@v6
with:
report_paths: packages/app/.artifacts/unit/junit.xml
check_name: unit results (app)
detailed_summary: true
include_time_in_summary: true
fail_on_failure: false

- name: Upload unit artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7
with:
name: unit-app-${{ github.run_attempt }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
path: packages/app/.artifacts/unit/junit.xml

unit-opencode:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # actions/checkout@v6
with:
persist-credentials: false

# Load-bearing: `bun install` runs `trustedDependencies`
# postinstalls from the root package.json (electron, node-pty,
# esbuild, tree-sitter at time of writing), which call `node`
# explicitly. Do not drop without first patching those scripts.
# Source of truth: grep `trustedDependencies` in package.json. (#70)
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # actions/setup-node@v6.3.0
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-unit-opencode-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-unit-opencode-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-
turbo-${{ runner.os }}-unit-opencode-

- run: bun install --frozen-lockfile

- name: unit
run: bun turbo test:ci --filter=opencode

- name: Publish unit reports
if: >
always() &&
(
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
)
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # mikepenz/action-junit-report@v6
with:
report_paths: packages/opencode/.artifacts/unit/junit.xml
check_name: unit results (opencode)
detailed_summary: true
include_time_in_summary: true
fail_on_failure: false

- name: Upload unit artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7
with:
name: unit-opencode-${{ github.run_attempt }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
path: packages/opencode/.artifacts/unit/junit.xml

unit-desktop:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # actions/checkout@v6
with:
persist-credentials: false

# Load-bearing: `bun install` runs `trustedDependencies`
# postinstalls from the root package.json (electron, node-pty,
# esbuild, tree-sitter at time of writing), which call `node`
# explicitly. Do not drop without first patching those scripts.
# Source of truth: grep `trustedDependencies` in package.json. (#70)
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # actions/setup-node@v6.3.0
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-unit-desktop-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-unit-desktop-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-
turbo-${{ runner.os }}-unit-desktop-

- run: bun install --frozen-lockfile

- name: unit
run: bun turbo test:ci --filter=@opencode-ai/desktop-electron

- name: Publish unit reports
if: >
always() &&
(
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
)
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # mikepenz/action-junit-report@v6
with:
report_paths: packages/desktop-electron/.artifacts/unit/junit.xml
check_name: unit results (desktop)
detailed_summary: true
include_time_in_summary: true
fail_on_failure: false

- name: Upload unit artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7
with:
name: unit-desktop-${{ github.run_attempt }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
path: packages/desktop-electron/.artifacts/unit/junit.xml

unit-windows:
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: windows-latest
timeout-minutes: 15
continue-on-error: true
permissions:
contents: read
checks: write
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # actions/checkout@v6
with:
persist-credentials: false

# Load-bearing: `bun install` runs `trustedDependencies`
# postinstalls from the root package.json (electron, node-pty,
# esbuild, tree-sitter at time of writing), which call `node`
# explicitly. Do not drop without first patching those scripts.
# Source of truth: grep `trustedDependencies` in package.json. (#70)
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # actions/setup-node@v6.3.0
with:
node-version: "24"

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # oven-sh/setup-bun@v2
with:
bun-version: "1.3.11"

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # actions/cache@v5
with:
path: .turbo/cache
key: turbo-${{ runner.os }}-unit-windows-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-unit-windows-${{ hashFiles('turbo.json', '**/package.json', 'bun.lock') }}-
turbo-${{ runner.os }}-unit-windows-

- run: bun install --frozen-lockfile

Expand All @@ -184,8 +401,8 @@ jobs:
)
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # mikepenz/action-junit-report@v6
with:
report_paths: packages/*/.artifacts/unit/junit.xml
check_name: unit results
report_paths: packages/**/.artifacts/unit/junit.xml
check_name: unit results (windows)
detailed_summary: true
include_time_in_summary: true
fail_on_failure: false
Expand All @@ -194,30 +411,35 @@ jobs:
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # actions/upload-artifact@v7
with:
name: unit-${{ github.run_attempt }}
name: unit-windows-${{ github.run_attempt }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
path: packages/*/.artifacts/unit/junit.xml
path: packages/**/.artifacts/unit/junit.xml

# Aggregator for the `dev` branch ruleset. The required check on GitHub
# is `ci / check`; every new job added above MUST be listed in `needs:`
# below, otherwise its failure will not block merge. If this list grows
# past ~5 entries, consider switching to `re-actors/alls-green` for
# dynamic aggregation (tracked in issue #54).
# is `ci / check`; every new blocking job added above MUST be listed in
# `needs:` below, otherwise its failure will not block merge. Non-blocking
# signal jobs, such as `unit-windows`, intentionally stay outside this list.
# If the blocking list grows past ~5 entries, consider switching to
# `re-actors/alls-green` for dynamic aggregation (tracked in issue #54).
check:
if: always()
needs:
- changes
- typecheck
- unit
- unit-app
- unit-opencode
- unit-desktop
runs-on: ubuntu-latest
steps:
- name: Validate CI result
env:
DOCS_ONLY: ${{ needs.changes.outputs.docs_only }}
TYPECHECK_RESULT: ${{ needs.typecheck.result }}
UNIT_RESULT: ${{ needs.unit.result }}
UNIT_APP_RESULT: ${{ needs['unit-app'].result }}
UNIT_OPENCODE_RESULT: ${{ needs['unit-opencode'].result }}
UNIT_DESKTOP_RESULT: ${{ needs['unit-desktop'].result }}
run: |
set -euo pipefail

Expand All @@ -226,8 +448,13 @@ jobs:
exit 0
fi

if [ "$TYPECHECK_RESULT" != "success" ] || [ "$UNIT_RESULT" != "success" ]; then
if [ "$TYPECHECK_RESULT" != "success" ] ||
[ "$UNIT_APP_RESULT" != "success" ] ||
[ "$UNIT_OPENCODE_RESULT" != "success" ] ||
[ "$UNIT_DESKTOP_RESULT" != "success" ]; then
echo "typecheck=$TYPECHECK_RESULT"
echo "unit=$UNIT_RESULT"
echo "unit-app=$UNIT_APP_RESULT"
echo "unit-opencode=$UNIT_OPENCODE_RESULT"
echo "unit-desktop=$UNIT_DESKTOP_RESULT"
exit 1
fi
2 changes: 1 addition & 1 deletion packages/app/src/shell-frame-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fs from "node:fs"
import path from "node:path"

function read(relativePath: string) {
return fs.readFileSync(path.join(import.meta.dir, relativePath), "utf8")
return fs.readFileSync(path.join(import.meta.dir, relativePath), "utf8").replaceAll("\r\n", "\n")
}

test("desktop shell shares titlebar height across titlebar and narrow sidebar geometry", () => {
Expand Down
7 changes: 5 additions & 2 deletions packages/desktop-electron/scripts/ci-smoke.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { describe, expect, test } from "bun:test"
import path from "node:path"
import { desktopShellMainSelector, titlebarShellSelector } from "../src/renderer/ci-smoke-selectors"
import { buildSmokeEnv, requiredSelectors, resolveCiSmokeReadyFile, resolveMainEntry } from "./ci-smoke"

describe("ci smoke helpers", () => {
test("resolveMainEntry points at the built Electron main process bundle", () => {
expect(resolveMainEntry()).toMatch(/packages\/desktop-electron\/out\/main\/index\.js$/)
expect(resolveMainEntry().endsWith(path.join("packages", "desktop-electron", "out", "main", "index.js"))).toBe(
true,
)
})

test("buildSmokeEnv isolates the app state in a temporary home", () => {
Expand All @@ -24,7 +27,7 @@ describe("ci smoke helpers", () => {

test("resolveCiSmokeReadyFile points at the CI-ready marker inside the isolated user data dir", () => {
expect(resolveCiSmokeReadyFile("/tmp/pawwork-ci-smoke")).toBe(
"/tmp/pawwork-ci-smoke/ai.pawwork.desktop.dev/ci-smoke-ready.json",
path.join("/tmp/pawwork-ci-smoke", "ai.pawwork.desktop.dev", "ci-smoke-ready.json"),
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import { resolveOpencodeRoot } from "./embedded-server-path"

test("prepare-embedded-server resolves the opencode workspace from the script directory", () => {
const scriptDir = path.join("/repo", "packages", "desktop-electron", "scripts")
expect(resolveOpencodeRoot(scriptDir)).toBe(path.join("/repo", "packages", "opencode"))
expect(resolveOpencodeRoot(scriptDir)).toBe(path.resolve(scriptDir, "../../opencode"))
})
Loading
Loading