mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 08:03:58 +00:00
* fix(wsl): migrate 21 call sites onto the runner, after five review rounds Rebased onto main now that the runner (#15903) has landed. 21 sites across 15 files move off ad-hoc `execFile('wsl.exe', ...)`. Allowlist 23 -> 16 on the WSL guard; 163 -> 152 on the W1 child_process guard, which moved as a consequence. Five review rounds, each finding real defects -- several introduced by the previous round's fixes: 1. Hooks ran user orca.yaml scripts under dash; probe failure fell back to the login shell, reintroducing the ~/.profile stall the runner exists to remove. 2. An unparseable probe was cached permanently, disabling every WSL feature on the distro; hooks regressed from "runs degraded" to "fails". 3. Exit 127 had no expiry; a starved 5s probe hard-failed the 10s scan behind it; a joiner burned its budget on someone else's probe. 4. The comment stripper blanked live code, so the windowsHide guard walked past a real unguarded spawn and reported the file clean; an ownership-probe timeout silently deselected the user's Claude account. 5. Verification of the guards themselves. The recurring finding -- a call answering "is this installed?" on a degraded PATH -- was eventually fixed structurally rather than per-caller: the runner refuses an unresolved guest PATH unless the caller opts in. Per-site vigilance was demonstrably not holding; 3 of 8 sites had already forgotten the analogous exit-code check. Remaining 16 files need a runner mode that does not exist: a long-lived streaming child (OAuth logins, hook relay), a synchronous caller, or a host-level flag like --status that the guest-command API cannot express. * fix(wsl): close round 5's P1s -- degrade where PATH was never needed Round 5 measured the guards by re-executing their algorithms standalone rather than reading them, and found four things. P1 -- four skill/plugin paths gained a hard dependency on the login-shell probe that they never had. They ran under a plain non-login `sh -c` on main, so a probe failure now breaks WSL skill discovery and install on exactly the distro the runner was built for: one with a slow `~/.profile`. Worse, the throw escapes before each site's own error mapping, so the UI gets a raw internal string. They degrade now, per the rule this branch already wrote down in `wsl-fish-history-cleanup.ts`. P1 -- Codex and Claude were asymmetric. Claude's five credential sites degrade; Codex's were strict, so adding a WSL Codex account failed where adding a Claude one succeeded. Three of the four are byte-equivalent to Claude sites, and their scripts read `$HOME`/`$WSL_DISTRO_NAME`, which wsl.exe supplies without a login shell. `assertWslCodexCliAvailable` stays strict on purpose -- that one really does answer "is this installed?" (#9725). P1 -- the ownership-probe timeout fix did not survive the rebase onto main. A timeout still returned "not owned", which the caller *persists*, clearing the user's account selection. P1 -- `blankStringContents` desynced on a nested template literal (`` `${`x`}` ``), leaving 116 lines of a child_process importer outside the ratchet, with 27 importers structurally at risk. Now tracks template depth. Regenerating against the fixed blanker: 70 -> 68 offenders. Also: the windowsHide vacuity check could not fail while the allowlist alone exceeded its bound -- the exact defect the sibling guard documents avoiding. It now names a file that definitely offends. * fix(wsl): close round 6 -- my blanker fix had traded a false positive for a miss Round 6 re-derived the guard's answer from a TypeScript AST instead of trusting the regex, and caught two things. P1 -- the nested-template fix I shipped in round 5 introduced a worse bug than the one it closed. Switching to "code mode" inside `${...}` without also resetting the quote at a newline meant an apostrophe in a regex literal -- `` `'${value.replace(/'/g, "'\\''")}'` `` , which is exactly the shellQuote shape all over this codebase -- inverted the lexer for the rest of the file. `claude-accounts/service.ts` went blind from line 96, hiding a REAL unguarded `spawn` at :1097: the WSL Claude managed-login path, which opens a console and steals foreground on Windows. Round 5 traded one false positive for one false negative and I did not notice, because the offender count went down. The blanker now resets non-backtick quotes at a newline (the rule stripComments already had) and tracks brace depth per interpolation. The spawn is fixed rather than allowlisted, and the count is 69 -- the number the AST predicted. P1 -- the ownership-timeout guard was dead code: it threw into its own `catch` three lines below, which returned null, which the caller persists as "not owned" and clears the user's account selection. Now a typed sentinel the catch rethrows. P2 -- `WslGuestEnvironmentUnavailableError` reached the UI verbatim from the CLI installer and the Codex availability check. Both mapped. Method note: I had been regenerating the allowlist with a Python transcription of the scanner, and the two drifted -- the same two-implementations problem this workstream keeps finding. The allowlist is now generated by running the shipped test with an empty list and taking what it reports. * fix(guards): stop patching the lexer -- make the scanner fail closed instead Round 7 proved my round-6 fix also did not work, by planting a plainly-named unguarded `spawn` in `claude-accounts/service.ts` and watching the guard pass 3/3. That is three consecutive attempts at an exact lexer, each shipping a desync that hid real calls, and each time the offender count went DOWN, which I read as progress. Round 6's diagnosis was wrong too: the culprit is the `templates` brace-depth stack, which nothing resets, not quote state. So stop trying to be exact. `blankStringContentsDesynced` reports when the lexer lost its bearings, and the guard treats that as an offender. Over-reporting is a nuisance; under-reporting is a false clean, and a false clean is what let a real console-flash spawn out of the ratchet twice. The allowlist goes 69 -> 82: the 13 extra are files whose scan cannot be trusted, now named rather than assumed fine. The planted violation is now caught. Also from round 7: - `SPAWN_CALL` missed promisified and renamed bindings, so `exec('where gemini')` (a real Windows cmd.exe spawn) and a detached `shell: true` in `cli/runtime/launch.ts` were invisible. Added execAsync/execFileAsync/ execFileCb/spawnDetached. - `BASHISM` matched `set -o pipefail` but not `set -euo pipefail`, which is the only spelling this tree uses -- so the check could not have caught the #14292 signature it exists for. Fixed, and it immediately flagged a file; that one turned out to be a comment, so the bashism scan now strips comments too. - The CLI installer error mapping my round-6 commit claimed was "both mapped" was never applied -- only the Codex side had been. Now actually mapped. * fix(guards): close the four holes round 8 found by planting violations Round 8 stopped reasoning about the guard and planted spawns into it. Four holes, none of which reading had found: - `windowsHide: false` **passed**. The check was `args.includes('windowsHide')`, a substring test. Now matches `windowsHide: true`. - A ternary first argument was silently skipped: the method-declaration filter `/^\(\s*\w+\s*[:?]/` also matches `exec(useAlt ? 'a' : 'b', …)`. Now requires a type after the colon. - Renamed bindings were not covered, despite the comment I wrote saying they were -- I had hardcoded three names. Aliases are now resolved from the import. Each is verified closed by planting it and watching the guard fail. `fork` is deliberately still unscanned. Round 8 is right that Node forwards the option, but `ForkOptions` does not declare it, so the two live sites cannot be fixed without a cast. Recorded in the verification doc rather than left as a silent gap, along with two others worth knowing: the allowlist is file-granular, so its ~18 false-positive entries carry a standing pre-approval for real regressions in those files and cannot be retired by fixing code; and `stripComments` has no desync report, so the fail-closed check is only half applied. The doc now also says how to verify a guard change: plant a violation. Every guard fix here that was verified by reading was wrong. * fix(wsl): stop preflight reporting installed CLIs as absent on a slow distro Round 9's merge blocker, and the sharpest finding of the whole workstream: the branch built to close #9725 had reopened it from the other side. `preflight-wsl-command.ts` was one of five sites without `allowDegradedEnvironment`, so a guest-PATH probe failure threw. Every consumer collapses a throw into a verdict: `isCommandAvailable` and `isCommandOnPath` catch to `false` ("not installed"), `isGhAuthenticated` and `isGlabAuthenticated` read an empty payload as "not authenticated". So a slow distro made WSL git, gh and glab read as missing. Two things made it likely rather than theoretical. The probe took two thirds of a 5s budget, leaving the command ~1667ms where main gave it the full 5s inside its own login shell -- a cold WSL VM start routinely lands in that band. And a probe timeout is cached for 30s with a re-probe threshold of 1.5x the failed budget, which a 5s caller can never clear, so every preflight command short-circuited without spawning wsl.exe at all -- and Re-check does not invalidate the cache. Fixes: preflight degrades instead of refusing, and the probe is capped at half the caller's budget and at 4s, so no caller ends up with less time than it had before the runner existed. Also fixes a real console flash found on the way: `preflight-command-exec.ts` spawns git/gh/node through `promisify(execFile)` with no `windowsHide`. Round 9 also confirmed the credential paths are now *safer* than main: all 11 account sites degrade, every destructive guest operation is still marker-gated, and main's `getOwnedManagedAuthPath` could disown an account on a 5s timeout -- which this branch turns into a failed launch instead of a destroyed selection. * fix(wsl): make "Try again" able to succeed, and test the round-9 fix Round 10 returned MERGE with one residual worth closing first. A transient probe failure left the null-resolving promise in `inFlight`, so the only way back was `retryAfter` -- and the 4s probe cap made the 1.5x budget escape unreachable, because no caller can pass more than 4s. For the full 30s window the four non-degrading sites returned their error *without spawning wsl.exe at all*, and each of those errors says "Try again". The advice was guaranteed to fail. The entry is now dropped on a transient outcome and an explicit cooldown gate replaces it, so the window alone decides. The window drops 30s -> 5s: long enough to stop a stampede, short enough that the user's next click reaches a distro that has since warmed up. Round 10 also noted the round-9 fix shipped untested, which was fair. Added: the probe-budget floor for 5s/8s/10s callers, and preflight's degrade opt-in plus its stdout/stderr-carrying rejection, which isGhAuthenticated reads off the caught error as an auth-success fallback. * test(wsl): make the probe-budget guard actually guard Round 11 caught that the regression test I added for the probe cap did not bind: it seeded the guest environment, so the probe resolved in ~0ms and the assertion read the command leg's timeout instead. Reverting the cap to the old 2/3 split left all three cases green. Dropping the seed and asserting on the probe leg fixes it -- verified by reverting the cap and watching all three fail. A regression guard that cannot fail is the shape that has cost the most in this workstream: the windowsHide guard silently passed a real unguarded spawn twice for the same reason.
832 lines
35 KiB
YAML
832 lines
35 KiB
YAML
name: PR Checks
|
|
|
|
on:
|
|
pull_request:
|
|
types:
|
|
- opened
|
|
- synchronize
|
|
- reopened
|
|
- ready_for_review
|
|
|
|
concurrency:
|
|
group: pr-checks-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
# Why: a README/docs-only PR used to start the full matrix (32 test shards,
|
|
# two package jobs, typecheck, git compat, xterm, shell contracts). Path
|
|
# filters on `on.pull_request` would drop the `verify` check entirely; this
|
|
# detector keeps verify as the required aggregate and skips the expensive jobs.
|
|
code_paths:
|
|
name: detect code-relevant changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should_run: ${{ steps.filter.outputs.should_run }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# Why blob:none: full history is needed for the merge-base diff, but historical
|
|
# file contents are not. Blobs are ~89% of this repo's pack, and Git fetches the
|
|
# few this job actually reads on demand.
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
persist-credentials: false
|
|
|
|
- name: Classify changed paths
|
|
id: filter
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Why --no-renames: name-only rename detection can report only the destination.
|
|
# A code file moved under docs/ must still expose its code-side deletion.
|
|
CHANGED="$(git diff --name-only --no-renames --diff-filter=ACDMR --merge-base "$BASE_SHA" "$HEAD_SHA")"
|
|
echo "Changed paths:"
|
|
printf '%s\n' "$CHANGED"
|
|
SHOULD_RUN="$(printf '%s\n' "$CHANGED" | node config/scripts/pr-code-change-scope.mjs)"
|
|
echo "should_run=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
|
|
echo "should_run=$SHOULD_RUN"
|
|
|
|
static_analysis:
|
|
name: static analysis
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# Why blob:none: full history is needed for the merge-base diff, but historical
|
|
# file contents are not. Blobs are ~89% of this repo's pack, and Git fetches the
|
|
# few this job actually reads on demand.
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
|
|
- name: Lint
|
|
run: pnpm exec oxlint --format github
|
|
|
|
- name: Enforce focused code-quality plugins
|
|
run: pnpm run audit:code-quality:native
|
|
|
|
- name: Enforce type-aware code-quality baseline
|
|
run: pnpm run audit:code-quality:type-aware
|
|
|
|
- name: Enforce changed-code quality
|
|
run: pnpm run check:code-quality:changed -- "${{ github.event.pull_request.base.sha }}"
|
|
|
|
- name: Enforce React Doctor on changed lines
|
|
run: pnpm run check:react-doctor:changed -- "${{ github.event.pull_request.base.sha }}"
|
|
|
|
- name: Check Zustand selector fan-out budget
|
|
run: pnpm run check:zustand-selector-fanout
|
|
|
|
- name: Check reliability gate manifest
|
|
run: pnpm run check:reliability-gates
|
|
|
|
- name: Check VM runtime rollback compatibility
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
if git diff --quiet --merge-base "$BASE_SHA" "$HEAD_SHA" -- \
|
|
src/shared/ephemeral-vm-runtime-store.ts \
|
|
src/shared/ephemeral-vm-runtime-feature-store.ts \
|
|
src/shared/ephemeral-vm-runtime-rollback-projection.ts \
|
|
src/shared/ephemeral-vm-runtimes.ts \
|
|
src/shared/ephemeral-vm-recipes.ts \
|
|
src/shared/orca-yaml-hook-types.ts \
|
|
src/main/ephemeral-vm-runtime-service.ts \
|
|
src/main/ephemeral-vm-runtime-provisioning-persistence.ts \
|
|
src/main/ephemeral-vm-failed-start-cleanup.ts; then
|
|
echo "VM runtime persistence is unchanged."
|
|
exit 0
|
|
fi
|
|
node config/scripts/run-ephemeral-vm-runtime-store-rollback-repro.mjs \
|
|
config/scripts/ephemeral-vm-runtime-store-cross-version.test.ts
|
|
|
|
- name: Enforce max-lines ratchet
|
|
run: pnpm run check:max-lines-ratchet
|
|
|
|
- name: Verify bundled skill guides
|
|
run: pnpm run verify:bundled-skill-guides
|
|
|
|
- name: Verify skill freshness manifest
|
|
run: pnpm run verify:skill-bundle-manifest
|
|
|
|
- name: Verify localization catalog
|
|
run: pnpm run verify:localization-catalog
|
|
|
|
# Why: extraction writes sorted evidence to an isolated temporary path,
|
|
# so feature PRs need one normalized AST pass rather than a three-OS matrix.
|
|
- name: Verify localization extraction
|
|
run: pnpm run verify:localization-extraction
|
|
|
|
- name: Verify localization coverage
|
|
run: pnpm run verify:localization-coverage
|
|
|
|
# Why: project-owned type declarations must live in .ts so tsc
|
|
# actually checks them. TypeScript's skipLibCheck: true (inherited
|
|
# from @electron-toolkit/tsconfig) silently widens unresolved names
|
|
# in .d.ts to `any`, which is how #1186 shipped a broken IPC signature
|
|
# past typecheck. See .github/CONTRIBUTING.md#type-declarations-prefer-ts-over-dts.
|
|
- name: Guard against project-owned .d.ts in preload/shared
|
|
run: |
|
|
matches=$(find src/preload src/shared -name '*.d.ts' 2>/dev/null || true)
|
|
if [ -n "$matches" ]; then
|
|
echo "::error::Project-owned .d.ts files are not allowed under src/preload or src/shared."
|
|
echo "Move type declarations into a .ts file so skipLibCheck does not hide errors."
|
|
echo "See .github/CONTRIBUTING.md#type-declarations-prefer-ts-over-dts."
|
|
echo "Found:"
|
|
echo "$matches"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Check feature wall asset budget
|
|
run: pnpm check:feature-wall-assets
|
|
|
|
- name: Verify macOS entitlements
|
|
run: pnpm verify:macos-entitlements
|
|
|
|
root_directory_guard:
|
|
name: root directory guard
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# Why blob:none: full history is needed for the merge-base diff, but historical
|
|
# file contents are not. Blobs are ~89% of this repo's pack, and Git fetches the
|
|
# few this job actually reads on demand.
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
persist-credentials: false
|
|
|
|
- name: Reject new root-level files and folders
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: node .github/scripts/check-root-directory-entries.mjs "$BASE_SHA" "$HEAD_SHA"
|
|
|
|
typecheck:
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
|
|
# Why: every project is `composite`, so tsc already writes a .tsbuildinfo that lets
|
|
# the next run skip unchanged files. CI threw it away each time. The key is per-SHA
|
|
# so each run saves its own; restore-keys inherit the newest prior graph to diff against.
|
|
- name: Cache TypeScript incremental state
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: config/*.tsbuildinfo
|
|
key: tsbuildinfo-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'config/tsconfig*.json') }}-${{ github.sha }}
|
|
restore-keys: |
|
|
tsbuildinfo-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'config/tsconfig*.json') }}-
|
|
tsbuildinfo-${{ runner.os }}-
|
|
|
|
- run: pnpm run typecheck
|
|
|
|
git_compatibility:
|
|
name: Git compatibility
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
|
|
# Why: the 2.25.5 lane is a source build of a pinned tarball, so it produced the
|
|
# same binary on every PR for minutes of runner time. The key carries the version
|
|
# because that is the only input; the sha256 assertion below still guards the
|
|
# tarball on the miss path that actually builds.
|
|
- name: Cache baseline Git build
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/.cache/orca-git-compat/git-2.25.5
|
|
key: git-compat-baseline-${{ runner.os }}-${{ runner.arch }}-2.25.5
|
|
|
|
- name: Verify Git binary compatibility matrix
|
|
run: |
|
|
pids=()
|
|
(
|
|
archive="$RUNNER_TEMP/git-2.25.5.tar.gz"
|
|
source="$HOME/.cache/orca-git-compat/git-2.25.5"
|
|
if [ ! -x "$source/git" ]; then
|
|
curl -fsSL https://www.kernel.org/pub/software/scm/git/git-2.25.5.tar.gz -o "$archive"
|
|
echo "41662c52fc16fec4963bfc41075e71f8ead6b5e386797eb6f9a1111ff95a8ddf $archive" \
|
|
| sha256sum --check
|
|
mkdir -p "$source"
|
|
tar -xzf "$archive" -C "$source" --strip-components=1
|
|
make -C "$source" -j"$(nproc)" \
|
|
NO_GETTEXT=YesPlease NO_TCLTK=YesPlease NO_PYTHON=YesPlease git
|
|
# Why: the linked binaries are what the next run needs; the objects that
|
|
# produced them are most of the tree and would bloat the cache entry.
|
|
find "$source" -name '*.o' -delete
|
|
fi
|
|
ORCA_GIT_COMPAT_BINARY="$source/git" ORCA_GIT_COMPAT_VERSION="2.25.5" \
|
|
pnpm exec vitest run --config config/vitest.config.ts \
|
|
src/shared/git-binary-compatibility.test.ts
|
|
) &
|
|
pids+=("$!")
|
|
|
|
for spec in \
|
|
"alpine/git:edge-2.38.1|2.38.1" \
|
|
"alpine/git:v2.49.1|2.49.1"; do
|
|
(
|
|
image="${spec%%|*}"
|
|
version="${spec#*|}"
|
|
ORCA_GIT_COMPAT_IMAGE="$image" ORCA_GIT_COMPAT_VERSION="$version" \
|
|
pnpm exec vitest run --config config/vitest.config.ts \
|
|
src/shared/git-binary-compatibility.test.ts
|
|
) &
|
|
pids+=("$!")
|
|
done
|
|
|
|
status=0
|
|
for pid in "${pids[@]}"; do
|
|
wait "$pid" || status=1
|
|
done
|
|
exit "$status"
|
|
|
|
xterm_patch_sync:
|
|
name: xterm patch sync
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
|
|
# Why: the check rebuilds xterm.js from a pinned upstream commit. Caching the
|
|
# npm metadata and the shallow clone turns a ~4 min cold run into well under a
|
|
# minute; the key is the manifest, so a commit or toolchain bump invalidates it.
|
|
- name: Restore upstream xterm build inputs
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.npm
|
|
${{ runner.temp }}/xterm-patch-build/upstream/.git
|
|
key: xterm-upstream-${{ hashFiles('config/patches/xterm-upstream.json') }}
|
|
|
|
- name: Verify xterm patches match the pinned upstream build
|
|
env:
|
|
WORK_DIR: ${{ runner.temp }}/xterm-patch-build
|
|
run: node config/scripts/regenerate-xterm-patches.mjs --check --work-dir="$WORK_DIR"
|
|
|
|
shell_contracts:
|
|
name: shell contracts
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
# Why: this job's cost is almost entirely package download, and a stalled mirror has
|
|
# no wall-clock bound of its own. A successful run finishes in ~4.5 minutes, so this
|
|
# is generous; it exists so a wedge fails the job instead of holding the whole run
|
|
# open for the 6h GitHub default — which also blocks `gh run rerun --failed`.
|
|
timeout-minutes: 15
|
|
env:
|
|
# Why: the suites below gate their live fish tests on the binary, which is
|
|
# right on a developer machine and wrong here — this job is a required check
|
|
# and its fish lane is the only end-to-end guard for #9993, so a skip would
|
|
# report green with nothing exercised. Turns those skips into failures.
|
|
ORCA_REQUIRE_FISH: '1'
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# Why fish: shell-ready.test.ts gates its live fish test on the binary being
|
|
# present, so without this the fish barrier is only covered by config-shape
|
|
# assertions and never actually exercised.
|
|
# Why release-4: DECSET 2031 arming lives in the fish 4.0 Rust tty_handoff, and
|
|
# fish-color-scheme-child-stdin.node-pty.test.ts (#9993) needs it. Noble ships
|
|
# 3.7, so the PPA is what makes that lane real.
|
|
- name: Install zsh and fish
|
|
run: |
|
|
# Why the update/PPA/fish steps are tolerant: a repo the runner image already
|
|
# ships can lack a Release file for this suite, and a failed add-apt-repository
|
|
# still leaves its list entry behind — either makes `apt-get update` exit
|
|
# non-zero and would red this required check over something unrelated to the
|
|
# PR. Every fish outcome is judged by the version gate below instead, so only
|
|
# the zsh install (which has no such gate) stays fatal here.
|
|
# Why retry only here: adding the PPA is the network-flaky step, and the
|
|
# version gate below is fatal, so a transient Launchpad blip would
|
|
# otherwise red a required check on PRs unrelated to shells.
|
|
# Why -n: add-apt-repository refreshes every configured repo on its own. With
|
|
# an update on each side of it this step refreshed them three times over, and
|
|
# the Azure archive mirror alone costs ~15-30s a pass. The PPA index is the
|
|
# only thing the repo list gains here, and the single update below fetches it.
|
|
# Why bound acquisition: measured on a *passing* run, this step spent 40s
|
|
# fetching 11.4 MB of index and then 2m17s fetching 8.9 MB of packages at
|
|
# 65 kB/s — it is dominated by download throughput, not by work. apt applies
|
|
# no wall-clock bound to a stalled mirror, so a slow Launchpad or archive
|
|
# host wedges the step for tens of minutes. This job is a required check, so
|
|
# a wedge holds the entire run open and blocks `gh run rerun --failed`.
|
|
# Bounded timeouts plus retries turn an unbounded hang into a fast, legible
|
|
# failure. Set in apt.conf.d rather than on each command line so the two
|
|
# invocations below stay exactly as pr-workflow-parallelism.test.mjs parses
|
|
# them. Retries are 1, not 3: a first attempt at these bounds already multiplied
|
|
# 30s x 3 retries across every index file into a ~15 minute stall on a dead
|
|
# mirror, which is worse than failing once and moving on.
|
|
sudo tee /etc/apt/apt.conf.d/99-orca-shell-contracts >/dev/null <<'APTCONF'
|
|
Acquire::http::Timeout "15";
|
|
Acquire::https::Timeout "15";
|
|
Acquire::Retries "1";
|
|
APTCONF
|
|
for attempt in 1 2 3; do
|
|
sudo add-apt-repository -y -n ppa:fish-shell/release-4 && break
|
|
echo "add-apt-repository attempt ${attempt} failed; retrying" >&2
|
|
sudo add-apt-repository -y -n -r ppa:fish-shell/release-4 || true
|
|
sleep 5
|
|
done
|
|
# Why a wall-clock bound on each command: apt's Acquire timeouts are per-connection,
|
|
# so a dead mirror costs timeout x retries x every index file. Measured: the archive
|
|
# mirror stalled with zero bytes and the step burned 14m26s before the job bound
|
|
# killed it. `timeout` is the only thing that bounds the command as a whole.
|
|
# The update is already tolerant by design (see above), so bounding it just caps
|
|
# what a dead mirror can cost before the install runs against whatever index exists.
|
|
timeout 120 sudo apt-get update || true
|
|
# Why both shells on one line: pr-workflow-parallelism.test.mjs parses only the
|
|
# first install command in this step to prove the lane really installs them.
|
|
timeout 300 sudo apt-get install -y zsh fish
|
|
|
|
# Separate from the install so the failure names the contract, not an apt error.
|
|
# ORCA_REQUIRE_FISH re-checks this at test time; this step just fails in seconds
|
|
# instead of after a full dependency install.
|
|
- name: Require fish 4+
|
|
run: |
|
|
version="$(fish --version 2>/dev/null || true)"
|
|
major="${version##*version }"
|
|
major="${major%%.*}"
|
|
case "$major" in '' | *[!0-9]*) major=0 ;; esac
|
|
echo "${version:-<fish not installed>}"
|
|
if [ "$major" -lt 4 ]; then
|
|
echo "::error::shell contracts needs fish 4+ (DECSET 2031 arming, #9993) but got '${version:-none}'. Fix the ppa:fish-shell/release-4 install rather than letting the fish lane skip." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: node
|
|
|
|
- name: Test real shell contracts
|
|
run: |
|
|
pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1 \
|
|
src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts \
|
|
src/main/daemon/shell-ready.test.ts \
|
|
src/main/daemon/node-pty-fd-leak.test.ts \
|
|
src/main/providers/local-pty-shell-ready-zsh-launch-environment.test.ts \
|
|
src/main/providers/__tests__/shell-ready-framework-example.test.ts \
|
|
src/main/pty/omp-shell-wrapper.node-pty.test.ts \
|
|
src/main/shell-startup-feature-channel.test.ts \
|
|
src/main/terminal-history-fish-session.node-pty.test.ts \
|
|
src/main/zsh-scoped-histfile.live-shell.test.ts \
|
|
src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts \
|
|
src/main/zsh-wrapper-version-mismatch.live-shell.test.ts \
|
|
src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts \
|
|
src/shared/fish-query-reply-child-stdin.node-pty.test.ts \
|
|
src/shared/startup-shell-portability.live-shell.test.ts \
|
|
src/shared/posix-command-path-lookup.test.ts
|
|
|
|
test:
|
|
name: tests node ${{ matrix.node }} ${{ matrix.shard }}/${{ matrix.shard_total }}
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
node: ['24', '26']
|
|
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
|
|
shard_total: [16]
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: node
|
|
node-version: ${{ matrix.node }}
|
|
|
|
- name: Install Electron package binary for tests
|
|
run: node config/scripts/install-electron-package-binary.mjs
|
|
|
|
- name: Test shard
|
|
run: |
|
|
pnpm exec vitest run --config config/vitest.config.ts \
|
|
--exclude=src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts \
|
|
--exclude=src/main/daemon/shell-ready.test.ts \
|
|
--exclude=src/main/daemon/node-pty-fd-leak.test.ts \
|
|
--exclude=src/main/providers/local-pty-shell-ready-zsh-launch-environment.test.ts \
|
|
--exclude=src/main/providers/__tests__/shell-ready-framework-example.test.ts \
|
|
--exclude=src/main/pty/omp-shell-wrapper.node-pty.test.ts \
|
|
--exclude=src/main/shell-startup-feature-channel.test.ts \
|
|
--exclude=src/main/terminal-history-fish-session.node-pty.test.ts \
|
|
--exclude=src/main/zsh-scoped-histfile.live-shell.test.ts \
|
|
--exclude=src/main/zsh-startup-hook-user-config-equivalence.live-shell.test.ts \
|
|
--exclude=src/main/zsh-wrapper-version-mismatch.live-shell.test.ts \
|
|
--exclude=src/renderer/src/components/terminal-pane/fish-color-scheme-child-stdin.node-pty.test.ts \
|
|
--exclude=src/shared/fish-query-reply-child-stdin.node-pty.test.ts \
|
|
--exclude=src/shared/startup-shell-portability.live-shell.test.ts \
|
|
--exclude=src/shared/posix-command-path-lookup.test.ts \
|
|
--exclude=tests/e2e/cross-version-wire/** \
|
|
--shard=${{ matrix.shard }}/${{ matrix.shard_total }}
|
|
|
|
cross-version-wire:
|
|
name: cross-version wire compatibility
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
# Why fetch-depth 0: the harness extracts the newest release tag to skew
|
|
# current code against it. The default shallow clone has no tags, which is
|
|
# why this cannot ride along in the sharded `test` job.
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# Why blob:none: full history is needed for the merge-base diff, but historical
|
|
# file contents are not. Blobs are ~89% of this repo's pack, and Git fetches the
|
|
# few this job actually reads on demand.
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: node
|
|
|
|
# A path filter that matches nothing exits 1 ("No test files found"), so this
|
|
# lane cannot report success while running zero tests.
|
|
- name: Old/new client and server terminal journey
|
|
run: pnpm exec vitest run --config config/vitest.config.ts tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts
|
|
|
|
managed_hook_node18:
|
|
name: managed hooks on Node 18
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
|
|
- name: Build relay companions
|
|
run: pnpm run build:relay
|
|
|
|
- name: Setup Node 18 runtime
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '18'
|
|
|
|
- name: Smoke managed-hook companions
|
|
run: node config/scripts/smoke-managed-hook-runtime-node18.mjs
|
|
|
|
package:
|
|
name: package
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Cache electron-builder downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~/.cache/electron
|
|
~/.cache/electron-builder
|
|
key: electron-builder-linux-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
electron-builder-linux-
|
|
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: electron
|
|
|
|
- name: Build package inputs
|
|
run: |
|
|
status=0
|
|
pnpm run build:cli || status=1
|
|
|
|
scripts=(build:relay build:electron-vite:parallel)
|
|
pids=()
|
|
for script in "${scripts[@]}"; do
|
|
pnpm run "$script" &
|
|
pids+=("$!")
|
|
done
|
|
|
|
for pid in "${pids[@]}"; do
|
|
wait "$pid" || status=1
|
|
done
|
|
exit "$status"
|
|
|
|
- name: Project web client from renderer build
|
|
run: pnpm run build:web-from-renderer
|
|
|
|
- name: Build native components
|
|
run: pnpm run build:native
|
|
|
|
- name: Package unpacked app
|
|
env:
|
|
ORCA_REUSE_PREPARED_NATIVE_RUNTIME: '1'
|
|
run: pnpm exec electron-builder --config config/electron-builder.config.cjs --linux AppImage --x64 --publish never
|
|
|
|
- name: Verify headless serve signal shutdown
|
|
run: node config/scripts/run-headless-serve-shutdown-docker.mjs --appimage dist/orca-linux.AppImage
|
|
|
|
- name: Smoke packaged CLI
|
|
run: node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/linux-unpacked
|
|
|
|
- name: Smoke packaged hang watchdog worker
|
|
run: xvfb-run --auto-servernum node config/scripts/smoke-packaged-hang-watchdog-worker.mjs --app-dir=dist/linux-unpacked
|
|
|
|
package_windows:
|
|
name: package (windows)
|
|
needs: [code_paths]
|
|
if: needs.code_paths.outputs.should_run == 'true'
|
|
runs-on: windows-2022
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@v6
|
|
with:
|
|
run_install: false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version-file: package.json
|
|
cache: pnpm
|
|
|
|
- name: Cache electron-builder downloads
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: |
|
|
~\AppData\Local\electron\Cache
|
|
~\AppData\Local\electron-builder\Cache
|
|
key: electron-builder-windows-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
electron-builder-windows-
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# Why: node-pty prefers its upstream prebuild, which does not contain
|
|
# Orca's Windows patch, so the job-object exports would be absent and the
|
|
# suite below would test an unpatched binary. build_from_source removes
|
|
# the prebuild, and the package's postinstall restores the ConPTY runtime
|
|
# files that a bare node-gyp rebuild would miss.
|
|
- name: Rebuild node-pty from patched source
|
|
env:
|
|
npm_config_build_from_source: 'true'
|
|
run: pnpm rebuild node-pty
|
|
|
|
- name: Test Windows-specific boundaries
|
|
run: >-
|
|
pnpm exec vitest run --config config/vitest.config.ts
|
|
src/main/providers/windows-conpty-wide-char-duplication.node-pty.test.ts
|
|
src/main/providers/pty-repaint-wide-char-buffer.node-pty.test.ts
|
|
src/shared/child-process/windows-command-line.win32.test.ts
|
|
src/main/windows/windows-pty-job.win32.test.ts
|
|
src/main/windows/windows-host-job.win32.test.ts
|
|
src/main/wsl/wsl-runner.test.ts
|
|
src/main/wsl/wsl-guest-environment.test.ts
|
|
src/main/wsl/wsl-invocation-boundary.test.ts
|
|
src/main/wsl/wsl-executable-path.win32.test.ts
|
|
src/main/wsl/wsl-w1-w3-contract.test.ts
|
|
src/shared/source-scan/source-tree-scan.test.ts
|
|
src/main/cli/wsl-cli-powershell-boundary.test.ts
|
|
src/main/orca-profiles/profile-index-store.test.ts
|
|
src/main/runtime/repo-worktree-admin-fingerprint.test.ts
|
|
src/main/runtime/worktree-scan-admin-fingerprint-gate.test.ts
|
|
src/shared/secure-file-fsync-flags.test.ts
|
|
|
|
# Why the :parallel variant: identical to build:release except the three
|
|
# electron-vite targets overlap instead of running back to back. The Linux package
|
|
# job already packages and smoke-tests an AppImage built that way.
|
|
- name: Build package inputs
|
|
run: pnpm run build:release:parallel
|
|
|
|
- name: Prepare Electron native runtime
|
|
run: node config/scripts/ensure-native-runtime.mjs --runtime=electron
|
|
|
|
- name: Package unpacked app
|
|
env:
|
|
ORCA_REUSE_PREPARED_NATIVE_RUNTIME: '1'
|
|
run: pnpm exec electron-builder --config config/electron-builder.config.cjs --dir
|
|
|
|
- name: Smoke packaged CLI
|
|
run: node config/scripts/smoke-packaged-cli.mjs --app-dir=dist/win-unpacked
|
|
|
|
# Why: PR E2E is advisory and only validates changed specs; scheduled and
|
|
# release runs retain full-suite coverage.
|
|
e2e-paths:
|
|
name: detect changed e2e specs
|
|
needs: [code_paths]
|
|
runs-on: ubuntu-latest
|
|
if: github.event.pull_request.draft != true && needs.code_paths.outputs.should_run == 'true'
|
|
# Why: detector only needs to read the checkout; do not inherit repo defaults.
|
|
permissions:
|
|
contents: read
|
|
outputs:
|
|
should_run: ${{ steps.filter.outputs.should_run }}
|
|
test_files: ${{ steps.filter.outputs.test_files }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
# Why blob:none: full history is needed for the merge-base diff, but historical
|
|
# file contents are not. Blobs are ~89% of this repo's pack, and Git fetches the
|
|
# few this job actually reads on demand.
|
|
fetch-depth: 0
|
|
filter: blob:none
|
|
persist-credentials: false
|
|
|
|
- name: Filter changed E2E specs
|
|
id: filter
|
|
run: |
|
|
set -euo pipefail
|
|
BASE="${{ github.event.pull_request.base.sha }}"
|
|
HEAD="${{ github.event.pull_request.head.sha }}"
|
|
CHANGED="$(git diff --name-only --diff-filter=AMCR --merge-base "$BASE" "$HEAD")"
|
|
TEST_FILES="$(printf '%s\n' "$CHANGED" | grep -E '^tests/e2e/.*\.spec\.ts$' || true)"
|
|
if printf '%s\n' "$CHANGED" | grep -Eq '^src/(main/ephemeral-vm-(runtime-(service|provisioning-persistence)|failed-start-cleanup)|shared/(ephemeral-vm-runtime-(store|feature-store|rollback-projection|runtimes)|ephemeral-vm-recipes|orca-yaml-hook-types))\.ts$'; then
|
|
TEST_FILES="$(printf '%s\n%s\n' "$TEST_FILES" 'tests/e2e/ephemeral-vm-provisioned-root.spec.ts' | sort -u)"
|
|
fi
|
|
# Why: the Docker-SSH specs only ever ran when someone edited a spec, so four
|
|
# pane-restore regressions shipped from SSH source edits that touched no test.
|
|
# Unit tests are excluded because they prove the same source without Docker.
|
|
# ssh-startup-exec-readiness.spec.ts is listed to route e2e.yml's dedicated
|
|
# ssh-docker-watcher-isolation lane, which filters it out of changed-e2e.
|
|
if printf '%s\n' "$CHANGED" | grep -Ev '\.test\.tsx?$' | grep -Eq '^(src/main/ssh/|src/main/providers/ssh-|src/main/ipc/(ssh-|pty)|src/relay/|src/renderer/src/components/terminal-pane/(pty-|ssh-|remote-runtime-|terminal-parked-pty))'; then
|
|
TEST_FILES="$(printf '%s\n' "$TEST_FILES" \
|
|
'tests/e2e/pty-input-write-queue-ssh.spec.ts' \
|
|
'tests/e2e/ssh-cold-activation-restore.spec.ts' \
|
|
'tests/e2e/ssh-docker-reconnect-pane-restore.spec.ts' \
|
|
'tests/e2e/ssh-startup-exec-readiness.spec.ts' \
|
|
'tests/e2e/ssh-terminal-window-wake-stale-grid-repro.spec.ts' | sort -u)"
|
|
fi
|
|
# Why: same lesson as the SSH specs above. The IME e2e suite asserts real pty bytes and
|
|
# real overlay geometry, but only ran when someone edited a spec, so every IME fix in the
|
|
# 1.4.18x window shipped without it - including one whose diagnosis hardware later refuted.
|
|
# config/patches is listed because the terminal's input handling lives in the xterm patch.
|
|
if printf '%s\n' "$CHANGED" | grep -Ev '\.test\.tsx?$' | grep -Eq '^(config/patches/|src/renderer/src/components/terminal-pane/(terminal-ime-|use-terminal-pane-lifecycle|xterm-bypass-policy|terminal-option-shortcut-policy))'; then
|
|
TEST_FILES="$(printf '%s\n' "$TEST_FILES" \
|
|
'tests/e2e/terminal-cjk-ime-committed-text.spec.ts' \
|
|
'tests/e2e/terminal-hangul-wrap-boundary-bytes.spec.ts' \
|
|
'tests/e2e/terminal-ime-exact-byte.spec.ts' \
|
|
'tests/e2e/terminal-korean-composing-chord-order.spec.ts' \
|
|
'tests/e2e/terminal-korean-endofrow-preedit-cell-span.spec.ts' \
|
|
'tests/e2e/terminal-korean-midline-preedit-occlusion.spec.ts' \
|
|
'tests/e2e/terminal-korean-preedit-visibility.spec.ts' | sort -u)"
|
|
fi
|
|
TEST_FILES_JSON="$(printf '%s\n' "$TEST_FILES" | jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))')"
|
|
echo "test_files=$TEST_FILES_JSON" >> "$GITHUB_OUTPUT"
|
|
if [ "$TEST_FILES_JSON" != '[]' ]; then
|
|
echo "should_run=true" >> "$GITHUB_OUTPUT"
|
|
echo "Changed E2E specs: $TEST_FILES_JSON"
|
|
else
|
|
echo "should_run=false" >> "$GITHUB_OUTPUT"
|
|
echo "No changed E2E specs"
|
|
fi
|
|
|
|
e2e:
|
|
name: e2e
|
|
needs: e2e-paths
|
|
if: needs.e2e-paths.outputs.should_run == 'true'
|
|
# Why: reusable e2e.yml only checkouts, builds, and uploads artifacts.
|
|
permissions:
|
|
contents: read
|
|
uses: ./.github/workflows/e2e.yml
|
|
with:
|
|
test_files: ${{ needs.e2e-paths.outputs.test_files }}
|
|
|
|
verify:
|
|
if: always()
|
|
needs:
|
|
- code_paths
|
|
- static_analysis
|
|
- root_directory_guard
|
|
- typecheck
|
|
- git_compatibility
|
|
- xterm_patch_sync
|
|
- shell_contracts
|
|
- test
|
|
- managed_hook_node18
|
|
- package
|
|
- package_windows
|
|
runs-on: ubuntu-latest
|
|
|
|
steps:
|
|
# Why: e2e is deliberately absent from needs. The suite is currently red on
|
|
# main (every scheduled run), so gating merges on it would block any PR that
|
|
# touches tests/e2e/** — including the ones fixing the suite. Until it is
|
|
# green the job runs and reports for E2E-path PRs without blocking. To flip
|
|
# it on: add `e2e` to needs, add E2E to the env below, and require
|
|
# `"$E2E" = success || skipped` after the loop — skipped is the normal
|
|
# result for a path-filtered job and must keep passing, so it has to be
|
|
# checked outside the loop or it would excuse the jobs above.
|
|
- name: Require successful checks
|
|
env:
|
|
CODE_PATHS: ${{ needs.code_paths.result }}
|
|
SHOULD_RUN: ${{ needs.code_paths.outputs.should_run }}
|
|
STATIC_ANALYSIS: ${{ needs.static_analysis.result }}
|
|
ROOT_DIRECTORY_GUARD: ${{ needs.root_directory_guard.result }}
|
|
TYPECHECK: ${{ needs.typecheck.result }}
|
|
GIT_COMPATIBILITY: ${{ needs.git_compatibility.result }}
|
|
XTERM_PATCH_SYNC: ${{ needs.xterm_patch_sync.result }}
|
|
SHELL_CONTRACTS: ${{ needs.shell_contracts.result }}
|
|
TEST: ${{ needs.test.result }}
|
|
MANAGED_HOOK_NODE18: ${{ needs.managed_hook_node18.result }}
|
|
PACKAGE: ${{ needs.package.result }}
|
|
PACKAGE_WINDOWS: ${{ needs.package_windows.result }}
|
|
run: |
|
|
if [ "$CODE_PATHS" != "success" ]; then
|
|
exit 1
|
|
fi
|
|
if [ "$SHOULD_RUN" != "true" ]; then
|
|
echo "Docs-only change; expensive PR checks skipped."
|
|
if [ "$ROOT_DIRECTORY_GUARD" != "success" ]; then
|
|
exit 1
|
|
fi
|
|
for result in \
|
|
"$STATIC_ANALYSIS" \
|
|
"$TYPECHECK" \
|
|
"$GIT_COMPATIBILITY" \
|
|
"$XTERM_PATCH_SYNC" \
|
|
"$SHELL_CONTRACTS" \
|
|
"$TEST" \
|
|
"$MANAGED_HOOK_NODE18" \
|
|
"$PACKAGE" \
|
|
"$PACKAGE_WINDOWS"; do
|
|
if [ "$result" != "skipped" ]; then
|
|
exit 1
|
|
fi
|
|
done
|
|
exit 0
|
|
fi
|
|
# Require success when the PR has code-relevant changes
|
|
for result in \
|
|
"$CODE_PATHS" \
|
|
"$STATIC_ANALYSIS" \
|
|
"$ROOT_DIRECTORY_GUARD" \
|
|
"$TYPECHECK" \
|
|
"$GIT_COMPATIBILITY" \
|
|
"$XTERM_PATCH_SYNC" \
|
|
"$SHELL_CONTRACTS" \
|
|
"$TEST" \
|
|
"$MANAGED_HOOK_NODE18" \
|
|
"$PACKAGE" \
|
|
"$PACKAGE_WINDOWS"; do
|
|
if [ "$result" != "success" ]; then
|
|
exit 1
|
|
fi
|
|
done
|