Skip to content

fix(workbench): hide disabled harnesses from new tasks #1572

fix(workbench): hide disabled harnesses from new tasks

fix(workbench): hide disabled harnesses from new tasks #1572

Workflow file for this run

name: CI
on:
push:
branches:
- master
- release/*
pull_request:
paths-ignore:
- "**.md"
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- Dry Run
- Release
# Runners resolve through repo/org `vars` (ArcBox pins paid Blacksmith labels there);
# forks without the vars fall back to GitHub-hosted labels with zero setup.
# Pull request and release-branch CI keeps latest-run cancellation. Every master SHA gets a unique
# group so a release-PR merge cannot be canceled or replaced before its exact SHA is tested.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/master' && github.sha || 'latest' }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
permissions:
contents: read
env:
# Non-lint Node steps still need more than the default 4096 MB heap; lint sets its own limit.
NODE_OPTIONS: --max-old-space-size=6144
jobs:
typescript:
name: TypeScript
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: "" # keep build.rustflags; the -D warnings default belongs to the Rust job's clippy call
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7.0.0
with:
node-version-file: .nvmrc
package-manager-cache: false
# Before `pnpm install`, so the hashFiles globs below cannot reach into node_modules.
#
# eslint validates a cached entry against that file's own content only, so a cross-file
# type change would leave an unchanged file's cached result in place — a type-aware rule
# such as no-floating-promises can start applying to a caller nobody edited, and `tsc`
# does not reject that code either. The key therefore covers every lint-visible input at
# once and there are deliberately no restore-keys: a prefix match would reintroduce
# exactly that hole. The cost is that this only hits on commits touching no lintable
# source (docs, Rust, workflows); a run that changes any of them re-lints from cold,
# which is the correct answer.
- name: Restore ESLint cache
uses: actions/cache@v6
with:
path: .eslintcache
key: eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-${{ github.sha }}
restore-keys: |
eslint-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'pnpm-workspace.yaml', 'package.json', 'apps/*/package.json', 'packages/*/*/package.json', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ hashFiles('packages/*/*/src/**') }}-
eslint-${{ runner.os }}-
- name: Install dependencies
run: pnpm install --frozen-lockfile
# After `pnpm install`, which could otherwise prune the restored node_modules/.cache dirs.
# `tsc --build` validates staleness from .tsbuildinfo content hashes, not timestamps
# (touching every source leaves a warm build sub-second), so restoring a stale build info
# is always safe — tsc rechecks exactly what changed. restore-keys is therefore sound here,
# unlike for the ESLint cache above.
- name: Restore TypeScript build info
uses: actions/cache@v6
with:
path: |
apps/*/node_modules/.cache/tsconfig*.tsbuildinfo
packages/*/*/node_modules/.cache/tsconfig*.tsbuildinfo
key: tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-${{ github.sha }}
restore-keys: |
tsc-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'tsconfig*.json', 'apps/*/tsconfig.json', 'apps/*/tests/tsconfig.json', 'apps/*/e2e/tsconfig.json', 'packages/*/*/tsconfig.json', 'packages/*/*/tests/tsconfig.json') }}-
tsc-${{ runner.os }}-
- wait: setup-rust
- name: Build PTY sidecar for interoperability tests
id: build-sidecar
run: cargo build --locked -p linkcode-pty
background: true
- name: Check formatting and imports
run: pnpm format:check
# Type-aware programs are duplicated per worker, so lint defaults to single-threaded.
- name: Lint
run: pnpm lint
- name: Typecheck
run: pnpm typecheck
- wait: build-sidecar
- name: Test
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
LINKCODE_REQUIRE_PTY_SIDECAR: "1"
run: |
pnpm test --exclude='apps/daemon/tests/integration/**'
pnpm test apps/daemon/tests/integration --no-file-parallelism
- name: Daemon process acceptance
env:
LINKCODE_PTY_SIDECAR_PATH: ${{ github.workspace }}/target/debug/linkcode-pty
run: |
pnpm -F @linkcode/daemon build
pnpm -F @linkcode/daemon e2e:startup
desktop:
name: Desktop App Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Setup Rust
id: setup-rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
background: true
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7.0.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Electron system dependencies
run: |
pnpm -F @linkcode/desktop exec playwright-core install-deps chromium
sudo apt-get install --no-install-recommends -y openbox x11-utils
- name: Test unpackaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:unpackaged
- name: Test window state persistence
run: |
xvfb-run -a sh -c '
openbox >/tmp/linkcode-openbox.log 2>&1 &
wm_pid=$!
trap "kill $wm_pid 2>/dev/null || true" EXIT
# Maximize needs a managing WM; wait for the EWMH readiness marker instead
# of racing openbox startup.
for _ in $(seq 1 50); do
xprop -root _NET_SUPPORTING_WM_CHECK 2>/dev/null | grep -q "window id" && break
sleep 0.2
done
pnpm -F @linkcode/desktop e2e:window-bounds
'
- wait: setup-rust
- name: Test unsigned packaged app entry
run: xvfb-run -a pnpm -F @linkcode/desktop e2e:packaged
webview:
name: Webview Browser Entry
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7.0.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Chromium
run: pnpm -F @linkcode/webview exec playwright-core install --with-deps chromium --only-shell
- name: Test production and mock browser entries
run: pnpm -F @linkcode/webview e2e:browser
# Never expose production telemetry configuration to pull-request code. The browser smoke
# above deliberately runs without it; push/manual builds separately verify the real bundle.
- name: Build production bundle with telemetry
if: ${{ github.event_name != 'pull_request' }}
env:
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN_WEBVIEW }}
VITE_POSTHOG_PROJECT_TOKEN: ${{ secrets.POSTHOG_PROJECT_TOKEN }}
VITE_POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
run: pnpm -F @linkcode/webview build
mobile:
name: Mobile Native Bundles
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
- parallel:
- uses: pnpm/action-setup@v6
with:
run_install: false
cache: true
- uses: actions/setup-node@v7.0.0
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Export Android and iOS app entries
# SENTRY_AUTH_TOKEN intentionally stays in EAS: ordinary CI does not upload source maps.
env:
EXPO_PUBLIC_SENTRY_DSN: ${{ github.event_name != 'pull_request' && secrets.SENTRY_DSN_MOBILE || '' }}
EXPO_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_PROJECT_TOKEN || '' }}
EXPO_PUBLIC_POSTHOG_HOST: ${{ github.event_name != 'pull_request' && secrets.POSTHOG_HOST || '' }}
run: pnpm -F @linkcode/mobile smoke:export
rust:
name: Rust
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v7
# rustfmt and clippy come from rust-toolchain.toml's components.
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
rustflags: ""
- parallel:
- name: Format
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-targets --locked -- -D warnings
- name: Test
run: cargo test --locked
config-integration:
name: Cross-repository config contract
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 10
steps:
- name: Require organization App credentials
env:
BOT_APP_ID: ${{ secrets.BOT_APP_ID }}
BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }}
run: |
if [ -z "$BOT_APP_ID" ] || [ -z "$BOT_APP_PRIVATE_KEY" ]; then
echo "::error::BOT_APP_ID and BOT_APP_PRIVATE_KEY must be available so CI can read arcboxlabs/linkcodehq and arcboxlabs/linkcode-config"
exit 1
fi
- name: Mint publisher read token
id: publisher-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: arcboxlabs
repositories: linkcodehq
permission-contents: read
- name: Mint config source read token
id: source-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
app-id: ${{ secrets.BOT_APP_ID }}
private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
owner: arcboxlabs
repositories: linkcode-config
permission-contents: read
- name: Check out pinned publisher
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: arcboxlabs/linkcodehq
ref: 986d9f21403df53bc932f511eb1b5f0bb634d48d
token: ${{ steps.publisher-token.outputs.token }}
path: .config-validation/linkcodehq
persist-credentials: false
- name: Check out pinned config source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
repository: arcboxlabs/linkcode-config
ref: a1ed4d666721c3aed0d563aaea42fce8b5f945b5
token: ${{ steps.source-token.outputs.token }}
path: .config-validation/linkcode-config
persist-credentials: false
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6
with:
version: 11.9.0
run_install: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
package-manager-cache: false
- name: Validate pinned source with the pinned parser
run: |
set -euo pipefail
hq="$GITHUB_WORKSPACE/.config-validation/linkcodehq"
config="$GITHUB_WORKSPACE/.config-validation/linkcode-config"
test "$(git -C "$hq" rev-parse HEAD)" = 986d9f21403df53bc932f511eb1b5f0bb634d48d
test "$(git -C "$config" rev-parse HEAD)" = a1ed4d666721c3aed0d563aaea42fce8b5f945b5
test "$(node --version | cut -d. -f1)" = v24
test "$(pnpm --version)" = 11.9.0
pnpm --dir "$hq" --filter @linkcodehq/config-structural... \
install --frozen-lockfile --ignore-scripts
pnpm --dir "$hq" --filter @linkcodehq/config-structural exec tsx \
"$config/scripts/validate.mts" \
--hq-root "$hq" \
--source-root examples/acme-zenith
all-green:
name: All Green
runs-on: ${{ vars.CI_RUNNER_LINUX || 'ubuntu-latest' }}
timeout-minutes: 5
needs:
- typescript
- desktop
- webview
- mobile
- rust
- config-integration
if: always()
steps:
- name: Check required jobs
run: |
if [ '${{ needs.typescript.result }}' != 'success' ] || [ '${{ needs.desktop.result }}' != 'success' ] || [ '${{ needs.webview.result }}' != 'success' ] || [ '${{ needs.mobile.result }}' != 'success' ] || [ '${{ needs.rust.result }}' != 'success' ]; then
exit 1
fi
if [ '${{ needs.config-integration.result }}' != 'success' ] && [ '${{ needs.config-integration.result }}' != 'skipped' ]; then
exit 1
fi