mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 09:37:33 +00:00
* Let in-page links switch sections inside a chat visual * Make in-page links work in chat visuals on mobile and in WebKit The first fix (an about:srcdoc base in the visual) only reached desktop Chromium. On mobile, the page hosting the visual carried a policy with base-uri 'none', and a srcdoc visual inherits its host's policy before its own, so the base was refused and an in-page link still took the visual off its page and showed it as failed. The mobile web shell sends the same rule as a header. Separately, WebKit (iOS, and the web client in Safari) fires the frame's load event for an in-page link inside a sandboxed frame, and both hosts treated any second load as the visual navigating away and retired it. - The mobile host page and the mobile web shell header now allow only an about: base, so the shell's own base applies; the visual's own policy still refuses any base it declares. - Instead of retiring a visual on its second load, the host now asks the visual's shell after every load. A later load must be answered by the same run of the shell within the plugin panel watchdog's 5 s deadline; no answer, or an answer from a new run (a reload), still retires it. - Tests: unit tests for the policy, the answer reader and the frame's keep/retire rules, and a Chromium + WebKit test of both mobile surfaces that runs in the mobile web app CI job. * Keep any answered later load, and say plainly what the check is for The previous commit tagged each run of a visual's wrapper with a random id so a reload would still retire it. The visual shares a window with its wrapper, so it could read or set that id itself, and a reload only reruns the same Orca-written document, which is not an escape. Drop it: after any load past the first, the host asks once, and any answer on this visual's channel within the wait keeps it; silence retires it. - Comments now say the check is a tripwire for a frame gone blank or to another page; the frame guard (desktop), the host page's policy plus the app's load filter (mobile) and the sandbox are the boundaries. - Visuals get their own 5 s wait instead of borrowing the plugin panel watchdog's, because a 1 s wait retired a busy visual in WebKit. - Tests: a reload is now kept; going blank, or dropping the base and following a link, still retires. The CI routing for the shared wrapper is now pinned in the path-classification test. * Load the visual links check's mobile modules only where they can build The unit shards run every config/scripts test with only the root dependencies installed. This check imported a mobile/ module at load, and transforming it reads mobile/tsconfig.json, which extends a package those shards do not have. The transform error could not be sent back to the test worker under Bun, so the whole shard stopped with "DataCloneError: The object can not be cloned". Import the visual shell and the mobile host page inside the dependency gate, as the session-dictation check already does, so the shards skip the file without loading them. The mobile web app job, which installs mobile dependencies, still runs it in full.
620 lines
24 KiB
JavaScript
620 lines
24 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import process from 'node:process'
|
|
import { pathToFileURL } from 'node:url'
|
|
|
|
const DOCS_ONLY_FILES = new Set([
|
|
'README.md',
|
|
'LICENSE',
|
|
'AGENTS.md',
|
|
'CLAUDE.md',
|
|
'Agents.md',
|
|
'Claude.md',
|
|
'.github/CONTRIBUTING.md',
|
|
'.github/pull_request_template.md',
|
|
'.github/CODEOWNERS'
|
|
])
|
|
|
|
const DOCS_ONLY_PREFIXES = ['docs/', '.github/ISSUE_TEMPLATE/']
|
|
|
|
export const PR_CHECK_JOBS = [
|
|
'static_analysis',
|
|
'typecheck',
|
|
'git_compatibility',
|
|
'codex_index_heal_contract',
|
|
'xterm_patch_sync',
|
|
'shell_contracts',
|
|
'test',
|
|
'orcad_browser',
|
|
'mobile_web_app',
|
|
'cross-version-wire',
|
|
'managed_hook_node18',
|
|
'package',
|
|
'package_windows'
|
|
]
|
|
|
|
const ALWAYS_ON_CODE_JOBS = new Set(['static_analysis', 'typecheck', 'test'])
|
|
|
|
const GLOBAL_FORCE_PREFIXES = [
|
|
'.github/workflows/pr.yml',
|
|
'.github/actions/install-node-dependencies/',
|
|
'.github/actions/restore-pnpm-verification/',
|
|
'.github/actions/prepare-native-runtime/',
|
|
'config/scripts/pr-code-change-scope'
|
|
]
|
|
|
|
const GLOBAL_FORCE_FILES = new Set(['package.json', 'pnpm-lock.yaml'])
|
|
|
|
const GIT_COMPAT_PREFIXES = [
|
|
'.github/actions/prepare-git-compatibility/',
|
|
'src/shared/git-',
|
|
'src/shared/review-head-tracking-ref',
|
|
'src/shared/worktree/local-base-branch-fast-forward',
|
|
'src/main/git/',
|
|
'src/relay/git-',
|
|
'config/scripts/git-binary-compatibility'
|
|
]
|
|
|
|
// Why narrow: the contract pins Codex's read-repair, so it runs when the heal that
|
|
// depends on it, its app-server transport, or the contract itself changes. The same
|
|
// job pins --no-daemon for Orca's codex shell wrapper, the project-trust key, and the
|
|
// approval Orca writes for its hook entry in managed Codex homes and ~/.codex.
|
|
const CODEX_INDEX_HEAL_CONTRACT_PREFIXES = [
|
|
'src/main/codex/codex-hook-file-entry-binary-contract',
|
|
'src/main/codex/codex-hook-trust-',
|
|
'src/main/codex/codex-hook-approval-first-write',
|
|
'src/main/codex/codex-hook-hash-lookup',
|
|
'src/main/codex/codex-hook-orca-approvals',
|
|
'src/main/codex/codex-hook-reconcile',
|
|
'src/main/codex/codex-hook-local-install',
|
|
'src/main/codex/codex-hook-user-mirroring',
|
|
'src/main/codex/codex-real-home-hook-',
|
|
'src/main/codex/codex-real-home-hooks-json',
|
|
'src/main/codex/codex-user-hook-trust-moves',
|
|
'src/main/codex/codex-hook-definition',
|
|
'src/main/codex/codex-hook-command-form',
|
|
'src/main/codex/codex-hook-identity',
|
|
'src/main/codex/codex-app-server-client',
|
|
'src/main/codex/codex-trust-identity',
|
|
'src/main/codex/config-toml-hook-trust-',
|
|
'src/main/codex/config-toml-key-path',
|
|
'src/main/agent-hooks/posix-hook-command',
|
|
'src/main/agent-hooks/hook-post-command',
|
|
'src/main/codex-cli/codex-read-only-app-server-args',
|
|
'src/main/agent-trust-presets',
|
|
'src/main/codex/config-toml-trust',
|
|
'src/main/pty/codex-no-daemon-binary-contract',
|
|
'src/main/pty/codex-shell-launch-preflight',
|
|
'src/shared/codex-shell-function',
|
|
'src/main/codex/codex-index-heal-binary-contract',
|
|
'src/main/codex/codex-session-index-heal',
|
|
'src/main/codex/codex-app-server-session',
|
|
'src/main/codex/codex-state-db',
|
|
'src/main/sqlite/sync-database',
|
|
'src/main/codex/codex-app-server-capability-signal',
|
|
'src/main/provider-process/provider-process-exit-deadline',
|
|
'src/main/provider-process/provider-process-launch',
|
|
'src/main/provider-process/provider-record-reader',
|
|
'src/main/codex/codex-session-backfill',
|
|
'src/main/codex/codex-session-index-heal-state',
|
|
'src/main/codex-cli/command',
|
|
'src/main/win32-utils',
|
|
'src/shared/node-cli-command-resolution',
|
|
'src/shared/windows-batch-spawn'
|
|
]
|
|
|
|
const XTERM_PREFIXES = [
|
|
'config/patches/xterm-upstream.json',
|
|
'config/patches/@xterm',
|
|
'config/patches/xterm-src/',
|
|
'config/scripts/regenerate-xterm-patches'
|
|
]
|
|
|
|
const SHELL_PREFIXES = [
|
|
'src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec',
|
|
'src/main/daemon/shell-ready',
|
|
'src/main/daemon/daemon-bash-shell-ready',
|
|
'src/main/daemon/daemon-shell-ready-wrapper',
|
|
'src/main/daemon/node-pty-fd-leak',
|
|
'src/main/providers/local-pty-shell-ready',
|
|
'src/main/providers/__tests__/shell-ready-framework-example',
|
|
'src/main/pty/',
|
|
'src/main/shell-templates',
|
|
'src/main/shell-startup-',
|
|
'src/main/shell-wrapper-',
|
|
'src/main/terminal-history-fish',
|
|
'src/main/zsh-',
|
|
'src/main/runtime/structured-session-cli-login-shell',
|
|
'src/main/runtime/structured-session-login-shell-test-harness',
|
|
'src/main/runtime/structured-session-child-identity-env',
|
|
'src/renderer/src/components/terminal-pane/fish-color-scheme',
|
|
'src/shared/fish-',
|
|
'src/shared/pty-reply-echo-shapes',
|
|
'src/shared/startup-shell-portability',
|
|
'src/shared/posix-command-path-lookup',
|
|
'config/patches/node-pty@',
|
|
'config/scripts/ensure-native-runtime',
|
|
'config/scripts/node-pty-job-ownership'
|
|
]
|
|
|
|
const ORCAD_BROWSER_PREFIXES = [
|
|
'src/main/orcad/external-chromium-',
|
|
'src/main/orcad/orcad-browser-provider',
|
|
'src/main/orcad/orcad-agent-browser-binary',
|
|
'src/main/orcad/electron-serve-browser-process'
|
|
]
|
|
|
|
// The page bundle the desktop packages: the builder and verifier, the manifest writer and the
|
|
// packaging guard they share, the entry, the route tree it mounts, the mobile source those routes
|
|
// import, and the shell policy the render check runs the page under.
|
|
const MOBILE_WEB_APP_PREFIXES = [
|
|
'config/scripts/build-mobile-web-app',
|
|
'config/scripts/run-mobile-web-app-checks',
|
|
'config/scripts/script-child-process.mjs',
|
|
'src/shared/child-process/',
|
|
'config/scripts/verify-mobile-web-app-bundle',
|
|
'config/scripts/mobile-web-app-',
|
|
'config/scripts/mobile-web-bundle-',
|
|
'config/scripts/verify-packaged-mobile-web-bundle',
|
|
'config/scripts/mobile-web-source-line-endings',
|
|
'config/scripts/script-entry-detection',
|
|
'mobile/web-entry/',
|
|
'mobile/app/',
|
|
'mobile/src/',
|
|
'mobile/packages/',
|
|
'mobile/package.json',
|
|
'mobile/pnpm-lock.yaml',
|
|
'mobile/modules/orca-mobile-web-shell/',
|
|
// Chat visuals on both mobile surfaces are built from it; their in-page links check runs here.
|
|
'src/shared/native-chat-visual-shell'
|
|
]
|
|
|
|
function changesMobileWebApp(changedFiles) {
|
|
return changedFiles.some((file) => matchesPrefix(file, MOBILE_WEB_APP_PREFIXES))
|
|
}
|
|
|
|
const CROSS_VERSION_WIRE_PREFIXES = [
|
|
'tests/e2e/cross-version-wire/',
|
|
'config/scripts/stable-release-tags',
|
|
// The R1 daemon protocol crossing gate runs in this job.
|
|
'config/scripts/daemon-protocol-facts',
|
|
'config/scripts/check-daemon-protocol-crossing',
|
|
// R3 runtime launcher protocol ratchet; a bump always routes here via the protocol file.
|
|
'config/scripts/check-runtime-launcher-protocol-ratchet',
|
|
'src/main/daemon/daemon-protocol-version.ts',
|
|
'src/shared/protocol-version',
|
|
'src/shared/terminal-stream-protocol',
|
|
'src/shared/browser-client-host-protocol',
|
|
'src/shared/browser-network-tunnel-protocol',
|
|
'src/shared/browser-client-host-placement',
|
|
'src/shared/agent-launch-intent',
|
|
'src/shared/rpc-contract/agent-launch-params',
|
|
'src/shared/agent-session-wire',
|
|
'src/shared/agent-session-mutation-envelope',
|
|
// The send a client builds (the agent-session suite sends it to the release host) and the
|
|
// fingerprint the host's ledger and journal re-derive.
|
|
'src/shared/structured-agent-session-mutation.ts',
|
|
'src/shared/structured-agent-session-send-mutation.ts',
|
|
'src/shared/agent-session-record',
|
|
'src/shared/agent-session-provider-handle',
|
|
'src/shared/agent-session-journal-',
|
|
'src/main/ai-vault/structured-session-ownership.ts',
|
|
'src/main/native-chat/agent-session-journal/',
|
|
'src/main/native-chat/agent-session-wire/',
|
|
'src/main/runtime/agent-session-record-store',
|
|
'src/main/runtime/agent-session-recovery-capsule',
|
|
'src/shared/agent-session-resume-marker',
|
|
'src/main/runtime/rpc/dispatcher',
|
|
// Run on every request the suites dispatch, whatever its method.
|
|
'src/main/runtime/rpc/core.ts',
|
|
'src/main/runtime/rpc/errors.ts',
|
|
'src/main/runtime/rpc/rpc-streaming-dispatcher.ts',
|
|
'src/main/runtime/rpc/orchestration-contract-fence.ts',
|
|
'src/main/runtime/rpc/orchestration-session-caller.ts',
|
|
'src/main/runtime/rpc/orchestration-legacy-compatibility.ts',
|
|
'src/main/runtime/rpc/orchestration-mutation-executor.ts',
|
|
'src/shared/orchestration-rpc-contract.ts',
|
|
'src/main/runtime/rpc/methods/agent-launch',
|
|
'src/main/runtime/rpc/methods/ai-vault.ts',
|
|
'src/main/runtime/rpc/methods/browser-tab-create-schema',
|
|
'src/main/runtime/rpc/methods/session-tabs.ts',
|
|
'src/main/runtime/rpc/methods/structured-agent-session',
|
|
'src/main/runtime/rpc/methods/terminal',
|
|
'src/main/runtime/runtime-worktree-agent-',
|
|
'src/main/runtime/runtime-worktree-pty-agent-sources',
|
|
'src/shared/runtime-worktree-contracts',
|
|
'src/renderer/src/runtime/remote-runtime-terminal-multiplexer',
|
|
// Turn-end status a newer host publishes and an older desktop reads (cross-version-host-observed-turn-end).
|
|
'src/shared/agent-turn-outcome',
|
|
'src/shared/agent-status-types',
|
|
'src/shared/agent-lead-status-fold',
|
|
'src/shared/agent-session-turn-record',
|
|
'src/shared/structured-agent-session-agent-status',
|
|
'src/shared/structured-agent-session-projection',
|
|
'src/shared/workspace-session-sleeping-agents',
|
|
// A current desktop's launch route against a released server's capabilities (cross-version-paired-structured-launch).
|
|
'src/shared/structured-native-chat-launch-route.ts',
|
|
'src/renderer/src/lib/agent-launch-routing.ts',
|
|
'src/renderer/src/runtime/paired-host-client-capabilities.ts',
|
|
'src/shared/electron-remote-runtime-client-capabilities.ts',
|
|
'src/shared/remote-runtime-client-capabilities.ts',
|
|
// An older app opening a newer orchestration database (orchestration-delivery-downgrade).
|
|
'src/main/runtime/orchestration/db.ts',
|
|
'src/main/runtime/orchestration/db/',
|
|
'src/main/runtime/orchestration/orchestration-schema-version-skew'
|
|
]
|
|
|
|
const MANAGED_HOOK_PREFIXES = [
|
|
'config/scripts/smoke-managed-hook-runtime-node18',
|
|
'config/scripts/build-relay',
|
|
'src/relay/',
|
|
'src/shared/agent-hook',
|
|
'src/main/agent-hooks/'
|
|
]
|
|
|
|
const NATIVE_RUNTIME_PREFIXES = [
|
|
'config/scripts/ensure-native-runtime',
|
|
'config/scripts/rebuild-native-deps',
|
|
'config/scripts/node-pty-job-ownership',
|
|
'config/scripts/windows-pe-machine',
|
|
'config/scripts/windows-pe-image-fixture',
|
|
'config/scripts/script-module-dependencies',
|
|
'config/scripts/windows-process-tree-creation-time',
|
|
'config/scripts/windows-process-tree-gyp-rebuild',
|
|
'config/scripts/electron-builder-native-rebuild',
|
|
'config/patches/node-pty@',
|
|
'config/patches/@vscode__windows-process-tree'
|
|
]
|
|
|
|
const NATIVE_CACHE_FILES = new Set([
|
|
'package.json',
|
|
'pnpm-lock.yaml',
|
|
'.github/actions/install-node-dependencies/action.yml',
|
|
'.github/actions/prepare-native-runtime/action.yml',
|
|
'pnpm-workspace.yaml',
|
|
'.npmrc',
|
|
'.pnpmfile.cjs',
|
|
'config/scripts/ensure-native-runtime.mjs',
|
|
'config/scripts/rebuild-native-deps.mjs',
|
|
'config/scripts/node-pty-job-ownership.cjs',
|
|
'config/scripts/windows-pe-machine.cjs',
|
|
'config/scripts/windows-process-tree-gyp-rebuild.mjs',
|
|
'config/scripts/windows-process-tree-creation-time.cjs',
|
|
'config/scripts/install-electron-package-binary.mjs',
|
|
'config/scripts/electron-platform-path.mjs',
|
|
'config/scripts/zip-extractor-command.mjs',
|
|
'src/shared/zip-extractor-command.ts',
|
|
'config/scripts/shared-electron-dist-cache.mjs',
|
|
'config/scripts/space-sharing-copy.mjs',
|
|
'native/windows-registry/src/addon.cc',
|
|
'native/windows-registry/binding.gyp',
|
|
'native/windows-registry/package.json',
|
|
'native/windows-registry/index.js'
|
|
])
|
|
|
|
const NATIVE_CACHE_PREFIXES = [
|
|
'config/patches/node-pty@',
|
|
'config/patches/@vscode__windows-process-tree'
|
|
]
|
|
|
|
const SHARED_PACKAGE_PREFIXES = [
|
|
'electron.vite.config.ts',
|
|
'config/electron-builder',
|
|
'config/packaged-runtime',
|
|
'config/build-plugins/',
|
|
'config/scripts/build-',
|
|
'config/scripts/smoke-packaged',
|
|
'config/scripts/install-electron-package-binary',
|
|
'config/scripts/verify-packaged',
|
|
'config/scripts/verify-skills-cli-runtime',
|
|
'config/scripts/verify-linux-glibc',
|
|
'config/scripts/run-electron-vite',
|
|
'skills/',
|
|
'skill-guides/',
|
|
'resources/build/',
|
|
'resources/onboarding/',
|
|
'resources/plugins/',
|
|
'resources/skills/',
|
|
...NATIVE_RUNTIME_PREFIXES
|
|
]
|
|
|
|
const LINUX_PACKAGE_PREFIXES = [
|
|
...SHARED_PACKAGE_PREFIXES,
|
|
'config/scripts/package-linux-formats',
|
|
'config/scripts/script-child-process.mjs',
|
|
'config/scripts/space-sharing-copy.mjs',
|
|
'.github/actions/prepare-linux-package-fixture/',
|
|
'config/docker/cli-launch-contract/',
|
|
'config/docker/headless-pairing/',
|
|
'config/docker/headless-serve-shutdown/',
|
|
'config/docker/daemon-shutdown-descendants/',
|
|
'config/scripts/run-linux-cli-launch-contract',
|
|
'config/scripts/run-headless-linux-pairing-docker',
|
|
'config/scripts/run-daemon-shutdown-descendants-docker',
|
|
'config/scripts/static-appimage-package-contract',
|
|
'native/computer-use-linux/',
|
|
'resources/linux/',
|
|
'config/scripts/run-headless-serve'
|
|
]
|
|
|
|
const WINDOWS_PACKAGE_PREFIXES = [
|
|
...SHARED_PACKAGE_PREFIXES,
|
|
'native/windows-cli-launcher/',
|
|
'native/computer-use-windows/',
|
|
'resources/win32/',
|
|
'config/scripts/build-windows-cli-launcher',
|
|
'config/scripts/windows-pty-native-capability',
|
|
'tests/tools/windows-pty-native-capability-smoke/'
|
|
]
|
|
|
|
const LINUX_PACKAGE_TESTS = [
|
|
'src/main/browser/browser-client-page-renderer-lifecycle.electron.test.ts',
|
|
'src/main/browser/browser-route-tcp-egress.electron.test.ts',
|
|
'src/main/browser/browser-route-webrtc-egress.electron.test.ts',
|
|
'src/main/browser/browser-route-h3-egress.electron.test.ts',
|
|
'src/main/browser/browser-route-dns-prefetch.electron.test.ts',
|
|
'src/main/persistence/profile-state/profile-state-writer-stall.electron.test.ts'
|
|
]
|
|
|
|
const WINDOWS_PACKAGE_TESTS = [
|
|
...LINUX_PACKAGE_TESTS,
|
|
'config/scripts/rebuild-native-deps.test.mjs',
|
|
'config/scripts/rebuild-native-deps-windows-process-tree.test.mjs',
|
|
'config/scripts/rebuild-native-deps-node-pty.test.mjs',
|
|
'config/scripts/nsis-process-check.test.mjs',
|
|
'config/scripts/ensure-native-runtime-job-ownership.test.mjs',
|
|
'config/scripts/verify-packaged-node-pty-job-ownership.test.mjs',
|
|
'config/scripts/windows-pe-machine.test.mjs',
|
|
'config/scripts/script-module-dependencies.test.mjs',
|
|
'src/main/windows-registry-addon.test.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/shared/child-process/windows-cmd-shim-resolution.test.ts',
|
|
'src/shared/child-process/windows-cmd-shim-resolution.win32.test.ts',
|
|
'src/main/agent-hooks/windows-hook-payload-delivery.test.ts',
|
|
'src/main/jcode/hook-gate-script.test.ts',
|
|
'src/main/agent-hooks/windows-direct-cmd-hook-command.test.ts',
|
|
'src/main/agent-hooks/windows-cmd-hook-command-unicode.test.ts',
|
|
'src/main/agent-hooks/windows-batch-hook-launcher.test.ts',
|
|
'src/main/agent-hooks/windows-powershell-hook-launcher.test.ts',
|
|
'src/main/codex/windows-hook-command.test.ts',
|
|
'src/main/codex/windows-hook-upgrade.test.ts',
|
|
'src/main/codex/hook-service-managed-install.test.ts',
|
|
'src/main/windows/windows-pty-job.win32.test.ts',
|
|
'src/main/windows/windows-msys-job.win32.test.ts',
|
|
'src/main/providers/agent-foreground-process-git-bash.win32.test.ts',
|
|
'src/main/windows/windows-host-job.win32.test.ts',
|
|
'src/main/windows/windows-process-tree-command-line-patch.test.ts',
|
|
'src/main/windows/windows-process-table-native-addon.win32.test.ts',
|
|
'src/main/persistence/profile-state/profile-state-access-windows-native.win32.test.ts',
|
|
'src/main/windows-live-tree-kill.win32.test.ts',
|
|
'src/main/wsl/wsl-runner.test.ts',
|
|
'src/main/wsl/wsl-guest-environment.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/computer/desktop-script-runtime-host.win32.test.ts',
|
|
'src/main/cursor/hook-service.test.ts',
|
|
'src/main/orca-profiles/profile-index-store.test.ts',
|
|
'src/main/startup/windows-install-dir-acl-repair.win32.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',
|
|
'src/shared/secure-path-windows-acl.win32.test.ts',
|
|
'src/main/runtime/unreadable-secret-store-preservation.win32.test.ts',
|
|
'src/main/ipc/pty-codex-account-attribution.test.ts',
|
|
'src/main/ipc/pty-spawn-env-codex-resume-provenance.test.ts',
|
|
'src/main/ipc/preflight-provider-command-selection.test.ts',
|
|
'src/main/ipc/preflight-runnable-local-cli.test.ts',
|
|
'src/relay/windows-port-scan.win32.test.ts',
|
|
'src/main/ssh/ssh-relay-upload-stage-windows-identity.test.ts',
|
|
'src/main/ssh/remote-node-runtime-store-windows.test.ts'
|
|
]
|
|
|
|
const DESKTOP_IRRELEVANT_PREFIXES = [
|
|
'mobile/',
|
|
'cloud/',
|
|
'.github/workflows/cloud-',
|
|
'.github/workflows/mobile.yml',
|
|
'.github/workflows/mobile-ios-release.yml',
|
|
'.github/workflows/mobile-android-release.yml'
|
|
]
|
|
|
|
const STATIC_ANALYSIS_AUDIT_SCRIPTS = [
|
|
'audit:code-quality:native',
|
|
'audit:code-quality:type-aware',
|
|
'audit:anti-slop'
|
|
]
|
|
|
|
// Positional arguments of an oxlint invocation are the trees it lints. `--config` consumes the
|
|
// next token; every other flag here is valueless.
|
|
function oxlintScanRoots(command) {
|
|
const roots = []
|
|
for (const segment of command.split('&&')) {
|
|
const tokens = segment.trim().split(/\s+/).filter(Boolean)
|
|
if (tokens[0] !== 'oxlint') {
|
|
continue
|
|
}
|
|
for (let index = 1; index < tokens.length; index += 1) {
|
|
if (tokens[index] === '--config') {
|
|
index += 1
|
|
} else if (!tokens[index].startsWith('-')) {
|
|
roots.push(tokens[index])
|
|
}
|
|
}
|
|
}
|
|
return roots
|
|
}
|
|
|
|
// Why derived from the commands rather than listed here: `mobile/` is desktop-irrelevant for every
|
|
// other job, yet these audits lint it. A second, hand-maintained copy of "which trees the gate
|
|
// reads" is what let #20702 land violations no PR check ran, so read it off the argv instead.
|
|
function readStaticAnalysisScanRoots() {
|
|
const manifest = join(import.meta.dirname, '../../package.json')
|
|
const { scripts = {} } = JSON.parse(readFileSync(manifest, 'utf8'))
|
|
return [
|
|
...new Set(
|
|
STATIC_ANALYSIS_AUDIT_SCRIPTS.flatMap((name) => oxlintScanRoots(scripts[name] ?? ''))
|
|
)
|
|
]
|
|
}
|
|
|
|
export const STATIC_ANALYSIS_SCAN_ROOTS = readStaticAnalysisScanRoots()
|
|
|
|
const STATIC_ANALYSIS_SCAN_PREFIXES = STATIC_ANALYSIS_SCAN_ROOTS.map((root) => `${root}/`)
|
|
|
|
export function isDocsOnlyPath(file) {
|
|
if (DOCS_ONLY_FILES.has(file)) {
|
|
return true
|
|
}
|
|
if (DOCS_ONLY_PREFIXES.some((prefix) => file.startsWith(prefix))) {
|
|
return true
|
|
}
|
|
return /^README\.[^/]+\.md$/.test(file)
|
|
}
|
|
|
|
export function shouldRunPrChecks(changedFiles) {
|
|
// Why empty-run: a silent empty diff is more likely a detector bug than a
|
|
// genuine no-op PR, so fail closed and keep the expensive jobs.
|
|
if (changedFiles.length === 0) {
|
|
return true
|
|
}
|
|
return changedFiles.some((file) => !isDocsOnlyPath(file) && !isDesktopIrrelevantPath(file))
|
|
}
|
|
|
|
export function needsMobileDependencies(changedFiles) {
|
|
// Why: static analysis lints CHANGED files, mobile ones included, and its
|
|
// type-aware pass resolves types from mobile/node_modules. Mobile is a
|
|
// separate pnpm project, so without this the root-only install leaves every
|
|
// mobile type an `error` type and the gate reports phantom findings.
|
|
return changedFiles.length === 0 || changedFiles.some((file) => file.startsWith('mobile/'))
|
|
}
|
|
|
|
export function classifyPrJobs(changedFiles) {
|
|
const emptyDiff = changedFiles.length === 0
|
|
const shouldRun = shouldRunPrChecks(changedFiles)
|
|
const forceAll = emptyDiff || changedFiles.some(isGlobalForcePath)
|
|
const jobs = Object.fromEntries(
|
|
PR_CHECK_JOBS.map((job) => [
|
|
job,
|
|
shouldRun && (forceAll || ALWAYS_ON_CODE_JOBS.has(job) || jobDetector(job)(changedFiles))
|
|
])
|
|
)
|
|
// Why outside should_run: a mobile-only diff is desktop-irrelevant and skips every job above,
|
|
// but the repo-wide audits lint mobile/, and skipping them lands the violation on main, where
|
|
// it then fails this same gate on every later PR's merge ref.
|
|
jobs.static_analysis = jobs.static_analysis || changedFiles.some(isStaticAnalysisScannedPath)
|
|
// Why outside should_run, for the same reason: a mobile-only diff is desktop-irrelevant, and
|
|
// that is exactly the diff that changes the page this job builds. Gated on should_run it would
|
|
// skip on every PR that can break it and run on none.
|
|
jobs.mobile_web_app = jobs.mobile_web_app || changesMobileWebApp(changedFiles)
|
|
return {
|
|
should_run: shouldRun,
|
|
native_cache_changed: shouldRun && (emptyDiff || changedFiles.some(isNativeCacheInputPath)),
|
|
mobile_dependencies:
|
|
(shouldRun || jobs.static_analysis) && needsMobileDependencies(changedFiles),
|
|
...jobs
|
|
}
|
|
}
|
|
|
|
function jobDetector(job) {
|
|
switch (job) {
|
|
case 'git_compatibility':
|
|
return (files) => files.some((file) => matchesPrefix(file, GIT_COMPAT_PREFIXES))
|
|
case 'codex_index_heal_contract':
|
|
return (files) =>
|
|
files.some((file) => matchesPrefix(file, CODEX_INDEX_HEAL_CONTRACT_PREFIXES))
|
|
case 'xterm_patch_sync':
|
|
return (files) => files.some((file) => matchesPrefix(file, XTERM_PREFIXES))
|
|
case 'shell_contracts':
|
|
return (files) => files.some((file) => matchesPrefix(file, SHELL_PREFIXES))
|
|
case 'orcad_browser':
|
|
return (files) => files.some((file) => matchesPrefix(file, ORCAD_BROWSER_PREFIXES))
|
|
// Not redundant with the lift below the jobs map: without a case here the default detector
|
|
// returns true, which would run this job on every desktop-relevant PR.
|
|
case 'mobile_web_app':
|
|
return changesMobileWebApp
|
|
case 'cross-version-wire':
|
|
return (files) => files.some((file) => matchesPrefix(file, CROSS_VERSION_WIRE_PREFIXES))
|
|
case 'managed_hook_node18':
|
|
return (files) => files.some((file) => matchesPrefix(file, MANAGED_HOOK_PREFIXES))
|
|
case 'package':
|
|
return (files) => files.some(isLinuxPackagePath)
|
|
case 'package_windows':
|
|
return (files) => files.some(isWindowsPackagePath)
|
|
default:
|
|
return () => true
|
|
}
|
|
}
|
|
|
|
function isLinuxPackagePath(file) {
|
|
return LINUX_PACKAGE_TESTS.includes(file) || isProductBundlePath(file, LINUX_PACKAGE_PREFIXES)
|
|
}
|
|
|
|
function isWindowsPackagePath(file) {
|
|
return WINDOWS_PACKAGE_TESTS.includes(file) || isProductBundlePath(file, WINDOWS_PACKAGE_PREFIXES)
|
|
}
|
|
|
|
function isProductBundlePath(file, extraPrefixes) {
|
|
if (isTestFile(file)) {
|
|
return false
|
|
}
|
|
if (file.startsWith('src/')) {
|
|
return true
|
|
}
|
|
return matchesPrefix(file, extraPrefixes)
|
|
}
|
|
|
|
// This shared fixture is consumed only by unit suites and their placement rig.
|
|
export function isUnitTestSupportSource(file) {
|
|
return file === 'src/renderer/src/runtime/web-session-tabs-sync-test-harness.ts'
|
|
}
|
|
|
|
function isTestFile(file) {
|
|
return (
|
|
isUnitTestSupportSource(file) ||
|
|
/\.(?:test|spec)\.(?:js|cjs|mjs|ts|tsx)$/.test(file) ||
|
|
file.includes('/__tests__/')
|
|
)
|
|
}
|
|
|
|
function isDesktopIrrelevantPath(file) {
|
|
return matchesPrefix(file, DESKTOP_IRRELEVANT_PREFIXES)
|
|
}
|
|
|
|
function isStaticAnalysisScannedPath(file) {
|
|
// Fail closed: roots we failed to parse must keep the gate, not silently drop it.
|
|
return (
|
|
STATIC_ANALYSIS_SCAN_PREFIXES.length === 0 || matchesPrefix(file, STATIC_ANALYSIS_SCAN_PREFIXES)
|
|
)
|
|
}
|
|
|
|
function isNativeCacheInputPath(file) {
|
|
return NATIVE_CACHE_FILES.has(file) || matchesPrefix(file, NATIVE_CACHE_PREFIXES)
|
|
}
|
|
|
|
function isGlobalForcePath(file) {
|
|
return GLOBAL_FORCE_FILES.has(file) || matchesPrefix(file, GLOBAL_FORCE_PREFIXES)
|
|
}
|
|
|
|
function matchesPrefix(file, prefixes) {
|
|
return prefixes.some((prefix) => file === prefix || file.startsWith(prefix))
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
// Why streamed, not readFileSync(0): a single read of fd 0 throws EAGAIN once the writer
|
|
// outgrows the 64 KB pipe buffer, which a stale PR base.sha reaches easily.
|
|
let input = ''
|
|
process.stdin.setEncoding('utf8')
|
|
for await (const chunk of process.stdin) {
|
|
input += chunk
|
|
}
|
|
const files = input.split(/\r?\n/).filter(Boolean)
|
|
const classification = classifyPrJobs(files)
|
|
for (const [name, value] of Object.entries(classification)) {
|
|
process.stdout.write(`${name}=${value ? 'true' : 'false'}\n`)
|
|
}
|
|
}
|