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
58 changes: 57 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
name: CI

# Pull requests, main and Release run the checks. A pull request or push to
# main that can change the desktop app also launches it: the unpacked app on
# Apple silicon and Linux, or every installer when a pull request changes the
# installer itself. Desktop Dev (nightly) and Release build and test every
# installer before publishing.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
workflow_call:

permissions:
contents: read

concurrency:
group: ci-${{ github.ref }}
# A new push to a pull request replaces its run; any other run has a group of
# its own, so no commit on main waits or is skipped.
group: ci-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
check:
name: Node ${{ matrix.node }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
Expand All @@ -40,3 +50,49 @@ jobs:
- run: npm run site
- name: Pack
run: npm pack --ignore-scripts --dry-run

# Which desktop check a pull request or a push to main needs, from the files
# it changes. Release calls CI for a tag and builds every installer itself.
changes:
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
runs-on: ubuntu-latest
outputs:
desktop: ${{ steps.scope.outputs.desktop }}
steps:
- uses: actions/checkout@v5
with:
# A pull request's merge commit and the base it merges into.
fetch-depth: 2
- uses: actions/setup-node@v5
with:
node-version: 24
- id: scope
env:
BEFORE: ${{ github.event_name == 'push' && github.event.before || '' }}
run: |
# A push to main compares with the previous tip (the parent after a force push).
base=HEAD^1
if [ -n "$BEFORE" ] && git fetch -q --depth=1 origin "$BEFORE"; then base=$BEFORE; fi
scope=$(bash scripts/desktop-scope.sh "$base")
# Main gets the quick check; the nightly Desktop Dev build makes its installers.
if [ "$GITHUB_EVENT_NAME" = push ] && [ "$scope" = installers ]; then scope=quick; fi
echo "Desktop check: $scope"
echo "desktop=$scope" >> "$GITHUB_OUTPUT"

desktop:
needs: changes
if: needs.changes.outputs.desktop != 'none'
uses: ./.github/workflows/desktop.yml
with:
quick: ${{ needs.changes.outputs.desktop == 'quick' }}

# The check to require: it passes when every job above passed or was not needed.
passed:
name: CI passed
if: always()
needs: [check, changes, desktop]
runs-on: ubuntu-latest
steps:
- name: Fail when a job failed or was cancelled
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
66 changes: 66 additions & 0 deletions .github/workflows/desktop-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Desktop Dev

# The desktop Dev channel: a nightly build of main (03:00 in UTC+8), or one
# started with Run workflow on main. Once every installer has built and passed
# its smoke test, the rolling desktop-dev prerelease that desktop apps on the
# Dev channel check moves to this commit.
on:
schedule:
- cron: '0 19 * * *'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: desktop-dev
cancel-in-progress: false

jobs:
# A nightly run without new commits on main has nothing to publish.
changes:
runs-on: ubuntu-latest
outputs:
build: ${{ steps.check.outputs.build }}
steps:
- id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "$GITHUB_REF" != refs/heads/main ]; then
echo "::error::Desktop Dev publishes main. Run the Desktop workflow to build another branch."
exit 1
fi
published=$(gh release download desktop-dev --repo "$GITHUB_REPOSITORY" --pattern desktop.json --output - 2>/dev/null | jq -r '.version // empty' || true)
if [ "$GITHUB_EVENT_NAME" = schedule ] && [ "${published##*-dev.}" = "$GITHUB_SHA" ]; then
echo "desktop-dev already has $published."
echo build=false >> "$GITHUB_OUTPUT"
else
echo build=true >> "$GITHUB_OUTPUT"
fi

desktop:
needs: changes
if: needs.changes.outputs.build == 'true'
uses: ./.github/workflows/desktop.yml
with:
dev: true
secrets: inherit

publish:
needs: desktop
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v5
- name: Download the tested installers and update layer
uses: actions/download-artifact@v4
with:
pattern: Papilot-*
merge-multiple: true
path: release/desktop
- name: Publish the dev build
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/publish-desktop-dev.mjs release/desktop
123 changes: 40 additions & 83 deletions .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
@@ -1,70 +1,44 @@
name: Desktop

# Builds the desktop app on native runners and launches it to check that it
# works. It never publishes: CI, Desktop Dev and Release call it. Run workflow
# builds every installer for any branch; the quick option instead checks only
# the unpacked app on Apple silicon and Linux, like most pull requests.
on:
workflow_dispatch:
inputs:
quick:
description: Check only the unpacked app on Apple silicon and Linux
type: boolean
default: false
workflow_call:
# The desktop Dev channel: a nightly build of main (03:00 in UTC+8), or one
# started with Run workflow on main.
schedule:
- cron: '0 19 * * *'
push:
branches: [main, codex/electron-desktop]
paths: ['desktop/**', 'scripts/*desktop*', 'src/**', 'engine/**', 'examples/**', 'bin/**', 'skills/**', 'package*.json', '.github/workflows/desktop.yml']
pull_request:
paths: ['desktop/**', 'scripts/*desktop*', 'src/**', 'engine/**', 'examples/**', 'bin/**', 'skills/**', 'package*.json', '.github/workflows/desktop.yml']
inputs:
quick:
description: Check only the unpacked app on Apple silicon and Linux
type: boolean
default: false
dev:
description: Report VERSION-dev.COMMIT, for the Dev channel
type: boolean
default: false

permissions:
contents: read

# A push to main does not cancel the nightly dev build.
concurrency:
group: desktop-${{ github.ref }}-${{ (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && 'dev' || 'ci' }}
cancel-in-progress: ${{ github.ref_type != 'tag' && github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' }}

jobs:
# A nightly run without new commits on main has nothing to publish.
changes:
runs-on: ubuntu-latest
outputs:
build: ${{ steps.check.outputs.build }}
steps:
- id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
published=$(gh release download desktop-dev --repo "$GITHUB_REPOSITORY" --pattern desktop.json --output - 2>/dev/null | jq -r '.version // empty' || true)
if [ "$GITHUB_EVENT_NAME" = schedule ] && [ "${published##*-dev.}" = "$GITHUB_SHA" ]; then
echo "desktop-dev already has $published."
echo build=false >> "$GITHUB_OUTPUT"
else
echo build=true >> "$GITHUB_OUTPUT"
fi

build:
name: ${{ matrix.platform }} ${{ matrix.arch }}
needs: changes
if: needs.changes.outputs.build == 'true'
runs-on: ${{ matrix.os }}
name: ${{ matrix.target }}
runs-on: ${{ fromJSON('{"mac-arm64":"macos-15","mac-x64":"macos-15-intel","linux-x64":"ubuntu-22.04"}')[matrix.target] }}
timeout-minutes: 45
env:
# Dev builds: their server and update layer report the commit.
PAPILOT_DESKTOP_REVISION: ${{ github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.sha || '' }}
PAPILOT_DESKTOP_REVISION: ${{ inputs.dev && github.sha || '' }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
platform: mac
arch: arm64
executable: mac-arm64/Papilot.app/Contents/MacOS/Papilot
- os: macos-15-intel
platform: mac
arch: x64
executable: mac/Papilot.app/Contents/MacOS/Papilot
- os: ubuntu-22.04
platform: linux
arch: x64
executable: linux-unpacked/papilot
# Intel Macs run the same code on the slowest runner, so quick checks
# leave them to full builds.
target: ${{ fromJSON(inputs.quick && '["mac-arm64","linux-x64"]' || '["mac-arm64","mac-x64","linux-x64"]') }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
Expand All @@ -80,29 +54,36 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -y libgtk-3-0 libnss3 libxss1 libxtst6 libasound2 libgbm1 libnotify4 libsecret-1-0 libfuse2 xvfb xauth openbox
- run: npm ci
- name: Build the app and installers
# Making the installers takes most of a full build. The smoke tests
# launch the unpacked app, which --dir builds by itself.
- name: Build the app${{ !inputs.quick && ' and installers' || '' }}
env:
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: npm run desktop:build
# Papilot's own code for in-place updates of installers with the same shell.
run: npm run desktop:build${{ inputs.quick && ' -- --dir' || '' }}
# Papilot's own code for in-place updates of installers with the same
# shell. The smoke test builds its own copy.
- name: Build the update layer
if: ${{ !inputs.quick }}
run: node scripts/desktop.mjs layer
- name: Launch the packaged app and check its backend
if: runner.os == 'macOS'
timeout-minutes: 10
env:
PAPILOT_DESKTOP_EXECUTABLE: ${{ github.workspace }}/release/desktop/${{ matrix.executable }}
PAPILOT_DESKTOP_EXECUTABLE: ${{ github.workspace }}/release/desktop/${{ matrix.target == 'mac-x64' && 'mac' || 'mac-arm64' }}/Papilot.app/Contents/MacOS/Papilot
run: npm run test:desktop
- name: Test the Linux directory, AppImage and installed deb
- name: Test the Linux directory${{ !inputs.quick && ', AppImage and installed deb' || '' }}
if: runner.os == 'Linux'
run: xvfb-run -a -s '-screen 0 1600x1000x24' bash scripts/check-desktop-linux.sh
timeout-minutes: 15
run: xvfb-run -a -s '-screen 0 1600x1000x24' bash scripts/check-desktop-linux.sh${{ inputs.quick && ' --dir' || '' }}
- name: Save installers
if: ${{ !inputs.quick }}
uses: actions/upload-artifact@v4
with:
name: Papilot-${{ matrix.platform }}-${{ matrix.arch }}
name: Papilot-${{ matrix.target }}
path: |
release/desktop/*.dmg
release/desktop/*.zip
Expand All @@ -111,7 +92,7 @@ jobs:
if-no-files-found: error
retention-days: 7
- name: Save the update layer
if: matrix.platform == 'linux'
if: ${{ !inputs.quick && matrix.target == 'linux-x64' }}
uses: actions/upload-artifact@v4
with:
name: Papilot-app
Expand All @@ -124,31 +105,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
name: desktop-check-${{ matrix.platform }}-${{ matrix.arch }}
name: desktop-check-${{ matrix.target }}
path: test-results/desktop*.png
if-no-files-found: ignore
retention-days: 7

# The tested nightly build updates the rolling desktop-dev prerelease that
# desktop apps on the Dev channel check.
publish-dev:
needs: build
if: github.ref == 'refs/heads/main' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: desktop-dev-release
cancel-in-progress: false
steps:
- uses: actions/checkout@v5
- name: Download the tested installers and update layer
uses: actions/download-artifact@v4
with:
pattern: Papilot-*
merge-multiple: true
path: release/desktop
- name: Publish the dev build
env:
GH_TOKEN: ${{ github.token }}
run: node scripts/publish-desktop-dev.mjs release/desktop
17 changes: 7 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ concurrency:
cancel-in-progress: false

jobs:
# CI's checks on both supported Node versions; a tag pushed without a pull
# request gets them here.
ci:
uses: ./.github/workflows/ci.yml

desktop:
# The reusable workflow's dev publishing job (skipped for tags) declares
# contents: write; its build jobs keep read-only tokens.
permissions:
contents: write
uses: ./.github/workflows/desktop.yml
secrets: inherit

release:
# Publish only after every native installer has built and passed its smoke test.
needs: desktop
# Publish only after CI and every native installer have passed.
needs: [ci, desktop]
runs-on: ubuntu-latest
env:
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
Expand Down Expand Up @@ -59,10 +60,6 @@ jobs:
printf '```sh\nnpm install -g papilot@latest\n```\n' >> "$RUNNER_TEMP/notes.md"
- run: npm ci
- run: npm run setup:review
- run: npm run typecheck
- run: npm test
- run: npm run i18n -- check
- run: npm run i18n:check-app
- name: Download tested desktop installers
uses: actions/download-artifact@v4
with:
Expand Down
Loading
Loading