mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
fix(wsl): retry the setup-runner .git link read and fall back to the distro's Git (#36792)
* fix(wsl): retry the setup-runner .git link read and fall back to the distro's Git A Windows stat over \\wsl.localhost can report a transient ENOENT. Retry the link read once, then ask the distro's Git asynchronously (fenced) before failing setup-script creation. * fix(wsl): map a mounted-drive Git answer to its Windows drive
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { gitExecFileAsyncMock, readWorktreeGitPathMock } = vi.hoisted(() => ({
|
||||
gitExecFileAsyncMock: vi.fn(),
|
||||
readWorktreeGitPathMock: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('./command-runner/git-exec-file', () => ({ gitExecFileAsync: gitExecFileAsyncMock }))
|
||||
vi.mock('./worktree-git-path', () => ({ readWorktreeGitPath: readWorktreeGitPathMock }))
|
||||
vi.mock('../wsl/wsl-automount-root', async (importOriginal) => ({
|
||||
...(await importOriginal<object>()),
|
||||
knownWslAutomountRoots: () => ['/mnt/']
|
||||
}))
|
||||
|
||||
import { resolveWslWorktreeGitPath } from './wsl-worktree-git-path'
|
||||
|
||||
const WORKTREE = '\\\\wsl.localhost\\Ubuntu\\home\\jin\\feature'
|
||||
const FROM_LINK =
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo\\.git\\worktrees\\feature\\orca\\setup-runner.sh'
|
||||
|
||||
describe('resolveWslWorktreeGitPath', () => {
|
||||
beforeEach(() => {
|
||||
gitExecFileAsyncMock.mockReset()
|
||||
readWorktreeGitPathMock.mockReset()
|
||||
})
|
||||
|
||||
it('uses the .git link without running Git', async () => {
|
||||
readWorktreeGitPathMock.mockReturnValue(FROM_LINK)
|
||||
|
||||
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
|
||||
FROM_LINK
|
||||
)
|
||||
expect(readWorktreeGitPathMock).toHaveBeenCalledWith(WORKTREE, 'orca/setup-runner.sh', [
|
||||
'/mnt/'
|
||||
])
|
||||
expect(gitExecFileAsyncMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('retries the link once after a transient miss', async () => {
|
||||
readWorktreeGitPathMock.mockReturnValueOnce(null).mockReturnValueOnce(FROM_LINK)
|
||||
|
||||
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
|
||||
FROM_LINK
|
||||
)
|
||||
expect(readWorktreeGitPathMock).toHaveBeenCalledTimes(2)
|
||||
expect(gitExecFileAsyncMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("asks the distro's Git when the link stays unreadable", async () => {
|
||||
readWorktreeGitPathMock.mockReturnValue(null)
|
||||
gitExecFileAsyncMock.mockResolvedValue({
|
||||
stdout: '/home/jin/repo/.git/worktrees/feature/orca/setup-runner.sh\n',
|
||||
stderr: ''
|
||||
})
|
||||
|
||||
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
|
||||
FROM_LINK
|
||||
)
|
||||
expect(readWorktreeGitPathMock).toHaveBeenCalledTimes(2)
|
||||
expect(gitExecFileAsyncMock).toHaveBeenCalledWith(
|
||||
['rev-parse', '--git-path', 'orca/setup-runner.sh'],
|
||||
{ cwd: WORKTREE }
|
||||
)
|
||||
})
|
||||
|
||||
it("maps Git's mounted-drive answer to the Windows drive", async () => {
|
||||
readWorktreeGitPathMock.mockReturnValue(null)
|
||||
gitExecFileAsyncMock.mockResolvedValue({
|
||||
stdout: '/mnt/c/src/repo/.git/worktrees/feature/orca/setup-runner.sh\n',
|
||||
stderr: ''
|
||||
})
|
||||
|
||||
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
|
||||
'C:\\src\\repo\\.git\\worktrees\\feature\\orca\\setup-runner.sh'
|
||||
)
|
||||
})
|
||||
|
||||
it("resolves Git's worktree-relative answer against the worktree", async () => {
|
||||
readWorktreeGitPathMock.mockReturnValue(null)
|
||||
gitExecFileAsyncMock.mockResolvedValue({ stdout: '.git/orca/setup-runner.sh\n', stderr: '' })
|
||||
|
||||
await expect(resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')).resolves.toBe(
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\feature\\.git\\orca\\setup-runner.sh'
|
||||
)
|
||||
})
|
||||
|
||||
it("names the worktree and keeps Git's error when both fail", async () => {
|
||||
readWorktreeGitPathMock.mockReturnValue(null)
|
||||
const gitError = new Error('fatal: not a git repository')
|
||||
gitExecFileAsyncMock.mockRejectedValue(gitError)
|
||||
|
||||
const failure = resolveWslWorktreeGitPath(WORKTREE, 'orca/setup-runner.sh')
|
||||
await expect(failure).rejects.toThrow(/couldn't find this WSL worktree's Git folder/)
|
||||
await expect(failure).rejects.toMatchObject({ cause: gitError })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
import { win32 } from 'node:path'
|
||||
import { parseWslUncPath, toWindowsWslUncPath } from '../../shared/wsl-paths'
|
||||
import { knownWslAutomountRoots, wslDrivePathUnderRoots } from '../wsl/wsl-automount-root'
|
||||
import { gitExecFileAsync } from './command-runner/git-exec-file'
|
||||
import { readWorktreeGitPath } from './worktree-git-path'
|
||||
|
||||
/**
|
||||
* A per-worktree Git path for a worktree inside a distro, as a path Windows can write. Reads the
|
||||
* `.git` link first; the distro's own Git answers when that read fails.
|
||||
*/
|
||||
export async function resolveWslWorktreeGitPath(
|
||||
worktreePath: string,
|
||||
relativePath: string
|
||||
): Promise<string> {
|
||||
const automountRoots = knownWslAutomountRoots()
|
||||
const read = (): string | null => readWorktreeGitPath(worktreePath, relativePath, automountRoots)
|
||||
// A Windows stat over \\wsl.localhost can report a transient ENOENT.
|
||||
const fromLink = read() ?? read()
|
||||
if (fromLink) {
|
||||
return fromLink
|
||||
}
|
||||
const distro = parseWslUncPath(worktreePath)?.distro
|
||||
try {
|
||||
// Async and fenced; the caller is a user act, so starting the distro is fine here.
|
||||
const { stdout } = await gitExecFileAsync(['rev-parse', '--git-path', relativePath], {
|
||||
cwd: worktreePath
|
||||
})
|
||||
const gitPath = stdout.trim()
|
||||
if (gitPath && distro) {
|
||||
// A git dir on a mounted drive maps to the drive itself, as the link read does.
|
||||
return gitPath.startsWith('/')
|
||||
? (wslDrivePathUnderRoots(gitPath, automountRoots) ?? toWindowsWslUncPath(gitPath, distro))
|
||||
: win32.join(worktreePath, gitPath)
|
||||
}
|
||||
} catch (error) {
|
||||
throw wslWorktreeGitPathError(worktreePath, error)
|
||||
}
|
||||
throw wslWorktreeGitPathError(worktreePath)
|
||||
}
|
||||
|
||||
function wslWorktreeGitPathError(worktreePath: string, cause?: unknown): Error {
|
||||
return new Error(
|
||||
`Orca couldn't find this WSL worktree's Git folder to write its setup script. Check that ${worktreePath} is a Git worktree.`,
|
||||
cause === undefined ? undefined : { cause }
|
||||
)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo\\feature\\',
|
||||
'pnpm install\npnpm build'
|
||||
@@ -105,7 +105,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
{ ...makeRepo(), path: 'C:\\Users\\jinwo\\git\\orca' },
|
||||
'C:\\repo\\feature',
|
||||
'#!/usr/bin/env bash\npnpm install',
|
||||
@@ -150,7 +150,7 @@ describe('createSetupRunnerScript', () => {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const { TERMINAL_GIT_CREDENTIAL_GUARD_POLICY_ENV } =
|
||||
await import('../shared/terminal-git-credential-guard')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo\\feature',
|
||||
'pnpm install',
|
||||
@@ -172,7 +172,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
{ ...makeRepo(), path: 'C:\\Users\\jinwo\\git\\orca' },
|
||||
'C:\\repo\\feature',
|
||||
'pnpm install'
|
||||
@@ -255,7 +255,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(makeRepo(), '/test/repo-feature', 'pnpm install')
|
||||
const result = await createSetupRunnerScript(makeRepo(), '/test/repo-feature', 'pnpm install')
|
||||
|
||||
expect(result.envVars).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -284,7 +284,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
{
|
||||
...makeRepo(),
|
||||
path: 'C:\\Users\\jinwo\\git\\orca'
|
||||
@@ -338,7 +338,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo\\feature',
|
||||
'pnpm install'
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo-worktree',
|
||||
'#!/usr/bin/env bash\r\npnpm install\r\nnpm run build',
|
||||
@@ -140,7 +140,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo-worktree',
|
||||
'copy .env.example .env\r\nxcopy /E assets dist',
|
||||
@@ -177,7 +177,7 @@ describe('createSetupRunnerScript', () => {
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const { buildSetupRunnerCommand } = await import('../shared/setup-runner-command')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo-worktree',
|
||||
'copy .env.example .env',
|
||||
@@ -207,7 +207,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
createSetupRunnerScript(
|
||||
await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo-worktree',
|
||||
'#!/usr/bin/env -S bash -euo pipefail\nmake build | tee build.log',
|
||||
@@ -236,7 +236,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
makeRepo(),
|
||||
'C:\\repo-worktree',
|
||||
'pnpm install\nnpm run build',
|
||||
@@ -277,7 +277,7 @@ describe('createSetupRunnerScript', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(makeRepo(), '/test/worktree', 'pnpm install')
|
||||
const result = await createSetupRunnerScript(makeRepo(), '/test/worktree', 'pnpm install')
|
||||
|
||||
expect(gitExecFileSyncMock).toHaveBeenCalledWith(
|
||||
['rev-parse', '--git-path', 'orca/setup-runner.sh'],
|
||||
@@ -301,34 +301,39 @@ describe('createSetupRunnerScript', () => {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
|
||||
expect(
|
||||
createSetupRunnerScript(makeRepo(), '/test/worktree', 'echo setup').waitForAgentStartup
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
createSetupRunnerScript(makeRepo('start-immediately'), '/test/worktree', 'echo setup')
|
||||
(await createSetupRunnerScript(makeRepo(), '/test/worktree', 'echo setup'))
|
||||
.waitForAgentStartup
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
createSetupRunnerScript(makeRepo('wait-for-setup'), '/test/worktree', 'echo setup')
|
||||
(await createSetupRunnerScript(makeRepo('start-immediately'), '/test/worktree', 'echo setup'))
|
||||
.waitForAgentStartup
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
(await createSetupRunnerScript(makeRepo('wait-for-setup'), '/test/worktree', 'echo setup'))
|
||||
.waitForAgentStartup
|
||||
).toBe(true)
|
||||
expect(
|
||||
createSetupRunnerScript(
|
||||
makeRepo('start-immediately'),
|
||||
'/test/worktree',
|
||||
'echo setup',
|
||||
undefined,
|
||||
undefined,
|
||||
'wait-for-setup'
|
||||
(
|
||||
await createSetupRunnerScript(
|
||||
makeRepo('start-immediately'),
|
||||
'/test/worktree',
|
||||
'echo setup',
|
||||
undefined,
|
||||
undefined,
|
||||
'wait-for-setup'
|
||||
)
|
||||
).waitForAgentStartup
|
||||
).toBe(true)
|
||||
expect(
|
||||
createSetupRunnerScript(
|
||||
makeRepo('wait-for-setup'),
|
||||
'/test/worktree',
|
||||
'echo setup',
|
||||
undefined,
|
||||
undefined,
|
||||
'start-immediately'
|
||||
(
|
||||
await createSetupRunnerScript(
|
||||
makeRepo('wait-for-setup'),
|
||||
'/test/worktree',
|
||||
'echo setup',
|
||||
undefined,
|
||||
undefined,
|
||||
'start-immediately'
|
||||
)
|
||||
).waitForAgentStartup
|
||||
).toBe(true)
|
||||
})
|
||||
@@ -338,7 +343,7 @@ describe('createSetupRunnerScript', () => {
|
||||
gitExecFileSyncMock.mockReturnValue('/test/repo/.git/orca/setup-runner.sh\n')
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
|
||||
const setup = createSetupRunnerScript(makeRepo(), '/test/worktree', 'git fetch')
|
||||
const setup = await createSetupRunnerScript(makeRepo(), '/test/worktree', 'git fetch')
|
||||
|
||||
expect(setup.envVars).toMatchObject({
|
||||
ORCA_ROOT_PATH: '/test/repo',
|
||||
|
||||
@@ -326,7 +326,7 @@ describe('runHook', () => {
|
||||
|
||||
try {
|
||||
const { createSetupRunnerScript } = await import('./worktree-runner-script')
|
||||
const result = createSetupRunnerScript(
|
||||
const result = await createSetupRunnerScript(
|
||||
{
|
||||
...makeRepo(),
|
||||
path: 'C:\\Users\\jinwo\\git\\orca'
|
||||
|
||||
@@ -1701,7 +1701,7 @@ async function performLocalWorktreeCreate(
|
||||
if (setupScript && shouldLaunchSetup) {
|
||||
try {
|
||||
// Main generates setup scripts without executing them; generation failure leaves the created workspace usable.
|
||||
setup = createSetupRunnerScript(
|
||||
setup = await createSetupRunnerScript(
|
||||
repo,
|
||||
worktreePath,
|
||||
setupScript,
|
||||
|
||||
@@ -148,7 +148,7 @@ describe('automation creates: main provisions setup and default tabs', () => {
|
||||
])(
|
||||
'runs a $platform runner with the $platform command',
|
||||
async ({ runnerScriptPath, platform }) => {
|
||||
createSetupRunnerScriptMock.mockReturnValueOnce({ runnerScriptPath, envVars: {} })
|
||||
createSetupRunnerScriptMock.mockResolvedValueOnce({ runnerScriptPath, envVars: {} })
|
||||
|
||||
await handlers['worktrees:create'](null, {
|
||||
repoId: 'repo-1',
|
||||
|
||||
@@ -758,7 +758,7 @@ describe('registerWorktreeHandlers', () => {
|
||||
getEffectiveHooksMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
getEffectiveHooksFromConfigMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
shouldRunSetupForCreateMock.mockReturnValue(true)
|
||||
createSetupRunnerScriptMock.mockReturnValueOnce({
|
||||
createSetupRunnerScriptMock.mockResolvedValueOnce({
|
||||
runnerScriptPath: 'C:\\workspace\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix', executable: 'wsl.exe' },
|
||||
envVars: {
|
||||
|
||||
@@ -253,7 +253,7 @@ export function setupWorktreeHandlers(): WorktreeRuntimeStub {
|
||||
GHOSTX_ROOT_PATH: repoArg.path
|
||||
})
|
||||
)
|
||||
createSetupRunnerScriptMock.mockReturnValue({
|
||||
createSetupRunnerScriptMock.mockResolvedValue({
|
||||
runnerScriptPath: '/workspace/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/workspace/repo',
|
||||
|
||||
@@ -63,7 +63,7 @@ async function createFromRendererPayload(options: {
|
||||
}
|
||||
])
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: RUNNER,
|
||||
envVars: SETUP_ENV
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix', executable: 'wsl.exe' },
|
||||
envVars: {
|
||||
@@ -167,7 +167,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -243,7 +243,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix', executable: 'wsl.exe' },
|
||||
envVars: {
|
||||
@@ -557,7 +557,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('OrcaRuntimeService', () => {
|
||||
ensurePathWithinWorkspaceMock.mockReturnValue('/tmp/workspaces/runtime-bg-split-setup')
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -146,7 +146,7 @@ describe('OrcaRuntimeService', () => {
|
||||
ensurePathWithinWorkspaceMock.mockReturnValue('/tmp/workspaces/runtime-active-split-setup')
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
|
||||
@@ -309,7 +309,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -441,7 +441,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\tmp\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix', executable: 'wsl.exe' },
|
||||
envVars: {
|
||||
|
||||
@@ -44,7 +44,7 @@ describe('OrcaRuntimeService', () => {
|
||||
setup: 'pnpm worktree:setup'
|
||||
}
|
||||
})
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -132,7 +132,7 @@ describe('OrcaRuntimeService', () => {
|
||||
setup: 'pnpm worktree:setup'
|
||||
}
|
||||
})
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -199,7 +199,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(resolveSetupRunnerShell).mockReturnValue({ family: 'posix' })
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix' },
|
||||
envVars: {
|
||||
@@ -282,7 +282,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
ORCA_ROOT_PATH: '/tmp/repo',
|
||||
@@ -396,7 +396,7 @@ describe('OrcaRuntimeService', () => {
|
||||
}
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix', executable: 'wsl.exe' },
|
||||
envVars: {
|
||||
@@ -474,7 +474,7 @@ describe('OrcaRuntimeService', () => {
|
||||
})
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(resolveSetupRunnerShell).mockReturnValue({ family: 'posix' })
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: 'C:\\repo\\.git\\orca\\setup-runner.sh',
|
||||
shell: { family: 'posix' },
|
||||
envVars: {
|
||||
|
||||
@@ -79,7 +79,7 @@ async function createActivated(options: {
|
||||
ensurePathWithinWorkspaceMock.mockReturnValue(WORKTREE_PATH)
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm worktree:setup' } })
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: RUNNER,
|
||||
envVars: { ORCA_ROOT_PATH: '/tmp/repo', ORCA_WORKTREE_PATH: WORKTREE_PATH }
|
||||
})
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('host worktree creation while its renderer is unavailable', () => {
|
||||
])
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'echo setup' } })
|
||||
vi.mocked(shouldRunSetupForCreate).mockReturnValue(true)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/tmp/setup-runner.sh',
|
||||
envVars: {}
|
||||
})
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function prepareRuntimeLocalWorktreeSetup(args: {
|
||||
if (shouldRunSetup && hooks?.scripts.setup) {
|
||||
if (args.shouldUseSetupRunner) {
|
||||
try {
|
||||
setup = createSetupRunnerScript(
|
||||
setup = await createSetupRunnerScript(
|
||||
repo,
|
||||
worktreePath,
|
||||
hooks.scripts.setup,
|
||||
|
||||
@@ -84,7 +84,7 @@ function chatCreateRuntime(args: {
|
||||
ensurePathWithinWorkspaceMock.mockReturnValue(path)
|
||||
vi.mocked(getEffectiveHooks).mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
vi.mocked(getDefaultTabsLaunch).mockReturnValue(args.withDefaults ? DEFAULTS : undefined)
|
||||
vi.mocked(createSetupRunnerScript).mockReturnValue({
|
||||
vi.mocked(createSetupRunnerScript).mockResolvedValue({
|
||||
runnerScriptPath: '/repo/setup.sh',
|
||||
envVars: {},
|
||||
...(args.wait ? { waitForAgentStartup: true } : {})
|
||||
|
||||
@@ -8,6 +8,7 @@ import { resolveWindowsGitBashShellPath } from './git-bash'
|
||||
import { gitExecFileSync } from './git/runner'
|
||||
import { isLocalGitRuntimeRouterInstalled } from './git/command-runner/local-git-runtime-route'
|
||||
import { readWorktreeGitPath } from './git/worktree-git-path'
|
||||
import { resolveWslWorktreeGitPath } from './git/wsl-worktree-git-path'
|
||||
import { knownWslAutomountRoots } from './wsl/wsl-automount-root'
|
||||
import { isWslPath, toWindowsWslPath, toLinuxPath } from './wsl'
|
||||
import { getHookRuntimeTarget, getHookWslContext } from './hook-runtime-target'
|
||||
@@ -30,14 +31,14 @@ function getGitPath(cwd: string, relativePath: string): string {
|
||||
}).trim()
|
||||
}
|
||||
|
||||
export function createSetupRunnerScript(
|
||||
export async function createSetupRunnerScript(
|
||||
repo: Repo,
|
||||
worktreePath: string,
|
||||
script: string,
|
||||
projectRuntime?: ProjectExecutionRuntimeResolution | HookRuntimeTarget,
|
||||
setupShell?: SetupRunnerShell,
|
||||
projectStartupPolicy?: SetupAgentStartupPolicy
|
||||
): WorktreeSetupLaunch {
|
||||
): Promise<WorktreeSetupLaunch> {
|
||||
return createWorktreeRunnerScript({
|
||||
repo,
|
||||
worktreePath,
|
||||
@@ -52,7 +53,7 @@ export function createSetupRunnerScript(
|
||||
})
|
||||
}
|
||||
|
||||
function createWorktreeRunnerScript(args: {
|
||||
async function createWorktreeRunnerScript(args: {
|
||||
repo: Repo
|
||||
worktreePath: string
|
||||
script: string
|
||||
@@ -60,7 +61,7 @@ function createWorktreeRunnerScript(args: {
|
||||
runtimeTarget?: HookRuntimeTarget
|
||||
waitForAgentStartup?: boolean
|
||||
setupShell?: SetupRunnerShell
|
||||
}): WorktreeSetupLaunch {
|
||||
}): Promise<WorktreeSetupLaunch> {
|
||||
const {
|
||||
repo,
|
||||
worktreePath,
|
||||
@@ -88,18 +89,13 @@ function createWorktreeRunnerScript(args: {
|
||||
// Linked worktrees have .git files; resolve the per-worktree gitdir through Git.
|
||||
const runnerExtension = runnerShell.family === 'cmd' ? 'cmd' : 'sh'
|
||||
const gitRelPath = `orca/${runnerBaseName}.${runnerExtension}`
|
||||
// Read the link with the D1c Git router (if WSL Git made the worktree, host Git can't), and always
|
||||
// for a worktree inside a distro: Git there would be a synchronous wsl.exe on the main thread.
|
||||
const inDistro = isWslPath(worktreePath)
|
||||
const fromLink =
|
||||
isLocalGitRuntimeRouterInstalled() || inDistro
|
||||
// Read the link with the D1c Git router (if WSL Git made the worktree, host Git can't). A worktree
|
||||
// inside a distro never runs synchronous Git: that would be a synchronous wsl.exe on the main thread.
|
||||
const fromLink = isWslPath(worktreePath)
|
||||
? await resolveWslWorktreeGitPath(worktreePath, gitRelPath)
|
||||
: isLocalGitRuntimeRouterInstalled()
|
||||
? readWorktreeGitPath(worktreePath, gitRelPath, knownWslAutomountRoots())
|
||||
: null
|
||||
if (!fromLink && inDistro) {
|
||||
throw new Error(
|
||||
`Orca couldn't find this WSL worktree's Git folder to write its setup script. Check that ${worktreePath} is a Git worktree.`
|
||||
)
|
||||
}
|
||||
let runnerScriptPath = fromLink ?? getGitPath(worktreePath, gitRelPath)
|
||||
|
||||
// Convert WSL Git's Linux path to a path Windows filesystem calls can reach.
|
||||
|
||||
Reference in New Issue
Block a user