mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 18:07:45 +00:00
* wip * wip * wip * wip * Improve experimental worktree cards with compact agent summaries - Add an optional details row for workspace metadata and indicators - Roll up descendant activity timestamps to parent cards * Reduce worktree card metadata indentation * wip * Expand sidebar agent lines with subagents and remote host status - Show subagent activity, send targets, and unread signals in slot cards - Identify remote hosts and provide SSH reconnection controls * Show child workspace status counts in sidebar cards Distinguish monitoring agents and refine lineage alignment so workspace activity is easier to scan. * Enable navigation from worktree sidebar activity indicators - Open the matching agent or workspace directly, or choose among multiple sources - Use host-qualified identities for nested workspace activity - Show pull requests in 2.0 card details and refine compact card layout * Show every status in the sidebar card corner and let cards open and close The title-row corner now shows one glyph per status the card speaks for (needs, failed, unread, working), most urgent first, followed by the time, instead of only the most urgent one. Each glyph opens its own sources. Collapsed child workspaces report all their statuses through a flat, shallow-comparable record. Experimental 2.0 cards can also be opened and closed; a closed card's title slot speaks for its hidden agents and children. * final worktree design * wip * rm * wip * wip * fix style * fix commit * Improve worktree card interactions and spacing - Let users toggle cards by clicking their headers. - Delay details until the pointer rests to avoid accidental popups. - Add breathing room around card content. * wip * fix issues * wip * Improve workspace sidebar icons and navigation - Batch AI icon generation and add settings to customize and regenerate icons. - Fix agent navigation and unread handling across execution hosts. - Keep linked reviews visible while loading and reduce unnecessary refresh work. * Fix sidebar agent ownership and targeted title updates Resolve unstamped agent sessions to their workspace runtime so sidebar summaries and unread state follow the correct host. Limit title-change invalidation to affected workspaces. * fix build * Simplify worktree sidebar icons and agent rows - Generate monochrome icons that match the sidebar foreground - Move agent model names into row tooltips to reduce clutter * Keep agent timestamps visible alongside dismiss controls * Fix worktree icon regeneration and bound the cache Cancel stale requests when clearing icons so older results cannot restore the previous cache. Evict unused renderer entries to limit memory growth. * Unify experimental workspace cards around title status - Surface hidden agent and child workspace activity in the card title. - Retire the second experimental layout and AI-generated icons. - Preserve existing experimental card selections and add a details row option. * wip * wip * wip * fix bugs * improve perf * Fix sidebar lineage rollup: unread dot on children, running status on collapsed parent - A card's unread dot and bold title now include its own visible agent rows' unread results, so expanding a parent reveals which child the unread came from. - A collapsed parent's status lane shows the most urgent descendant state (needs you > failed > working) when it beats the parent's own status. - Host-scoped workspace unread markers and related attention fixes. * Improve sidebar activity rollup performance - Refresh only stale descendant slots and scope unread updates to their workspace. - Share agent row projections and index unread markers to reduce repeated work. - Preserve host-specific agent activation and show the latest activity time. * wip * Fix sidebar attention state and optimize slot invalidation - Preserve unread flags raised by users or earlier sessions when markers clear. - Scope agent list expansion to each host. - Track displayed panes and freshness deadlines to limit slot updates. * wip * Fix sidebar workspace status and host ownership - Keep agent navigation and unread state scoped to the correct host, including paired SSH workspaces and runtimes. - Correct collapsed lineage status, stale review display, and settings synchronization while reducing unnecessary sidebar refreshes. * Show unread agent completions and scope workspace reads by host * fix compile issue * wip fix static checks * wip * Fix workspace sidebar agent ownership and PR display - Update agent rows when terminal host bindings arrive and preserve local ownership for unstamped retained completions. - Keep newer hosted PRs visible when an older merged PR is cached.
694 lines
36 KiB
YAML
694 lines
36 KiB
YAML
name: Headless Node server
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
|
|
paths:
|
|
- 'src/**'
|
|
- 'config/**'
|
|
- 'native/**'
|
|
- 'tests/**'
|
|
- 'resources/**'
|
|
- 'package.json'
|
|
- 'pnpm-lock.yaml'
|
|
- 'pnpm-workspace.yaml'
|
|
- 'tsconfig.json'
|
|
- '.npmrc'
|
|
- 'config/pnpmfile.cjs'
|
|
- '.github/actions/install-node-dependencies/**'
|
|
- '.github/actions/restore-pnpm-verification/**'
|
|
- '.github/actions/prepare-headless-compiler/**'
|
|
- '.github/actions/prepare-native-runtime/**'
|
|
- '.github/actions/prepare-orcad-prebuilds/**'
|
|
- '.github/workflows/node-server-tests.yml'
|
|
workflow_dispatch:
|
|
inputs:
|
|
build_template:
|
|
description: Also merge every lane's slot into the desktop orcad template artifact
|
|
type: boolean
|
|
default: false
|
|
fast_runners:
|
|
description: Use Blacksmith where the platform and architecture are available
|
|
type: boolean
|
|
default: true
|
|
# Release and dev builds qualify every platform for the template they ship.
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: Git ref every lane checks out, e.g. the release tag
|
|
type: string
|
|
default: ''
|
|
build_template:
|
|
description: Upload each lane's release slot and merge them into the orcad-template artifact
|
|
type: boolean
|
|
default: false
|
|
fast_runners:
|
|
description: Use Blacksmith where the platform and architecture are available
|
|
type: boolean
|
|
default: true
|
|
# Requalify fresh builds daily; publish missing immutable Windows cache keys.
|
|
schedule:
|
|
- cron: '30 11 * * *'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Keep template callers independent while replacing superseded PR qualification.
|
|
concurrency:
|
|
group: node-server-${{ (inputs.build_template || github.event_name == 'push') && format('run-{0}', github.run_id) || github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: ${{ !inputs.build_template && github.event_name != 'push' }}
|
|
|
|
jobs:
|
|
changes:
|
|
if: >-
|
|
github.event_name == 'push' ||
|
|
(github.event_name == 'pull_request' && github.event.pull_request.draft != true)
|
|
runs-on: ${{ inputs.fast_runners && 'blacksmith-2vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
|
timeout-minutes: 5
|
|
outputs:
|
|
should_run: ${{ steps.scope.outputs.should_run || steps.graph.outputs.should_run }}
|
|
qualification: ${{ steps.scope.outputs.qualification || steps.graph.outputs.qualification }}
|
|
native_compatibility: ${{ steps.scope.outputs.native_compatibility || steps.graph.outputs.native_compatibility }}
|
|
runners: ${{ steps.scope.outputs.runners || steps.graph.outputs.runners }}
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 2
|
|
persist-credentials: false
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: package.json
|
|
check-latest: true
|
|
package-manager-cache: false
|
|
- name: Detect headless-server build and test inputs
|
|
id: scope
|
|
shell: bash
|
|
env:
|
|
PUSH_BASE: ${{ github.event.before }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
ALLOW_TEST_ONLY_NATIVE_SKIP: ${{ github.event_name == 'pull_request' && !inputs.build_template && inputs.ref == '' }}
|
|
run: |
|
|
if [ "$EVENT_NAME" = push ]; then
|
|
# Compare the entire push, including multi-commit pushes and removed files.
|
|
if git fetch --no-tags --depth=1 origin "$PUSH_BASE" &&
|
|
git diff --name-only --no-renames -z "$PUSH_BASE" HEAD > "$RUNNER_TEMP/node-server-changes" &&
|
|
git diff --name-only --no-renames --diff-filter=M -z "$PUSH_BASE" HEAD > "$RUNNER_TEMP/node-server-modified"; then
|
|
node config/scripts/node-server/qualification/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --defer-graph --full-qualification --modified-paths "$RUNNER_TEMP/node-server-modified"
|
|
else
|
|
echo 'should_run=true' >> "$GITHUB_OUTPUT"
|
|
fi
|
|
exit 0
|
|
fi
|
|
NATIVE_SCOPE_ARGS=()
|
|
if [ "$ALLOW_TEST_ONLY_NATIVE_SKIP" = true ]; then
|
|
NATIVE_SCOPE_ARGS=(--pull-request --diff-paths "$RUNNER_TEMP/node-server-native-diff")
|
|
fi
|
|
# Compare the tested merge with its base, retaining both sides of renames.
|
|
if git diff --name-only --no-renames -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-changes" &&
|
|
git diff --name-only --no-renames --diff-filter=M -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-modified" &&
|
|
git diff --raw --no-renames --no-abbrev -z HEAD^1 HEAD > "$RUNNER_TEMP/node-server-native-diff"; then
|
|
node config/scripts/node-server/qualification/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --defer-graph --modified-paths "$RUNNER_TEMP/node-server-modified" "${NATIVE_SCOPE_ARGS[@]}"
|
|
else
|
|
echo 'should_run=true' >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- uses: ./.github/actions/prepare-headless-compiler
|
|
id: compiler
|
|
if: steps.scope.outputs.graph_required == 'true'
|
|
continue-on-error: true
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
if: steps.scope.outputs.graph_required == 'true' && steps.compiler.outputs.available != 'true'
|
|
- name: Check the headless import graph
|
|
id: graph
|
|
if: steps.scope.outputs.graph_required == 'true'
|
|
shell: bash
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
ALLOW_TEST_ONLY_NATIVE_SKIP: ${{ github.event_name == 'pull_request' && !inputs.build_template && inputs.ref == '' }}
|
|
run: |
|
|
if [ "$EVENT_NAME" = push ]; then
|
|
node config/scripts/node-server/qualification/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --full-qualification --modified-paths "$RUNNER_TEMP/node-server-modified"
|
|
else
|
|
NATIVE_SCOPE_ARGS=()
|
|
if [ "$ALLOW_TEST_ONLY_NATIVE_SKIP" = true ]; then
|
|
NATIVE_SCOPE_ARGS=(--pull-request --diff-paths "$RUNNER_TEMP/node-server-native-diff")
|
|
fi
|
|
node config/scripts/node-server/qualification/node-server-change-scope.mjs "$RUNNER_TEMP/node-server-changes" --modified-paths "$RUNNER_TEMP/node-server-modified" "${NATIVE_SCOPE_ARGS[@]}"
|
|
fi
|
|
|
|
persistence:
|
|
needs: [changes, mobile_web_bundle]
|
|
permissions:
|
|
contents: read
|
|
concurrency:
|
|
group: node-server-persistence-${{ matrix.os }}-${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' && github.ref || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' }}
|
|
# Missing/failed detection runs the full matrix; scheduled and manual runs stay unconditional.
|
|
# Drafts re-trigger at readiness; inherited caller events must not depend on PR fields.
|
|
if: >-
|
|
${{ !cancelled() && needs.changes.outputs.should_run != 'false' &&
|
|
(needs.changes.outputs.qualification == 'false' || needs.mobile_web_bundle.result == 'success') &&
|
|
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: ${{ fromJSON(needs.changes.outputs.runners || '["ubuntu-22.04","ubuntu-24.04-arm","macos-15","macos-15-intel","windows-2022","windows-11-arm"]') }}
|
|
# Keep platform keys stable for slot names and Windows cross-compilation. PR and scheduled runs use the
|
|
# free hosted label; only explicit fast_runners callers rent Blacksmith where it has the platform.
|
|
runs-on: >-
|
|
${{ inputs.fast_runners && fromJSON('{"ubuntu-22.04":"blacksmith-8vcpu-ubuntu-2204","ubuntu-24.04-arm":"blacksmith-8vcpu-ubuntu-2404-arm","macos-15":"blacksmith-6vcpu-macos-15"}')[matrix.os] || matrix.os }}
|
|
timeout-minutes: 20
|
|
env:
|
|
ORCA_BACKGROUND_LAUNCH: '1'
|
|
JOBS: ${{ (inputs.fast_runners || github.event_name == 'pull_request' || github.event_name == 'schedule') && 'max' || '' }}
|
|
steps:
|
|
- name: Parallelize Windows checkout
|
|
if: (inputs.fast_runners || github.event_name == 'pull_request' || github.event_name == 'schedule') && runner.os == 'Windows'
|
|
shell: bash
|
|
run: |
|
|
git config --global checkout.workers 0
|
|
# These fresh native builds do not need MSBuild's incremental file tracking.
|
|
echo 'TrackFileAccess=false' >> "$GITHUB_ENV"
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
id: dependencies
|
|
with:
|
|
native-runtime: ${{ runner.os == 'Windows' && 'node' || 'none' }}
|
|
# Template qualification must not wait for large download-cache uploads.
|
|
cache-pnpm-store: ${{ !inputs.build_template && runner.os != 'Windows' }}
|
|
cache-pnpm-store-lookup-only: 'true'
|
|
# Design D7 upgrade and rollback: the last Bun orcad, built from a main commit that shipped
|
|
# it, using the current Bun pin beside this checkout's Node slot; hand-over skips once PROTOCOL_VERSION
|
|
# moves past the Bun daemon's. Install its pinned dependencies independently of this checkout.
|
|
- name: Find installed Bun
|
|
id: installed-bun
|
|
if: runner.os == 'Linux'
|
|
shell: bash
|
|
run: |
|
|
bun="$GITHUB_WORKSPACE/node_modules/.bin/bun"
|
|
if [ -x "$bun" ] && [ "$("$bun" --version)" = "$(cat config/.bun-version)" ]; then
|
|
echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH"
|
|
echo 'found=true' >> "$GITHUB_OUTPUT"
|
|
fi
|
|
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
|
if: runner.os == 'Linux' && steps.installed-bun.outputs.found != 'true'
|
|
with:
|
|
bun-version-file: config/.bun-version
|
|
no-cache: true
|
|
- name: Build the last Bun orcad for the cross-runtime tests
|
|
id: bun-orcad
|
|
background: true
|
|
shell: bash
|
|
env:
|
|
BUN_ORCAD_COMMIT: f4092c06d639ee13ad446261dcabc78b27a21fbc
|
|
run: |
|
|
if [ "$RUNNER_OS" != Linux ]; then exit 0; fi
|
|
git fetch --no-tags --depth=1 origin "$BUN_ORCAD_COMMIT"
|
|
git worktree add --detach "$RUNNER_TEMP/bun-orcad-source" "$BUN_ORCAD_COMMIT"
|
|
pnpm --dir "$RUNNER_TEMP/bun-orcad-source" install --frozen-lockfile --ignore-scripts
|
|
node "$RUNNER_TEMP/bun-orcad-source/config/scripts/build-orcad-bun.mjs" --out-dir "$RUNNER_TEMP/bun-orcad"
|
|
echo "slot=$RUNNER_TEMP/bun-orcad" >> "$GITHUB_OUTPUT"
|
|
echo "executable=$(command -v bun)" >> "$GITHUB_OUTPUT"
|
|
# Linux release slots come from the floor and Alpine lanes; this slot serves local tests.
|
|
- uses: ./.github/actions/prepare-orcad-prebuilds
|
|
id: orcad-prebuild
|
|
with:
|
|
resolve-windows-cache: >-
|
|
${{ inputs.fast_runners || (!inputs.build_template && inputs.ref == '' &&
|
|
(github.event_name == 'pull_request' ||
|
|
(github.ref == 'refs/heads/main' && contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name)))) }}
|
|
restore-windows-cache: ${{ inputs.fast_runners || github.event_name == 'pull_request' || github.event_name == 'push' }}
|
|
# The template lane cross-compiles arm64 too, as it does for the process-table addon.
|
|
- uses: ./.github/actions/prepare-windows-cli-launcher
|
|
if: runner.os == 'Windows'
|
|
with:
|
|
arches: ${{ (inputs.build_template && matrix.os == 'windows-2022') && 'x64 arm64' || (runner.arch == 'ARM64' && 'arm64' || 'x64') }}
|
|
- uses: actions/download-artifact@v8
|
|
id: mobile-web-artifact
|
|
if: needs.changes.outputs.qualification != 'false'
|
|
continue-on-error: true
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web
|
|
- name: Retry mobile web artifact download
|
|
if: steps.mobile-web-artifact.outcome == 'failure'
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web
|
|
# Narrow test-only runs skip the shared producer but still need a page.
|
|
- uses: ./.github/actions/build-mobile-web-bundle
|
|
if: needs.changes.outputs.qualification == 'false'
|
|
- run: pnpm build:orcad
|
|
env:
|
|
ORCA_REUSE_PREPARED_WINDOWS_SECURITY: ${{ runner.os == 'Windows' && steps.dependencies.outcome == 'success' }}
|
|
- wait: bun-orcad
|
|
- run: pnpm test:node-server --artifact ${{ runner.os == 'Linux' && '--cross-runtime' || '' }}
|
|
env:
|
|
ORCA_BUN_ORCAD_SLOT: ${{ steps.bun-orcad.outputs.slot }}
|
|
BUN_EXECUTABLE: ${{ steps.bun-orcad.outputs.executable }}
|
|
- name: Build the host-target desktop server template
|
|
if: runner.os == 'Linux'
|
|
run: node "$(if [ -f config/scripts/orcad-distribution/template/build-orcad-template.mjs ]; then printf %s config/scripts/orcad-distribution/template/build-orcad-template.mjs; else printf %s config/scripts/build-orcad-template.mjs; fi)" --targets linux-${{ runner.arch == 'ARM64' && 'arm64' || 'x64' }}-glibc
|
|
- name: Verify a structured chat from the desktop server template
|
|
if: runner.os == 'Linux'
|
|
run: pnpm test:node-server --artifact src/main/orcad/testing/structured-chat/orcad-packaged-structured-chat.integration.test.ts --maxWorkers=2
|
|
# Only a Windows runner compiles it; arm64 cross-compiles here.
|
|
# Before the Node 18 check below: the build script imports TypeScript, which Node 18 cannot load.
|
|
# orcad's instance lock and stop proof read a PID's creation time through it; the x64 binary this
|
|
# runner loads must answer it, and answer nothing for a missing PID rather than a stale time.
|
|
- name: Build the Windows process-table addons for the desktop template
|
|
if: inputs.build_template && matrix.os == 'windows-2022'
|
|
shell: bash
|
|
run: |
|
|
node "$(if [ -f config/scripts/native-build/windows/build-windows-process-tree-addon.mjs ]; then printf %s config/scripts/native-build/windows/build-windows-process-tree-addon.mjs; else printf %s config/scripts/build-windows-process-tree-addon.mjs; fi)" --arch=x64
|
|
node "$(if [ -f config/scripts/native-build/windows/build-windows-process-tree-addon.mjs ]; then printf %s config/scripts/native-build/windows/build-windows-process-tree-addon.mjs; else printf %s config/scripts/build-windows-process-tree-addon.mjs; fi)" --arch=arm64
|
|
node -e "
|
|
const { assertWindowsProcessTreeCreationTime } = require(require('node:fs').existsSync('./config/scripts/native-build/windows/windows-process-tree-creation-time.cjs') ? './config/scripts/native-build/windows/windows-process-tree-creation-time.cjs' : './config/scripts/windows-process-tree-creation-time.cjs')
|
|
const addon = require('./.build/windows-process-tree/x64/windows-process-tree.node')
|
|
assertWindowsProcessTreeCreationTime({ module: addon })
|
|
const own = addon.getProcessCreationTime(process.pid)
|
|
const started = Date.now() - process.uptime() * 1000
|
|
if (typeof own !== 'number' || Math.abs(own - started) > 5000) throw new Error('creation time ' + own + ' vs ' + started)
|
|
if (addon.getProcessCreationTime(0x7ffffff0) !== undefined) throw new Error('a missing PID must read undefined')
|
|
console.log('creation time ok:', own)
|
|
"
|
|
- name: Keep the Windows process-table addons for the desktop template
|
|
if: inputs.build_template && matrix.os == 'windows-2022'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-windows-process-tree
|
|
path: .build/windows-process-tree/
|
|
include-hidden-files: true
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
- name: Keep the Windows CLI launchers for the desktop template
|
|
if: inputs.build_template && matrix.os == 'windows-2022'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-windows-cli-launcher
|
|
path: .build/windows-cli-launcher/
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
# orcad's data-root privacy check fails closed without it, so every Windows slot needs one.
|
|
# Older requested refs use the previous security implementation and have no addon builder.
|
|
- name: Build the Windows security addons for the desktop template
|
|
if: inputs.build_template && matrix.os == 'windows-2022' && hashFiles('config/scripts/native-build/windows/build-windows-security-addon.mjs', 'config/scripts/build-windows-security-addon.mjs') != ''
|
|
shell: bash
|
|
run: |
|
|
node "$(if [ -f config/scripts/native-build/windows/build-windows-security-addon.mjs ]; then printf %s config/scripts/native-build/windows/build-windows-security-addon.mjs; else printf %s config/scripts/build-windows-security-addon.mjs; fi)" --arch=x64
|
|
node "$(if [ -f config/scripts/native-build/windows/build-windows-security-addon.mjs ]; then printf %s config/scripts/native-build/windows/build-windows-security-addon.mjs; else printf %s config/scripts/build-windows-security-addon.mjs; fi)" --arch=arm64
|
|
node -e "require('./.build/windows-security/x64/windows-security.node').currentUserSid()"
|
|
- name: Keep the Windows security addons for the desktop template
|
|
if: inputs.build_template && matrix.os == 'windows-2022' && hashFiles('config/scripts/native-build/windows/build-windows-security-addon.mjs', 'config/scripts/build-windows-security-addon.mjs') != ''
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-windows-security
|
|
path: .build/windows-security/*/windows-security.node
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
- uses: actions/setup-node@v6
|
|
if: runner.arch == 'X64'
|
|
with:
|
|
node-version: '18'
|
|
- name: Verify Node 18 loads and hands off to the pinned Node
|
|
if: runner.arch == 'X64'
|
|
shell: bash
|
|
run: |
|
|
node out/orcad/orcad.js --orcad-smoke-load-check
|
|
node out/orcad/orcad.js --orcad-profile-state-preflight 00000000-0000-4000-8000-000000000018 | tee "$RUNNER_TEMP/preflight.json"
|
|
node -e "const r=JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'));if(r.runtime!=='node'||!/^24\./.test(r.runtimeVersion))process.exit(1)" "$RUNNER_TEMP/preflight.json"
|
|
- name: Save the freshly qualified Windows server prebuild
|
|
if: >-
|
|
success() && runner.os == 'Windows' && (runner.arch == 'X64' || runner.arch == 'ARM64') &&
|
|
((inputs.fast_runners && github.event_name == 'workflow_dispatch') ||
|
|
(!inputs.build_template && inputs.ref == '' && github.ref == 'refs/heads/main' &&
|
|
contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name))) &&
|
|
steps.orcad-prebuild.outputs.cache-identity-outcome == 'success' &&
|
|
steps.orcad-prebuild.outputs.cache-key != ''
|
|
continue-on-error: true
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: ${{ steps.orcad-prebuild.outputs.cache-path }}
|
|
key: ${{ steps.orcad-prebuild.outputs.cache-key }}
|
|
# Linux release slots come from the floor and Alpine lanes; these runners own the rest.
|
|
- name: Keep this runner's qualified slot for the desktop template
|
|
if: inputs.build_template && runner.os != 'Linux'
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-prebuild-${{ matrix.os }}
|
|
path: out/orcad-prebuilds/
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
# A rerun attempt re-uploads under the same name, which v4 otherwise refuses.
|
|
overwrite: true
|
|
|
|
# Every qualification lane tests the same platform-independent page.
|
|
mobile_web_bundle:
|
|
needs: changes
|
|
if: >-
|
|
${{ !cancelled() && needs.changes.outputs.should_run != 'false' &&
|
|
needs.changes.outputs.qualification != 'false' &&
|
|
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) }}
|
|
runs-on: ${{ inputs.fast_runners && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
|
timeout-minutes: 15
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
- uses: ./.github/actions/build-mobile-web-bundle
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
overwrite: true
|
|
|
|
linux_glibc_floor:
|
|
needs: [changes, mobile_web_bundle]
|
|
permissions:
|
|
contents: read
|
|
concurrency:
|
|
group: node-server-linux_glibc_floor-${{ matrix.os }}-${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' && github.ref || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' }}
|
|
# Each Linux lane owns its inputs; the template still requires every lane to pass.
|
|
if: >-
|
|
${{ !cancelled() &&
|
|
needs.changes.outputs.should_run != 'false' &&
|
|
needs.changes.outputs.qualification != 'false' &&
|
|
needs.mobile_web_bundle.result == 'success' &&
|
|
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-22.04, ubuntu-24.04-arm]
|
|
# Design D6: server glibc slots are built and gated on glibc 2.28, where the pinned Node's
|
|
# own floor is; the desktop keeps its Ubuntu 20.04 gate. gcc-toolset-14 supplies C++20 and
|
|
# links newer libstdc++ symbols statically, so the slot needs only RHEL 8's GLIBCXX_3.4.25.
|
|
include:
|
|
- os: ubuntu-22.04
|
|
image: quay.io/pypa/manylinux_2_28_x86_64@sha256:407f771c51a2c3e83ebe5a7970b4289ead3a6db21d9b9c089168775cad11d328
|
|
- os: ubuntu-24.04-arm
|
|
image: quay.io/pypa/manylinux_2_28_aarch64@sha256:c22ffd129ac99a8a42d1f2c2f4e88a9089288dd9ee987a7092da1c7dc48f27a9
|
|
runs-on: ${{ inputs.fast_runners && (matrix.os == 'ubuntu-22.04' && 'blacksmith-8vcpu-ubuntu-2204' || 'blacksmith-8vcpu-ubuntu-2404-arm') || matrix.os }}
|
|
container: ${{ matrix.image }}
|
|
timeout-minutes: 25
|
|
env:
|
|
ORCA_BACKGROUND_LAUNCH: '1'
|
|
# The image puts /opt/clang first on PATH; the slot is built with its gcc-toolset.
|
|
CC: gcc
|
|
CXX: g++
|
|
PYTHON: /opt/python/cp312-cp312/bin/python3
|
|
steps:
|
|
- name: Install glibc 2.28 prerequisites
|
|
run: |
|
|
missing_tool=false
|
|
for tool in git ps unzip which xz; do
|
|
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
missing_tool=true
|
|
fi
|
|
done
|
|
if [ "$missing_tool" = true ]; then
|
|
# The image's source-built Git needs no RPM; missing tools come from AlmaLinux.
|
|
dnf --disablerepo='epel*' install -y git procps-ng unzip which xz
|
|
fi
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- name: Trust the checked-out workspace
|
|
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
# A direct install (6-9s) beats restoring the 417 MB store plus a linking install.
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
cache-pnpm-store: 'false'
|
|
- name: Build and smoke this runner's node-pty prebuild slot under the pinned Node
|
|
run: |
|
|
pnpm build:orcad-prebuilds
|
|
pnpm build:orcad-prebuilds --require-slots "$(node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --print-slot)"
|
|
pnpm build:orcad-prebuilds --smoke
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web
|
|
- run: pnpm build:orcad
|
|
- run: pnpm test:node-server --artifact
|
|
- name: Keep this runner's glibc 2.28 slot for the desktop template
|
|
if: inputs.build_template
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-prebuild-glibc-${{ matrix.os }}
|
|
path: out/orcad-prebuilds/
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
|
|
linux_glibc217_compat:
|
|
needs: changes
|
|
concurrency:
|
|
group: node-server-linux_glibc217_compat-${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' && github.ref || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' }}
|
|
if: >-
|
|
${{ !cancelled() &&
|
|
needs.changes.outputs.should_run != 'false' &&
|
|
needs.changes.outputs.qualification != 'false' &&
|
|
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) &&
|
|
(github.event_name != 'pull_request' || inputs.build_template || inputs.ref != '' ||
|
|
needs.changes.outputs.native_compatibility != 'false') }}
|
|
runs-on: ${{ inputs.fast_runners && 'blacksmith-8vcpu-ubuntu-2204' || 'ubuntu-22.04' }}
|
|
timeout-minutes: 20
|
|
env:
|
|
ORCA_BACKGROUND_LAUNCH: '1'
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
# Design D6 rung B: the opt-in linux-x64-glibc217 slot beside the unofficial glibc-217 Node.
|
|
# Why docker run and not container: the runner's own Node for JS actions needs glibc 2.28,
|
|
# so the host installs and fetches the pinned Node, and the glibc 2.17 image only builds and
|
|
# smokes with that Node. devtoolset-10 supplies C++20; the slot links libstdc++ statically.
|
|
- name: Build and smoke the glibc 2.17 compat slot under the glibc-217 Node
|
|
run: |
|
|
compat_node="$(node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --slot=linux-x64-glibc217 --print-runtime)"
|
|
case "$compat_node" in
|
|
"$GITHUB_WORKSPACE"/*) ;;
|
|
*) echo "glibc-217 Node cached outside the workspace: $compat_node" >&2; exit 1 ;;
|
|
esac
|
|
docker run --rm --init -i \
|
|
-e ORCA_BACKGROUND_LAUNCH=1 \
|
|
-e COMPAT_NODE="/work/${compat_node#"$GITHUB_WORKSPACE"/}" \
|
|
-e CC=gcc -e CXX=g++ \
|
|
-e PYTHON=/opt/python/cp312-cp312/bin/python3 \
|
|
-v "$GITHUB_WORKSPACE:/work" -w /work \
|
|
quay.io/pypa/manylinux2014_x86_64@sha256:6f74cabeac2432570aa4bfdb29f7c1f30313d4d6654d764c44e574b6ffdd4ed5 bash -s <<'GLIBC217_COMPAT_SLOT'
|
|
set -eu
|
|
export PATH="$(dirname "$COMPAT_NODE"):$PATH"
|
|
node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --slot=linux-x64-glibc217
|
|
node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --require-slots linux-x64-glibc217
|
|
# The image's devtoolset LD_LIBRARY_PATH must not stand in for a host C++ runtime.
|
|
env -u LD_LIBRARY_PATH node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --slot=linux-x64-glibc217 --smoke
|
|
GLIBC217_COMPAT_SLOT
|
|
- name: Keep the glibc 2.17 compat slot for the desktop template
|
|
if: inputs.build_template
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-prebuild-glibc217
|
|
path: out/orcad-prebuilds/
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
|
|
linux_musl:
|
|
needs: [changes, mobile_web_bundle]
|
|
permissions:
|
|
contents: read
|
|
concurrency:
|
|
group: node-server-linux_musl-${{ matrix.os }}-${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' && github.ref || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'push' && !inputs.build_template && inputs.ref == '' }}
|
|
if: >-
|
|
${{ !cancelled() &&
|
|
needs.changes.outputs.should_run != 'false' &&
|
|
needs.changes.outputs.qualification != 'false' &&
|
|
needs.mobile_web_bundle.result == 'success' &&
|
|
(github.event_name != 'pull_request' || github.event.pull_request.draft != true) }}
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-22.04, ubuntu-24.04-arm]
|
|
runs-on: ${{ inputs.fast_runners && (matrix.os == 'ubuntu-22.04' && 'blacksmith-8vcpu-ubuntu-2204' || 'blacksmith-8vcpu-ubuntu-2404-arm') || matrix.os }}
|
|
timeout-minutes: 20
|
|
env:
|
|
ORCA_BACKGROUND_LAUNCH: '1'
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/restore-pnpm-verification
|
|
id: pnpm-verification
|
|
with:
|
|
container-toolchain: 'true'
|
|
- name: Prepare native Alpine artifact
|
|
env:
|
|
VERIFICATION_CACHE_PATH: ${{ steps.pnpm-verification.outputs.path }}
|
|
run: |
|
|
touch "$VERIFICATION_CACHE_PATH"
|
|
mkdir -p out/mobile-web
|
|
# Multi-arch index digest of the tag; re-resolve it whenever NODE_RUNTIME_PIN moves.
|
|
docker run --rm --init --detach --name orcad-alpine \
|
|
-e ORCA_BACKGROUND_LAUNCH=1 \
|
|
-v "$VERIFICATION_CACHE_PATH:/root/.cache/pnpm/lockfile-verified.jsonl" \
|
|
-v "$GITHUB_WORKSPACE:/work" -w /work \
|
|
node:24.22.1-alpine@sha256:6a1c190e17215d844926b0677f4bf87273ff7d5e54db06d3b5b5863ab0b09a3d sleep infinity
|
|
docker exec -i orcad-alpine sh -s <<'NODE_SERVER_PREPARATION'
|
|
set -eu
|
|
apk add --no-cache bash git libstdc++ python3 make g++
|
|
git config --global --add safe.directory /work
|
|
npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")"
|
|
pnpm install --frozen-lockfile --ignore-scripts
|
|
pnpm build:orcad-prebuilds
|
|
pnpm build:orcad-prebuilds --require-slots "$(node "$(if [ -f config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/build-orcad-prebuilds.mjs; else printf %s config/scripts/build-orcad-prebuilds.mjs; fi)" --print-slot)"
|
|
pnpm build:orcad-prebuilds --smoke
|
|
NODE_SERVER_PREPARATION
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web
|
|
- name: Verify native Alpine artifact and persistence
|
|
run: |
|
|
docker exec -i orcad-alpine sh -s <<'NODE_SERVER_QUALIFICATION'
|
|
set -eu
|
|
pnpm build:orcad
|
|
pnpm test:node-server --artifact
|
|
NODE_SERVER_QUALIFICATION
|
|
- name: Clean up the Alpine qualification container
|
|
if: always()
|
|
run: docker rm --force orcad-alpine
|
|
- name: Keep this runner's musl slot for the desktop template
|
|
if: inputs.build_template
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-prebuild-musl-${{ matrix.os }}
|
|
path: out/orcad-prebuilds/
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|
|
|
|
# Design D2: the desktop ships every target's addons, merged from the lanes that qualified them.
|
|
desktop_template:
|
|
needs: changes
|
|
if: ${{ !cancelled() && inputs.build_template }}
|
|
permissions:
|
|
contents: read
|
|
actions: read
|
|
runs-on: ${{ inputs.fast_runners && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
- name: Wait for every template qualification lane
|
|
uses: actions/github-script@v8
|
|
with:
|
|
script: |
|
|
const deadline = Date.now() + 25 * 60_000
|
|
const expected = {
|
|
persistence: 6,
|
|
mobile_web_bundle: 1,
|
|
linux_glibc_floor: 2,
|
|
linux_glibc217_compat: 1,
|
|
linux_musl: 2
|
|
}
|
|
while (Date.now() < deadline) {
|
|
const jobs = await github.paginate(github.rest.actions.listJobsForWorkflowRun, {
|
|
...context.repo, run_id: context.runId, filter: 'latest', per_page: 100
|
|
})
|
|
const assemblers = jobs.filter(job =>
|
|
job.name === 'desktop_template' || job.name.endsWith(' / desktop_template'))
|
|
if (assemblers.length > 1) throw new Error('Ambiguous desktop template qualification')
|
|
if (assemblers.length === 1) {
|
|
const prefix = assemblers[0].name.slice(0, -'desktop_template'.length)
|
|
let ready = true
|
|
for (const [name, count] of Object.entries(expected)) {
|
|
const lanes = jobs.filter(job => job.name === `${prefix}${name}` ||
|
|
job.name.startsWith(`${prefix}${name} (`))
|
|
if (lanes.length > count) throw new Error(`Unexpected qualification lanes: ${name}`)
|
|
for (const lane of lanes) {
|
|
if (lane.status === 'completed' && lane.conclusion !== 'success') {
|
|
throw new Error(`Template qualification did not succeed: ${lane.name} (${lane.conclusion})`)
|
|
}
|
|
}
|
|
if (lanes.length !== count || lanes.some(lane => lane.status !== 'completed')) ready = false
|
|
}
|
|
if (ready) return
|
|
}
|
|
await new Promise(resolve => setTimeout(resolve, 15_000))
|
|
}
|
|
throw new Error('Timed out waiting for every template qualification lane')
|
|
|
|
- name: Collect every lane's slot
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
pattern: orcad-prebuild-*
|
|
path: ${{ runner.temp }}/orcad-prebuild-lanes
|
|
- name: Collect the Windows process-table addons
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-windows-process-tree
|
|
path: .build/windows-process-tree
|
|
- name: Collect the Windows CLI launchers
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-windows-cli-launcher
|
|
path: .build/windows-cli-launcher
|
|
- name: Collect the Windows security addons
|
|
if: hashFiles('config/scripts/native-build/windows/build-windows-security-addon.mjs', 'config/scripts/build-windows-security-addon.mjs') != ''
|
|
uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-windows-security
|
|
path: .build/windows-security
|
|
- name: Merge the lanes and gate the full slot matrix
|
|
shell: bash
|
|
run: |
|
|
node "$(if [ -f config/scripts/orcad-distribution/prebuilds/merge-orcad-prebuilds.mjs ]; then printf %s config/scripts/orcad-distribution/prebuilds/merge-orcad-prebuilds.mjs; else printf %s config/scripts/merge-orcad-prebuilds.mjs; fi)" "$RUNNER_TEMP"/orcad-prebuild-lanes/*
|
|
pnpm build:orcad-prebuilds --require-slots
|
|
pnpm build:orcad-prebuilds --require-slots linux-x64-glibc217
|
|
# The page the qualification lanes tested, not a rebuild.
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: orcad-mobile-web-bundle
|
|
path: out/mobile-web
|
|
- run: pnpm build:orcad-template
|
|
- name: Report the template size
|
|
shell: bash
|
|
run: |
|
|
{
|
|
echo '### orcad template'
|
|
echo '```'
|
|
du -sh out/orcad-template
|
|
du -sh out/orcad-template/targets/*
|
|
echo '```'
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: orcad-template
|
|
path: out/orcad-template/
|
|
# The per-target .server-target and .runtime-node markers are dotfiles.
|
|
include-hidden-files: true
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
overwrite: true
|