mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
* fix(ssh): expand %d and other tokens in ssh -G IdentityFile paths OpenSSH prints IdentityFile values from `ssh -G` with their percent tokens unexpanded (it expands them only at connect time), so a config line like `IdentityFile %d/keys/work.pem` reached Orca as a literal relative path. The key was silently skipped and the connection fell back to a password prompt even though plain `ssh <alias>` worked. Expand the tokens OpenSSH supports for IdentityFile that can be derived locally or from the ssh -G output (%%, %d, %u, %i, %l, %L, %h, %n, %p, %r). Paths with tokens that need OpenSSH internals (%C, %j, %k) are left unchanged. A configured, non-default identity file that does not exist is now named in the auth error instead of being skipped silently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ssh): reproduce #33849 end to end and keep tests off the real ~/.ssh Adds a Docker e2e: a host from ~/.ssh/config whose IdentityFile uses %r and %p connects with that key. It fails on v1.4.241 and passes with the token expansion. Drops a unit assertion that read the real ~/.ssh/id_xmss. * fix(ssh): expand ${NAME} in ssh -G IdentityFile paths too ssh -G also prints IdentityFile's ${NAME} environment references unexpanded, so a key at ${KEYS}/work.pem fell back to a password prompt in the same way as #33849's %d. Expand the braced form first, and keep the raw path when the variable is unset, as OpenSSH refuses it. --------- Co-authored-by: william <wanglv93@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
632 lines
32 KiB
JavaScript
632 lines
32 KiB
JavaScript
import process from 'node:process'
|
|
import { pathToFileURL } from 'node:url'
|
|
import {
|
|
DAEMON_INIT_TEST_SUPPORT,
|
|
isDaemonInitTestSupportChange,
|
|
isUnitTestSupportSource
|
|
} from './pr-code-change-scope.mjs'
|
|
import { MANAGED_SSH_E2E_SPEC_PREFIX } from './ci-e2e-job-selection.mjs'
|
|
import { LAYOUT_ORACLE_E2E_SPECS } from './pr-layout-oracle-route.mjs'
|
|
|
|
const isProductSource = (file) => !/\.test\.tsx?$/.test(file) && !isUnitTestSupportSource(file)
|
|
|
|
// The core xterm patch owns the helper textarea an input method attaches to.
|
|
const XTERM_CORE_PATCH =
|
|
/^config\/patches\/(?:@xterm__xterm@[^/]+\.patch|xterm-src\/@xterm__xterm@[^/]+\.src\.patch)$/
|
|
|
|
const NATIVE_IME_PRODUCT_SOURCE =
|
|
/^(?:src\/shared\/terminal-unicode-provider\.ts$|src\/renderer\/src\/lib\/pane-manager\/terminal-ime-|src\/renderer\/src\/components\/terminal-pane\/(?:terminal-ime-|terminal-ios-hangul-|xterm-bypass-policy))/
|
|
|
|
/** The harness itself: the session runner, the boundary probes, and the native specs. */
|
|
const NATIVE_IME_HARNESS =
|
|
/^(?:config\/scripts\/focus-nested-wayland-terminal\.sh$|config\/scripts\/(?:run-terminal-ibus-hangul-e2e|terminal-ime-engagement-receipt)\.mjs$|tests\/e2e\/terminal-ime-(?:boundary-probe|byte-reader|engagement-receipt)\.ts$|tests\/e2e\/terminal-(?:ibus-hangul|hangul-terminating-digit|macos-2set-korean)-native\.spec\.ts$)/
|
|
|
|
// This spec requires ORCA_SIDEBAR_INPUTS=main; ordinary PR builds can only skip it.
|
|
export const MANUAL_ONLY_E2E_SPECS = ['tests/e2e/ssh-orcad-phone-feed-list.spec.ts']
|
|
|
|
// Shared fixtures and workflow changes must exercise every managed-SSH consumer.
|
|
const MANAGED_SSH_HARNESS =
|
|
/^tests\/e2e\/helpers\/(?:orcad-managed-workspace|orcad-convert-(?:flow|host|teardown)|orcad-template-variant|orcad-upgrade-(?:profile|retained-state)|docker-ssh-host-target|reserve-local-port|ssh-remote-only-browser-fixture|windows-host-orcad-processes)\.ts$/
|
|
|
|
export const PR_E2E_SOURCE_ROUTES = [
|
|
{
|
|
id: 'ssh.orcad-editor-ownership',
|
|
specs: ['tests/e2e/ssh-orcad-editor-ownership.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/runtime\/(?:web-session-existing-tab-index|web-session-tabs-sync\/(?:mirrored-editor-file-identity|tab-builders|apply-preparation-browser|state-equality-files|terminal-surfaces))\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-markdown-conversion',
|
|
specs: ['tests/e2e/ssh-orcad-markdown-conversion.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/components\/editor\/(?:useMarkdownDocuments|restored-editor-workspace-runtime-owner|migrate-restored-editor-file-owner)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-markdown-link-refresh',
|
|
specs: ['tests/e2e/ssh-orcad-markdown-link-refresh.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/components\/editor\/(?:rich-markdown-doc-link|useRichMarkdownProgrammaticSync)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-markdown-live-documents',
|
|
specs: ['tests/e2e/ssh-orcad-markdown-live-documents.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/components\/editor\/(?:useMarkdownDocuments|use-markdown-document-watch-refresh|markdown-document-list-request)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-explorer-watch-recovery',
|
|
specs: ['tests/e2e/ssh-orcad-explorer-watch-recovery.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
(file === 'src/renderer/src/components/right-sidebar/useFileExplorerWatch.ts' ||
|
|
file === 'src/renderer/src/hooks/worktree-file-change-event.ts')
|
|
},
|
|
{
|
|
id: 'ssh.orcad-editor-watch-recovery',
|
|
specs: ['tests/e2e/ssh-orcad-editor-watch-recovery.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/hooks\/(?:useEditorExternalWatch|editor-runtime-file-watch)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-explorer-selected-host',
|
|
specs: ['tests/e2e/ssh-orcad-explorer-selected-host.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
(file === 'src/renderer/src/components/right-sidebar/useFileExplorerWatch.ts' ||
|
|
file === 'src/renderer/src/components/right-sidebar/file-explorer-operation-owner.ts' ||
|
|
file === 'src/renderer/src/components/right-sidebar/FileExplorer.tsx' ||
|
|
file === 'src/renderer/src/hooks/editor-external-watch-targets.ts' ||
|
|
file === 'src/renderer/src/components/right-sidebar/useFileExplorerTree.ts' ||
|
|
file === 'src/renderer/src/components/right-sidebar/use-file-explorer-tree-load-effects.ts')
|
|
},
|
|
{
|
|
// A paired client refuses project setup on its server's SSH host with a clear reason (#33124).
|
|
id: 'paired.ssh-host-project-setup',
|
|
specs: ['tests/e2e/headless-paired-ssh-host-project-setup.spec.ts'],
|
|
matches: (file) =>
|
|
file === 'tests/e2e/helpers/headless-paired-runtime-host.ts' ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:renderer\/src\/store\/projects\/project-host-setup-actions|main\/runtime\/runtime-project-host-setup-controller|shared\/project-host-setup-target)\.ts$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'serve.orcad-mode-switch',
|
|
specs: ['tests/e2e/orcad-serve-mode-switch.spec.ts'],
|
|
matches: (file) =>
|
|
/^tests\/e2e\/helpers\/(?:orca-serve-cli-host|headless-paired-runtime-host)\.ts$/.test(
|
|
file
|
|
) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:cli\/runtime\/(?:launch|serve-)|main\/orcad\/(?:main|orcad-entry|orcad-instance-lock|orcad-command-arguments|orcad-lifecycle)\.ts$|main\/startup\/desktop-profile-instance-lock\.ts$|main\/daemon\/daemon-(?:spawner|endpoint-adoption|init)|main\/server\/serve-)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'startup.windows-missing-appdata',
|
|
specs: ['tests/e2e/windows-missing-appdata-startup.spec.ts'],
|
|
matches: (file) =>
|
|
file === 'tests/e2e/helpers/orca-serve-cli-host.ts' ||
|
|
(isProductSource(file) &&
|
|
/^src\/main\/startup\/(?:windows-app-data-path|main-process-preflight)\.ts$/.test(file))
|
|
},
|
|
{
|
|
id: 'clipboard.windows-copy-file',
|
|
specs: ['tests/e2e/file-explorer-copy-file-windows.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/(?:main\/window\/clipboard-(?:file-copy|ipc-handlers)|renderer\/src\/components\/right-sidebar\/file-explorer-row-file-transfer)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-auto-convert',
|
|
specs: ['tests/e2e/ssh-orcad-auto-convert.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/main\/(?:ipc\/(?:ssh\/)?ssh-host-server-|(?:ssh|managed-server\/[a-z-]+)\/(?:ssh-host-server-|orcad-runtime-conversion|orcad-migration-|orcad-retained-source|orcad-runtime-deployment))/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-ssh-failure-reason',
|
|
specs: ['tests/e2e/ssh-orcad-ssh-failure-reason.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/(?:ipc\/ssh\/(?:ssh-connect-flow|ssh-host-server-)|ssh\/(?:ssh-session-start-failure|ssh-host-server-|connection\/ssh-connection-manager))|renderer\/src\/ssh\/(?:ssh-host-server-status-copy|ssh-connect-failure-toast))/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-identity-file-tokens',
|
|
specs: ['tests/e2e/ssh-orcad-identity-file-tokens.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
/^tests\/e2e\/helpers\/ssh-config-host-picker\.ts$/.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/main\/ssh\/(?:config\/(?:ssh-g-config-resolution|ssh-identity-file-tokens|ssh-config-path-expansion)|ssh-auth-resolution|ssh-identity-file-read)\.ts$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-update-anyway',
|
|
specs: ['tests/e2e/ssh-orcad-update-anyway.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/(?:ssh\/orcad-(?:update-plan|runtime-maintenance|installed-activation|remote-deploy)|managed-server\/lifecycle\/orcad-(?:managed-update-|managed-auto-update|runtime-status))|renderer\/src\/components\/settings\/(?:managed-server-|use-managed-server-lifecycle))/.test(
|
|
file
|
|
))
|
|
},
|
|
// COMPAT(legacy-relay-bridge): revisit 2027-01-15 with src/main/ssh/legacy-relay/.
|
|
{
|
|
id: 'ssh.orcad-legacy-relay-bridge',
|
|
specs: [
|
|
'tests/e2e/ssh-orcad-legacy-relay-bridge.spec.ts',
|
|
'tests/e2e/ssh-orcad-relay-tab-only-shell.spec.ts'
|
|
],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
/^tests\/e2e\/(?:helpers\/(?:legacy-relay-docker-host|build-released-relay-bundle)|cross-version-wire\/released-relay-(?:daemon|transport))\.m?ts$/.test(
|
|
file
|
|
) ||
|
|
(isProductSource(file) &&
|
|
/(?:legacy-relay(?:-bridge|\/)|orcad-migration-terminal-gate|ssh-host-relay-terminals-on-connect|orcad-source-dependency-census)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-browser-capabilities',
|
|
specs: ['tests/e2e/ssh-orcad-browser-capabilities.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/main\/(?:runtime\/(?:runtime-browser-commands-factory|orca-runtime-get-status|browser\/browser-client-page-opening)|host\/electron-browser-commands|orcad\/orcad-browser-)/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'browser.orcad-service-status',
|
|
specs: ['tests/e2e/ssh-orcad-browser-service-status.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/components\/browser-pane\/stream-remote\/remote-browser-stream-(?:errors|status|lifecycle|restart-attempt)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-browser-routing',
|
|
specs: ['tests/e2e/ssh-orcad-browser-routing.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/(?:main\/browser\/local-ssh-browser|main\/ipc\/browser\.ts$|renderer\/src\/(?:lib\/(?:ssh-workspace-browser-route-eligibility|worktree-host-connection-phase)|components\/browser-pane\/use-ssh-workspace-browser-route|store\/repos\/converted-ssh-browser-pages|store\/slices\/browser\/browser-tab-actions|hooks\/ipc-events\/ssh-managed-server-state-effects))/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-terminal-browser-link',
|
|
specs: ['tests/e2e/ssh-orcad-terminal-browser-link.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/(?:lib\/(?:workspace-browser-tab-open|http-link-(?:destinations|routing)|ssh-workspace-browser-route-eligibility)|components\/terminal-pane\/terminal-http-link-source-owner)\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-desktop-browser-bridge',
|
|
specs: ['tests/e2e/ssh-orcad-desktop-browser-bridge.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/(?:shared\/desktop-browser-bridge-protocol\.ts$|main\/(?:runtime\/desktop-browser-bridge\/|browser\/desktop-browser-bridge\/|ipc\/desktop-browser-bridge-|runtime\/rpc\/methods\/desktop-browser-bridge\.ts$))/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'ssh.orcad-browser-drop-owner',
|
|
specs: ['tests/e2e/ssh-orcad-browser-drop-owner.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
/^tests\/e2e\/helpers\/browser-split-(?:guest-probes|page-server)\.ts$/.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/renderer\/src\/(?:lib\/(?:file-preview|workspace-file-drag|worktree-runtime-owner|connection-context)\.ts|components\/(?:right-sidebar\/FileExplorer(?:Row|VirtualRows)\.tsx|browser-pane\/(?:navigate\/use-browser-page-navigation-downloads\.ts|host-guest\/attach-browser-page-webview\.ts)))$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-open-in-owner',
|
|
specs: ['tests/e2e/ssh-orcad-open-in-owner.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/renderer\/src\/(?:lib\/(?:local-path-open-guard|external-editor-open-capability|worktree-runtime-owner)\.ts|components\/(?:sidebar\/(?:WorktreeOpenInMenu|WorktreeContextMenuView)\.tsx|right-sidebar\/(?:FileExplorer(?:Toolbar)?\.tsx|source-control\/listing\/entry-context-menu\.tsx)))$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-terminal-root-owner',
|
|
specs: ['tests/e2e/ssh-orcad-terminal-root-owner.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
file === 'tests/e2e/helpers/terminal-workspace-root-link.ts' ||
|
|
(isProductSource(file) &&
|
|
/^src\/renderer\/src\/(?:components\/terminal-pane\/terminal-(?:worktree-path-link|file-link-actions|file-link-hit-testing|file-open-routing|link-handlers)\.ts|lib\/(?:workspace-file-host-routing|worktree-owner-route|worktree-operation-route|worktree-operation-catalog-route)\.ts)$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-sleep-disconnected',
|
|
specs: ['tests/e2e/ssh-orcad-sleep-disconnected.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/runtime\/(?:workspaces\/(?:workspace-sleep-|remote-workspace-terminal-sleep)|terminal\/stop\/worktree-terminal-sleep)|renderer\/src\/components\/sidebar\/(?:sleep-|workspace-sleep-))/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'ssh.orcad-idle-exit',
|
|
specs: ['tests/e2e/ssh-orcad-idle-exit.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/(?:orcad\/orcad-(?:idle-|managed-idle-)|(?:ssh|managed-server\/[a-z-]+)\/orcad-(?:already-active-wake|runtime-deployment|managed-serving|managed-wake|managed-tunnel|recovery-slot|remote-launch|remote-runtime-control))|shared\/orcad-idle-exit)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// Managed SSH is the only SSH path; include Connect/Disconnect so journeys exercise reconnect (#30130).
|
|
id: 'ssh.orcad-journeys',
|
|
specs: [
|
|
'tests/e2e/ssh-orcad-terminal-journeys.spec.ts',
|
|
'tests/e2e/ssh-orcad-workspace-tools.spec.ts',
|
|
'tests/e2e/ssh-orcad-vm-provisioned-root.spec.ts'
|
|
],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
/^tests\/e2e\/helpers\/(?:paired-host-terminal|terminal-workspace-root-link)\.ts$/.test(
|
|
file
|
|
) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/(?:ssh|managed-server\/[a-z-]+)\/orcad-managed-|main\/ipc\/(?:ssh\/)?ssh-port-forward|main\/(?:ipc\/(?:(?:ssh\/)?ssh-(?:connect-flow|connection-handlers|host-user-disconnect|session-teardown)|(?:runtime-environments\/)?runtime-environment-(?:connectivity-handlers|manual-disconnect))|ssh\/(?:connection\/ssh-connection-manager|ssh-user-disconnect))\.ts$|renderer\/src\/startup\/(?:ssh-startup-reconnect|startup-ssh-connection-restore)\.ts$|renderer\/src\/runtime\/(?:web-runtime-session|web-session-close-|runtime-terminal-artifact-read\.ts$)|renderer\/src\/components\/terminal-pane\/(?:remote-runtime-|terminal-host-(?:workspace-file|file-link-existence)\.ts$))/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// A Claude chat a managed host's CLI refuses to start names why (#33768).
|
|
id: 'ssh.orcad-claude-chat-start-failure',
|
|
specs: ['tests/e2e/ssh-orcad-claude-chat-start-failure.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
file === 'tests/e2e/fixtures/structured-claude-stub.cjs' ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/(?:claude\/claude-(?:stream-json-connection|stream-json-exit-error|structured-session-exit-lifecycle)|native-chat\/agent-session-wire\/structured-agent-session-failure-text)|renderer\/src\/components\/native-chat\/structured-agent-session-delivery-notices)\.ts$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// A reconnect the host cannot image must still bring an idle full-screen program up to date (#26887).
|
|
id: 'ssh.orcad-reconnect-idle-frame',
|
|
specs: ['tests/e2e/ssh-orcad-terminal-reconnect-idle-frame.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:renderer\/src\/components\/terminal-pane\/(?:remote-runtime-|pty-connection\/(?:remote-program-redraw|hidden-output-restore-|park-reveal-snapshot-verdict))|main\/runtime\/rpc\/methods\/terminal\/terminal-(?:multiplex-initial-snapshot|snapshot-publication)\.ts$)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// Unsaved drafts stay on the desktop and are renamed to the server when a move commits.
|
|
id: 'ssh.orcad-editor-drafts-migration',
|
|
specs: ['tests/e2e/ssh-orcad-editor-drafts-migration.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/main\/(?:persistence\/migrating-orcad-catalog\/orcad-source-(?:draft-rename|workspace-session|session-copy-resolution|export)|ssh\/orcad-migration-source-retention|ipc\/ssh\/ssh)\.ts$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// A saved managed tunnel port that can no longer be bound moves to a free one (#33182).
|
|
id: 'ssh.orcad-unbindable-tunnel-port',
|
|
specs: ['tests/e2e/ssh-orcad-unbindable-tunnel-port.spec.ts'],
|
|
matches: (file) =>
|
|
MANAGED_SSH_HARNESS.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/managed-server\/lifecycle\/orcad-managed-tunnel|main\/ssh\/system-transport\/system-ssh-forward-port-reservation|shared\/runtime-environment-(?:sidecar|managed-orcad-store))/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// Why: hand-kept routes missed #30130 and #30120; scoped to managed-host code to bound lane cost.
|
|
id: 'ssh.orcad-remote-sources',
|
|
specs: [
|
|
'tests/e2e/ssh-orcad-paired-phone-projects.spec.ts',
|
|
'tests/e2e/ssh-orcad-pairing-repair.spec.ts',
|
|
'tests/e2e/ssh-orcad-retained-upgrade-state.spec.ts',
|
|
'tests/e2e/ssh-orcad-shared-home-machines.spec.ts',
|
|
'tests/e2e/ssh-orcad-shared-login-migration.spec.ts',
|
|
'tests/e2e/ssh-orcad-terminal-network-outage.spec.ts',
|
|
'tests/e2e/ssh-orcad-windows-cli-matrix.spec.ts'
|
|
],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/(?:main\/(?:ssh|orcad|managed-server)\/|main\/ipc\/(?:ssh|runtime-environment|orcad-)|cli\/runtime\/(?:launch-)?serve|shared\/(?:remote-runtime|orcad-|protocol-version\.ts$|runtime-capability)|renderer\/src\/store\/repos\/(?:repo-removal|runtime-project-terminal-stop)|renderer\/src\/components\/sidebar\/(?:sleep-|SleepTwin|WorkspaceSleep|use-worktree-sleep|ssh-host-remove|host-rename-remove|HostRemove)|renderer\/src\/components\/terminal-pane\/(?:TerminalRemoteRuntimeReconnectBanner|use-managed-pairing-refused|TerminalPaneRuntimePortals))/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'browser-network.ssh-docker-route',
|
|
specs: ['tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts'],
|
|
matches: (file) =>
|
|
file === 'tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts' ||
|
|
/^tests\/e2e\/helpers\/docker-ssh-host-(?:image|target)\.ts$/.test(file) ||
|
|
(isProductSource(file) &&
|
|
/^src\/main\/(?:browser\/(?:ssh-browser-network-execution-route|browser-network-deferred-socket|browser-network-execution-route|system-ssh-socks-client-socket)|ssh\/system-transport\/system-ssh-dynamic-forward-process)\.ts$/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
id: 'terminal.windows-wsl-launch-and-paste',
|
|
specs: [
|
|
'tests/e2e/golden-tab-bar-agent-launch.spec.ts',
|
|
'tests/e2e/terminal-windows-shell-paste-ownership.spec.ts'
|
|
],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:config\/scripts\/(?:verify-wsl-e2e-participation|verify-playwright-participation)\.mjs$|src\/main\/(?:wsl[/-]|pty\/.*wsl|providers\/wsl)|src\/shared\/(?:wsl-|windows-terminal-shell|shell-argument-quoting)|src\/renderer\/src\/.*(?:terminal-paste|pty-paste)|tests\/e2e\/(?:golden-tab-bar-agent-launch\.spec|terminal-windows-shell-paste-ownership\.spec|terminal-inline-images-windows\.spec|helpers\/(?:wsl-golden-stub-agent|golden-stub-agent))|\.github\/(?:actions\/setup-wsl-test-runtime\/|workflows\/windows-wsl-e2e\.yml))/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-input.ime-and-synthetic-forwarding',
|
|
specs: [
|
|
'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'
|
|
],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
(XTERM_CORE_PATCH.test(file) ||
|
|
/^src\/renderer\/src\/components\/terminal-pane\/(?:terminal-ime-|use-terminal-pane-lifecycle|xterm-bypass-policy|terminal-option-shortcut-policy)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// CDP Input.imeSetComposition proves synthetic handling without an IME; this separate route alone selects the real ibus-hangul lane.
|
|
id: 'terminal-ime.native-input-method',
|
|
specs: ['tests/e2e/terminal-ibus-hangul-native.spec.ts'],
|
|
matches: (file) =>
|
|
(isProductSource(file) &&
|
|
(XTERM_CORE_PATCH.test(file) || NATIVE_IME_PRODUCT_SOURCE.test(file))) ||
|
|
NATIVE_IME_HARNESS.test(file)
|
|
},
|
|
{
|
|
id: 'terminal-startup.quick-command-pre-bind-recovery',
|
|
specs: ['tests/e2e/terminal-quick-command-pre-bind-recovery.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/renderer\/src\/components\/tab-bar\/TabBarQuickCommandsMenu\.tsx|src\/renderer\/src\/hooks\/use-terminal-quick-command-hosts\.ts|src\/renderer\/src\/components\/terminal-pane\/(?:pty-connection|pty-transport|terminal-pty-pre-spawn-e2e-barrier)\.ts|src\/renderer\/src\/components\/terminal-pane\/pty-connection\/(?:connect-pane-pty|fresh-spawn-start|pane-pty-visibility-bind|pty-input-recovery)\.ts|src\/renderer\/src\/components\/terminal-pane\/(?:TerminalPane|use-terminal-pane-lifecycle)\.tsx?|src\/renderer\/src\/store\/slices\/terminals\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'quick-open.paired-host-path-search',
|
|
specs: ['tests/e2e/paired-quick-open-large-tree.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/main\/ipc\/filesystem-(?:list-files|search-file-paths)\.ts|src\/main\/ripgrep\/bundled-ripgrep-path\.ts|src\/main\/providers\/(?:filesystem-provider-contract|ssh-filesystem-provider(?:-capabilities)?)\.ts|src\/main\/runtime\/(?:orca-runtime-files|rpc\/methods\/files)\.ts|src\/renderer\/src\/(?:components\/(?:QuickOpen|quick-open-file-list|quick-open-search)\.tsx?|runtime\/(?:runtime-file-search-client|runtime-legacy-quick-open-inventory)\.ts)|src\/shared\/(?:quick-open-(?:install-rg|path-search|transport-budget)|ripgrep-process-availability|bundled-ripgrep)\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-session.host-cold-park-stream-continuity',
|
|
specs: ['tests/e2e/host-parked-pane-remote-viewer.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/renderer\/src\/components\/terminal-pane\/(?:terminal-hidden-view-parking|terminal-tab-park-candidates|terminal-tab-activation-order|terminal-parked-pty-watcher|terminal-parked-tab-watchers|terminal-parked-watcher-registry)\.ts|src\/renderer\/src\/runtime\/sync-runtime-graph\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
// Route unbind/retirement as well as binders: missed lifecycle edges can strand PTYs or leave blank mounted leaves.
|
|
// Omit runtime-rpc-client.ts: it makes no close decision and churns ~3x as often, triggering unrelated work.
|
|
id: 'terminal-pane.close-and-retirement',
|
|
specs: [
|
|
// Closing a tab whose pane is parked (never mounted) must retire that exact PTY.
|
|
'tests/e2e/terminal-parked-close-retirement.spec.ts',
|
|
// Split-leaf close must preserve agreement among root leaves, leaf-to-PTY bindings and live panes to prevent ghost blanks.
|
|
'tests/e2e/terminal-pane-close-layout-consistency.spec.ts',
|
|
// The runtime half: a leaf the host retires must stop being mounted on a paired client.
|
|
'tests/e2e/paired-remote-split-pane-host-retired-ghost.spec.ts'
|
|
],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/renderer\/src\/components\/terminal-pane\/(?:retire-unbound-(?:ipc|runtime)-terminal-pane|terminal-pane-(?:close-admission|close-identity|lifecycle-close|pane-closed|retirement-ownership)|use-terminal-pane-close-actions)|src\/renderer\/src\/store\/(?:terminals\/terminal-tab-close(?:-providers)?|slices\/(?:terminal-tab-retirement|terminal-retirement-teardown-reservation|retired-terminal-tab-state-sweep)))\.ts$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-session.parked-cli-split',
|
|
specs: ['tests/e2e/terminal-parked-cli-split.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/main\/window\/attach-main-window-services\.ts|src\/preload\/(?:index|api\/ui-command-event-api)\.ts|src\/renderer\/src\/components\/terminal-pane\/(?:terminal-pane-split-request-routing|use-terminal-pane-lifecycle|use-terminal-tab-cold-parking)\.ts|src\/renderer\/src\/hooks\/ipc-events\/terminal-ui-routing-ipc-bridge\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-session.paired-serve-restart-binding-continuity',
|
|
specs: ['tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/main\/daemon\/(?:daemon-attach-only-retirement|daemon-pty-applied-size|daemon-pty-session-control|daemon-pty-spawn-result)\.ts|src\/renderer\/src\/components\/terminal-pane\/(?:remote-runtime-pty-transport|terminal-error-accumulation)\.ts|src\/renderer\/src\/runtime\/(?:web-runtime-terminal-creation|web-runtime-session-tab-lifecycle|web-session-tabs-sync\/use-web-session-tabs-sync|web-session-terminal-orphan-(?:topology|recovery(?:-(?:adoption|surface|inventory|inventory-validation|cache|queue|rpc-lane|pane))?))\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-provider.ssh-remote-reattach-contract',
|
|
specs: ['tests/e2e/paired-remote-terminal-materialization-reconnect.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
!file.endsWith('-test-harness.ts') &&
|
|
/^(?:src\/renderer\/src\/components\/terminal-pane\/remote-runtime-pty-transport(?:-[a-z0-9-]+)?\.ts|src\/renderer\/src\/runtime\/remote-runtime-terminal-multiplexer\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
// Layout resolution can invent split directions; publishing the guess overwrites the original tree irreversibly.
|
|
id: 'terminal-session.split-orientation-resolution',
|
|
specs: ['tests/e2e/desktop-published-split-orientation-legacy-leaf.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/renderer\/src\/runtime\/(?:remote-terminal-layout-resolution\.ts|sync-runtime-graph\/(?:graph-publication|mobile-session-terminal-tabs|mobile-session-surfaces)\.ts|web-session-tabs-sync\/terminal-surfaces\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
id: 'terminal-session.remote-pane-layout-retry',
|
|
specs: ['tests/e2e/paired-remote-pane-layout-retry.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^(?:src\/renderer\/src\/components\/terminal-pane\/(?:remote-pane-layout-push|TerminalPane)\.tsx?|src\/renderer\/src\/lib\/terminal-layout-equality\.ts|src\/renderer\/src\/runtime\/web-session-tabs-sync\/use-web-session-tabs-sync\.ts|src\/renderer\/src\/store\/slices\/terminals\.ts)$/.test(
|
|
file
|
|
)
|
|
},
|
|
{
|
|
// Client-rendered host rows need two real Electron apps; omit high-churn ipc/runtime/useIpcEvents/preload seams covered by main-process integration.
|
|
id: 'client-hosted-browser.host-strip',
|
|
specs: ['tests/e2e/paired-client-hosted-browser-host-strip.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
/^src\/.*(?:[Cc]lient-?[Hh]osted-?[Bb]rowser|BrowserPaneOverlayLayer)/.test(file)
|
|
},
|
|
{
|
|
// Restart survival also depends on adoption, lease/reconciliation and tab snapshots; include orca-runtime.ts despite churn because it publishes the hold flag.
|
|
id: 'client-hosted-browser.restart-survival',
|
|
specs: ['tests/e2e/paired-client-hosted-browser-restart-survival.spec.ts'],
|
|
matches: (file) =>
|
|
isProductSource(file) &&
|
|
(file === 'src/main/runtime/browser/browser-commands.ts' ||
|
|
/^src\/.*(?:[Cc]lient-?[Hh]osted|browser-host-(?:lease|page|client-page)|browser-client-(?:host|page)|runtime-browser-(?:client-)?page|session-tabs-sync|host-session-snapshot-authority|orca-runtime\.ts|\/runtime-(?:status|types)\.ts)/.test(
|
|
file
|
|
))
|
|
},
|
|
{
|
|
// Connector code only: the shared launcher, writer and hook store it reuses already have their own routes.
|
|
id: 'connectors.headless-serve-conversation',
|
|
specs: ['tests/e2e/connector-headless-serve.spec.ts'],
|
|
matches: (file) =>
|
|
file === 'tests/e2e/helpers/connector-fake-claude.ts' ||
|
|
(isProductSource(file) &&
|
|
/^src\/(?:main\/external-conversations\/|main\/runtime\/rpc\/methods\/connector\.ts$|cli\/(?:handlers|specs)\/connector\.ts$|shared\/rpc-contract\/connector-params\.ts$)/.test(
|
|
file
|
|
))
|
|
}
|
|
]
|
|
|
|
export function selectPrE2eSpecs(
|
|
changedPaths,
|
|
reportRoute = () => undefined,
|
|
{ allowDaemonInitTestSupport = false } = {}
|
|
) {
|
|
const routePaths = isDaemonInitTestSupportChange(changedPaths, allowDaemonInitTestSupport)
|
|
? changedPaths.filter((file) => file !== DAEMON_INIT_TEST_SUPPORT)
|
|
: changedPaths
|
|
// The required layout_oracle job owns the oracle specs; running them here too would double the cost.
|
|
const specs = new Set(
|
|
changedPaths.filter(
|
|
(file) =>
|
|
/^tests\/e2e\/.*\.spec\.ts$/.test(file) &&
|
|
!LAYOUT_ORACLE_E2E_SPECS.includes(file) &&
|
|
!MANUAL_ONLY_E2E_SPECS.includes(file)
|
|
)
|
|
)
|
|
for (const route of PR_E2E_SOURCE_ROUTES) {
|
|
const matchedFiles = routePaths.filter(route.matches)
|
|
if (matchedFiles.length === 0) {
|
|
continue
|
|
}
|
|
route.specs.forEach((spec) => specs.add(spec))
|
|
reportRoute(`[pr-e2e] ${route.id}: ${route.specs.join(', ')}`)
|
|
}
|
|
if (
|
|
routePaths.some(
|
|
(file) =>
|
|
(file.startsWith('tests/e2e/helpers/') &&
|
|
[...specs].some((spec) => spec.startsWith(MANAGED_SSH_E2E_SPEC_PREFIX))) ||
|
|
file === '.github/workflows/e2e.yml' ||
|
|
file === '.github/scripts/prepare-ubuntu-apt.sh' ||
|
|
file === 'config/scripts/ci-e2e-job-selection.mjs'
|
|
)
|
|
) {
|
|
specs.add(MANAGED_SSH_E2E_SPEC_PREFIX)
|
|
}
|
|
return [...specs].sort((left, right) => left.localeCompare(right))
|
|
}
|
|
|
|
/** Routes whose authorities a real input method can judge, and so require the native IME lane. */
|
|
export const NATIVE_IME_SOURCE_ROUTE_IDS = ['terminal-ime.native-input-method']
|
|
|
|
// Derive native-IME triggers from source routes, independent of whether a route still lists the native spec.
|
|
export function hasNativeImeSourceChange(changedPaths) {
|
|
return PR_E2E_SOURCE_ROUTES.filter((route) =>
|
|
NATIVE_IME_SOURCE_ROUTE_IDS.includes(route.id)
|
|
).some((route) => changedPaths.some(route.matches))
|
|
}
|
|
|
|
export function shouldRunReusablePrE2e(changedPaths, options = {}) {
|
|
// Native IME has its own workflow.
|
|
return selectPrE2eSpecs(changedPaths, undefined, options).some(
|
|
(spec) => spec !== 'tests/e2e/terminal-ibus-hangul-native.spec.ts'
|
|
)
|
|
}
|
|
|
|
export function hasWslSourceChange(changedPaths) {
|
|
const route = PR_E2E_SOURCE_ROUTES.find(
|
|
(candidate) => candidate.id === 'terminal.windows-wsl-launch-and-paste'
|
|
)
|
|
return changedPaths.some(route.matches)
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
let input = ''
|
|
process.stdin.setEncoding('utf8')
|
|
for await (const chunk of process.stdin) {
|
|
input += chunk
|
|
}
|
|
const changedPaths = input.split(/\r?\n/).filter(Boolean)
|
|
const options = {
|
|
allowDaemonInitTestSupport: process.argv.includes('--daemon-init-test-support')
|
|
}
|
|
if (process.argv.includes('--reusable-workflow')) {
|
|
process.stdout.write(`${shouldRunReusablePrE2e(changedPaths, options)}\n`)
|
|
} else if (process.argv.includes('--wsl-source')) {
|
|
process.stdout.write(`${hasWslSourceChange(changedPaths)}\n`)
|
|
} else if (process.argv.includes('--native-ime-source')) {
|
|
process.stdout.write(`${hasNativeImeSourceChange(changedPaths)}\n`)
|
|
} else {
|
|
const specs = selectPrE2eSpecs(changedPaths, (message) => console.error(message), options)
|
|
process.stdout.write(`${JSON.stringify(specs)}\n`)
|
|
}
|
|
}
|