Fix SSH setup hooks for remote worktrees (#2477)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
fd1cbdf3c5
commit
c8c7553ecf
|
|
@ -297,10 +297,11 @@ function hasLegacyLocalScripts(repo: Repo): boolean {
|
|||
)
|
||||
}
|
||||
|
||||
export function getEffectiveHooks(repo: Repo, worktreePath?: string): OrcaHooks | null {
|
||||
const hooksRoot = worktreePath ?? repo.path
|
||||
const yamlHooks = loadHooks(hooksRoot)
|
||||
const yamlFileExists = hasHooksFile(hooksRoot)
|
||||
export function getEffectiveHooksFromConfig(
|
||||
repo: Repo,
|
||||
yamlHooks: OrcaHooks | null,
|
||||
yamlFileExists: boolean
|
||||
): OrcaHooks | null {
|
||||
const localSetup = repo.hookSettings?.scripts.setup
|
||||
const localArchive = repo.hookSettings?.scripts.archive
|
||||
const rawPolicy = repo.hookSettings?.commandSourcePolicy
|
||||
|
|
@ -329,6 +330,11 @@ export function getEffectiveHooks(repo: Repo, worktreePath?: string): OrcaHooks
|
|||
}
|
||||
}
|
||||
|
||||
export function getEffectiveHooks(repo: Repo, worktreePath?: string): OrcaHooks | null {
|
||||
const hooksRoot = worktreePath ?? repo.path
|
||||
return getEffectiveHooksFromConfig(repo, loadHooks(hooksRoot), hasHooksFile(hooksRoot))
|
||||
}
|
||||
|
||||
export function getEffectiveSetupRunPolicy(repo: Repo): SetupRunPolicy {
|
||||
return repo.hookSettings?.setupRunPolicy ?? getDefaultRepoHookSettings().setupRunPolicy!
|
||||
}
|
||||
|
|
@ -399,7 +405,7 @@ function getGitPath(cwd: string, relativePath: string): string {
|
|||
}).trim()
|
||||
}
|
||||
|
||||
function buildWindowsRunnerScript(script: string): string {
|
||||
export function buildWindowsRunnerScript(script: string): string {
|
||||
const lines = script.replace(/\r?\n/g, '\n').split('\n')
|
||||
const runnerLines = ['@echo off', 'setlocal EnableExtensions']
|
||||
|
||||
|
|
@ -430,6 +436,14 @@ export function createSetupRunnerScript(
|
|||
return createWorktreeRunnerScript(repo, worktreePath, script, 'setup-runner')
|
||||
}
|
||||
|
||||
export function getSetupRunnerEnvVars(repo: Repo, worktreePath: string): Record<string, string> {
|
||||
return getSetupEnvVars(repo, worktreePath)
|
||||
}
|
||||
|
||||
export function buildPosixRunnerScript(script: string): string {
|
||||
return `#!/usr/bin/env bash\nset -e\n${script.replace(/\r\n/g, '\n')}\n`
|
||||
}
|
||||
|
||||
export function createIssueCommandRunnerScript(
|
||||
repo: Repo,
|
||||
worktreePath: string,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
// cohesive flow would split awkwardly.
|
||||
|
||||
import type { BrowserWindow } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { join, posix, win32 } from 'path'
|
||||
import { existsSync } from 'fs'
|
||||
import { randomUUID } from 'crypto'
|
||||
import type { Store } from '../persistence'
|
||||
|
|
@ -29,11 +29,22 @@ import { parseGitHubOwnerRepo } from '../github/gh-utils'
|
|||
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
|
||||
import type { RemoteFetchResult, RemoteTrackingBase } from '../runtime/orca-runtime'
|
||||
import { isWslPath, parseWslPath, getWslHome } from '../wsl'
|
||||
import { createSetupRunnerScript, getEffectiveHooks, shouldRunSetupForCreate } from '../hooks'
|
||||
import {
|
||||
buildPosixRunnerScript,
|
||||
buildWindowsRunnerScript,
|
||||
createSetupRunnerScript,
|
||||
getEffectiveHooks,
|
||||
getEffectiveHooksFromConfig,
|
||||
getSetupRunnerEnvVars,
|
||||
parseOrcaYaml,
|
||||
shouldRunSetupForCreate
|
||||
} from '../hooks'
|
||||
import { requireSshGitProvider } from '../providers/ssh-git-dispatch'
|
||||
import { getSshFilesystemProvider } from '../providers/ssh-filesystem-dispatch'
|
||||
import { getActiveMultiplexer } from './ssh'
|
||||
import type { SshGitProvider } from '../providers/ssh-git-provider'
|
||||
import { isTuiAgent } from '../../shared/tui-agent-config'
|
||||
import { isWindowsAbsolutePathLike } from '../../shared/cross-platform-path'
|
||||
import {
|
||||
sanitizeWorktreeName,
|
||||
sanitizeWorktreeDisplayName,
|
||||
|
|
@ -48,6 +59,8 @@ import { getRepoIdFromWorktreeId } from '../../shared/worktree-id'
|
|||
import { invalidateAuthorizedRootsCache } from './filesystem-auth'
|
||||
import { createWorktreeSymlinks } from './worktree-symlinks'
|
||||
import { normalizeSparseDirectories } from './sparse-checkout-directories'
|
||||
import { joinWorktreeRelativePath } from '../runtime/runtime-relative-paths'
|
||||
import type { IFilesystemProvider } from '../providers/types'
|
||||
|
||||
async function findRemoteForUrl(repoPath: string, remoteUrl: string): Promise<string | null> {
|
||||
const target = parseGitHubOwnerRepo(remoteUrl)
|
||||
|
|
@ -484,6 +497,48 @@ async function configureCreatedWorktreePushTargetSsh(
|
|||
return target
|
||||
}
|
||||
|
||||
async function readRemoteEffectiveHooks(
|
||||
repo: Repo,
|
||||
fsProvider: IFilesystemProvider,
|
||||
hooksRootPath: string
|
||||
): Promise<ReturnType<typeof getEffectiveHooksFromConfig>> {
|
||||
try {
|
||||
const result = await fsProvider.readFile(joinWorktreeRelativePath(hooksRootPath, 'orca.yaml'))
|
||||
const yamlHooks = result.isBinary ? null : parseOrcaYaml(result.content)
|
||||
return getEffectiveHooksFromConfig(repo, yamlHooks, true)
|
||||
} catch {
|
||||
return getEffectiveHooksFromConfig(repo, null, false)
|
||||
}
|
||||
}
|
||||
|
||||
async function createRemoteSetupRunnerScript(
|
||||
repo: Repo,
|
||||
worktreePath: string,
|
||||
script: string,
|
||||
gitProvider: SshGitProvider,
|
||||
fsProvider: IFilesystemProvider
|
||||
): Promise<CreateWorktreeResult['setup']> {
|
||||
const useWindowsFormat = isWindowsAbsolutePathLike(worktreePath)
|
||||
const runnerRelativePath = useWindowsFormat ? 'orca/setup-runner.cmd' : 'orca/setup-runner.sh'
|
||||
const { stdout } = await gitProvider.exec(
|
||||
['rev-parse', '--git-path', runnerRelativePath],
|
||||
worktreePath
|
||||
)
|
||||
const runnerScriptPath = stdout.trim()
|
||||
const runnerDir = useWindowsFormat
|
||||
? win32.dirname(runnerScriptPath)
|
||||
: posix.dirname(runnerScriptPath)
|
||||
await fsProvider.createDir(runnerDir)
|
||||
await fsProvider.writeFile(
|
||||
runnerScriptPath,
|
||||
useWindowsFormat ? buildWindowsRunnerScript(script) : buildPosixRunnerScript(script)
|
||||
)
|
||||
return {
|
||||
runnerScriptPath,
|
||||
envVars: getSetupRunnerEnvVars(repo, worktreePath)
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveRemoteTrackingBaseSsh(
|
||||
provider: SshGitProvider,
|
||||
repoPath: string,
|
||||
|
|
@ -642,6 +697,14 @@ export async function createRemoteWorktree(
|
|||
}
|
||||
}
|
||||
|
||||
const fsProvider = getSshFilesystemProvider(repo.connectionId!)
|
||||
if (fsProvider) {
|
||||
const primaryHooks = await readRemoteEffectiveHooks(repo, fsProvider, repo.path)
|
||||
if (primaryHooks?.scripts.setup) {
|
||||
shouldRunSetupForCreate(repo, args.setupDecision)
|
||||
}
|
||||
}
|
||||
|
||||
let preparedPushTarget: GitPushTarget | undefined
|
||||
if (args.pushTarget) {
|
||||
// Why: fork-PR SSH worktrees need the same contributor-remote setup as
|
||||
|
|
@ -759,8 +822,41 @@ export async function createRemoteWorktree(
|
|||
// local-only until that protocol work is in scope. Remote repos with
|
||||
// `symlinkPaths` configured have them silently ignored here.
|
||||
|
||||
let setup: CreateWorktreeResult['setup']
|
||||
if (fsProvider) {
|
||||
const hooks = await readRemoteEffectiveHooks(repo, fsProvider, created.path)
|
||||
const setupScript = hooks?.scripts.setup
|
||||
let shouldLaunchSetup = false
|
||||
if (setupScript) {
|
||||
try {
|
||||
shouldLaunchSetup = shouldRunSetupForCreate(repo, args.setupDecision)
|
||||
} catch (error) {
|
||||
// Why: the remote worktree already exists. If the created branch adds
|
||||
// a setup hook without a renderer decision, skip setup instead of
|
||||
// reporting successful git creation as failed.
|
||||
console.warn(`[hooks] setup hook skipped for ${created.path}:`, error)
|
||||
}
|
||||
}
|
||||
if (setupScript && shouldLaunchSetup) {
|
||||
try {
|
||||
setup = await createRemoteSetupRunnerScript(
|
||||
repo,
|
||||
created.path,
|
||||
setupScript,
|
||||
provider,
|
||||
fsProvider
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`[hooks] Failed to prepare setup runner for ${created.path}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
notifyWorktreesChanged(mainWindow, repo.id)
|
||||
return { worktree }
|
||||
return {
|
||||
worktree,
|
||||
...(setup ? { setup } : {})
|
||||
}
|
||||
}
|
||||
|
||||
export async function createLocalWorktree(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,12 @@ const {
|
|||
getEffectiveHooksMock,
|
||||
createIssueCommandRunnerScriptMock,
|
||||
createSetupRunnerScriptMock,
|
||||
getEffectiveHooksFromConfigMock,
|
||||
parseOrcaYamlMock,
|
||||
shouldRunSetupForCreateMock,
|
||||
buildPosixRunnerScriptMock,
|
||||
buildWindowsRunnerScriptMock,
|
||||
getSetupRunnerEnvVarsMock,
|
||||
runHookMock,
|
||||
hasHooksFileMock,
|
||||
loadHooksMock,
|
||||
|
|
@ -27,6 +32,7 @@ const {
|
|||
ensurePathWithinWorkspaceMock,
|
||||
gitExecFileAsyncMock,
|
||||
getSshGitProviderMock,
|
||||
getSshFilesystemProviderMock,
|
||||
getActiveMultiplexerMock
|
||||
} = vi.hoisted(() => ({
|
||||
handleMock: vi.fn(),
|
||||
|
|
@ -46,7 +52,12 @@ const {
|
|||
getEffectiveHooksMock: vi.fn(),
|
||||
createIssueCommandRunnerScriptMock: vi.fn(),
|
||||
createSetupRunnerScriptMock: vi.fn(),
|
||||
getEffectiveHooksFromConfigMock: vi.fn(),
|
||||
parseOrcaYamlMock: vi.fn(),
|
||||
shouldRunSetupForCreateMock: vi.fn(),
|
||||
buildPosixRunnerScriptMock: vi.fn(),
|
||||
buildWindowsRunnerScriptMock: vi.fn(),
|
||||
getSetupRunnerEnvVarsMock: vi.fn(),
|
||||
runHookMock: vi.fn(),
|
||||
hasHooksFileMock: vi.fn(),
|
||||
loadHooksMock: vi.fn(),
|
||||
|
|
@ -54,6 +65,7 @@ const {
|
|||
ensurePathWithinWorkspaceMock: vi.fn(),
|
||||
gitExecFileAsyncMock: vi.fn(),
|
||||
getSshGitProviderMock: vi.fn(),
|
||||
getSshFilesystemProviderMock: vi.fn(),
|
||||
getActiveMultiplexerMock: vi.fn()
|
||||
}))
|
||||
|
||||
|
|
@ -105,15 +117,24 @@ vi.mock('../providers/ssh-git-dispatch', () => ({
|
|||
}
|
||||
}))
|
||||
|
||||
vi.mock('../providers/ssh-filesystem-dispatch', () => ({
|
||||
getSshFilesystemProvider: getSshFilesystemProviderMock
|
||||
}))
|
||||
|
||||
vi.mock('./ssh', () => ({
|
||||
getActiveMultiplexer: getActiveMultiplexerMock
|
||||
}))
|
||||
|
||||
vi.mock('../hooks', () => ({
|
||||
buildPosixRunnerScript: buildPosixRunnerScriptMock,
|
||||
buildWindowsRunnerScript: buildWindowsRunnerScriptMock,
|
||||
createIssueCommandRunnerScript: createIssueCommandRunnerScriptMock,
|
||||
createSetupRunnerScript: createSetupRunnerScriptMock,
|
||||
getEffectiveHooks: getEffectiveHooksMock,
|
||||
getEffectiveHooksFromConfig: getEffectiveHooksFromConfigMock,
|
||||
getSetupRunnerEnvVars: getSetupRunnerEnvVarsMock,
|
||||
loadHooks: loadHooksMock,
|
||||
parseOrcaYaml: parseOrcaYamlMock,
|
||||
runHook: runHookMock,
|
||||
hasHooksFile: hasHooksFileMock,
|
||||
shouldRunSetupForCreate: shouldRunSetupForCreateMock
|
||||
|
|
@ -202,8 +223,13 @@ describe('registerWorktreeHandlers', () => {
|
|||
getWorkItemMock,
|
||||
getPullRequestPushTargetMock,
|
||||
getEffectiveHooksMock,
|
||||
getEffectiveHooksFromConfigMock,
|
||||
parseOrcaYamlMock,
|
||||
createIssueCommandRunnerScriptMock,
|
||||
createSetupRunnerScriptMock,
|
||||
buildPosixRunnerScriptMock,
|
||||
buildWindowsRunnerScriptMock,
|
||||
getSetupRunnerEnvVarsMock,
|
||||
shouldRunSetupForCreateMock,
|
||||
runHookMock,
|
||||
hasHooksFileMock,
|
||||
|
|
@ -212,6 +238,7 @@ describe('registerWorktreeHandlers', () => {
|
|||
ensurePathWithinWorkspaceMock,
|
||||
gitExecFileAsyncMock,
|
||||
getSshGitProviderMock,
|
||||
getSshFilesystemProviderMock,
|
||||
getActiveMultiplexerMock,
|
||||
mainWindow.webContents.send,
|
||||
store.getRepos,
|
||||
|
|
@ -277,7 +304,22 @@ describe('registerWorktreeHandlers', () => {
|
|||
// don't trip on undefined.
|
||||
gitExecFileAsyncMock.mockResolvedValue({ stdout: '', stderr: '' })
|
||||
getEffectiveHooksMock.mockReturnValue(null)
|
||||
getEffectiveHooksFromConfigMock.mockReturnValue(null)
|
||||
parseOrcaYamlMock.mockReturnValue(null)
|
||||
shouldRunSetupForCreateMock.mockReturnValue(false)
|
||||
buildPosixRunnerScriptMock.mockImplementation(
|
||||
(script: string) => `#!/usr/bin/env bash\nset -e\n${script.replace(/\r\n/g, '\n')}\n`
|
||||
)
|
||||
buildWindowsRunnerScriptMock.mockImplementation((script: string) => script)
|
||||
getSetupRunnerEnvVarsMock.mockImplementation(
|
||||
(repoArg: { path: string }, worktreePath: string) => ({
|
||||
ORCA_ROOT_PATH: repoArg.path,
|
||||
ORCA_WORKTREE_PATH: worktreePath,
|
||||
ORCA_WORKSPACE_NAME: worktreePath.split('/').at(-1) ?? '',
|
||||
CONDUCTOR_ROOT_PATH: repoArg.path,
|
||||
GHOSTX_ROOT_PATH: repoArg.path
|
||||
})
|
||||
)
|
||||
createSetupRunnerScriptMock.mockReturnValue({
|
||||
runnerScriptPath: '/workspace/repo/.git/orca/setup-runner.sh',
|
||||
envVars: {
|
||||
|
|
@ -874,6 +916,94 @@ describe('registerWorktreeHandlers', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('reads remote orca.yaml and returns a setup launch payload during SSH create', async () => {
|
||||
const repo = {
|
||||
id: 'repo-ssh',
|
||||
path: '/remote/repo',
|
||||
displayName: 'ssh',
|
||||
badgeColor: '#000',
|
||||
addedAt: 0,
|
||||
connectionId: 'conn-1',
|
||||
worktreeBaseRef: 'origin/main'
|
||||
}
|
||||
const provider = {
|
||||
exec: vi.fn().mockImplementation(async (args: string[]) => {
|
||||
if (args[0] === 'remote') {
|
||||
return { stdout: 'origin\n', stderr: '' }
|
||||
}
|
||||
if (args[0] === 'rev-parse') {
|
||||
return {
|
||||
stdout: '/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh\n',
|
||||
stderr: ''
|
||||
}
|
||||
}
|
||||
return { stdout: '', stderr: '' }
|
||||
}),
|
||||
addWorktree: vi.fn().mockResolvedValue(undefined),
|
||||
listWorktrees: vi.fn().mockResolvedValue([
|
||||
{
|
||||
path: '/remote/improve-dashboard',
|
||||
head: 'abc123',
|
||||
branch: 'refs/heads/improve-dashboard',
|
||||
isBare: false,
|
||||
isMainWorktree: false
|
||||
}
|
||||
])
|
||||
}
|
||||
const fsProvider = {
|
||||
readFile: vi.fn().mockResolvedValue({
|
||||
content: 'scripts:\n setup: pnpm install\n',
|
||||
isBinary: false
|
||||
}),
|
||||
createDir: vi.fn().mockResolvedValue(undefined),
|
||||
writeFile: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
const mux = {
|
||||
request: vi.fn().mockResolvedValue(undefined),
|
||||
notify: vi.fn()
|
||||
}
|
||||
store.getRepos.mockReturnValue([repo])
|
||||
store.getRepo.mockReturnValue(repo)
|
||||
getSshGitProviderMock.mockReturnValue(provider)
|
||||
getSshFilesystemProviderMock.mockReturnValue(fsProvider)
|
||||
getActiveMultiplexerMock.mockReturnValue(mux)
|
||||
store.setWorktreeMeta.mockImplementation((_worktreeId, meta) => meta)
|
||||
parseOrcaYamlMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
getEffectiveHooksFromConfigMock.mockReturnValue({ scripts: { setup: 'pnpm install' } })
|
||||
shouldRunSetupForCreateMock.mockReturnValue(true)
|
||||
|
||||
const result = await handlers['worktrees:create'](null, {
|
||||
repoId: 'repo-ssh',
|
||||
name: 'improve-dashboard',
|
||||
setupDecision: 'run'
|
||||
})
|
||||
|
||||
expect(fsProvider.readFile).toHaveBeenCalledWith('/remote/repo/orca.yaml')
|
||||
expect(fsProvider.readFile).toHaveBeenCalledWith('/remote/improve-dashboard/orca.yaml')
|
||||
expect(provider.exec).toHaveBeenCalledWith(
|
||||
['rev-parse', '--git-path', 'orca/setup-runner.sh'],
|
||||
'/remote/improve-dashboard'
|
||||
)
|
||||
expect(fsProvider.createDir).toHaveBeenCalledWith(
|
||||
'/remote/repo/.git/worktrees/improve-dashboard/orca'
|
||||
)
|
||||
expect(fsProvider.writeFile).toHaveBeenCalledWith(
|
||||
'/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh',
|
||||
'#!/usr/bin/env bash\nset -e\npnpm install\n'
|
||||
)
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
setup: {
|
||||
runnerScriptPath: '/remote/repo/.git/worktrees/improve-dashboard/orca/setup-runner.sh',
|
||||
envVars: expect.objectContaining({
|
||||
ORCA_ROOT_PATH: '/remote/repo',
|
||||
ORCA_WORKTREE_PATH: '/remote/improve-dashboard'
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('does not create an SSH worktree when remote-tracking base refresh fails', async () => {
|
||||
const repo = {
|
||||
id: 'repo-ssh',
|
||||
|
|
|
|||
|
|
@ -6268,6 +6268,7 @@ export class OrcaRuntimeService {
|
|||
workspaceStatus?: string
|
||||
sparseCheckout?: { directories: string[]; presetId?: string }
|
||||
pushTarget?: GitPushTarget
|
||||
runHooks?: boolean
|
||||
setupDecision?: 'run' | 'skip' | 'inherit'
|
||||
createdWithAgent?: TuiAgent
|
||||
startup?: WorktreeStartupLaunch
|
||||
|
|
@ -6292,7 +6293,8 @@ export class OrcaRuntimeService {
|
|||
...(args.displayName ? { displayName: args.displayName } : {}),
|
||||
...(args.baseBranch ? { baseBranch: args.baseBranch } : {}),
|
||||
...(args.branchNameOverride ? { branchNameOverride: args.branchNameOverride } : {}),
|
||||
...(args.setupDecision ? { setupDecision: args.setupDecision } : {}),
|
||||
...(args.runHooks ? { setupDecision: 'run' as const } : {}),
|
||||
...(!args.runHooks && args.setupDecision ? { setupDecision: args.setupDecision } : {}),
|
||||
...(args.sparseCheckout ? { sparseCheckout: args.sparseCheckout } : {}),
|
||||
...(args.linkedIssue != null ? { linkedIssue: args.linkedIssue } : {}),
|
||||
...(args.linkedPR != null ? { linkedPR: args.linkedPR } : {}),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ describe('buildSetupRunnerCommand', () => {
|
|||
buildSetupRunnerCommand(
|
||||
'\\\\wsl.localhost\\Ubuntu\\home\\jin\\repo\\.git\\worktrees\\feature\\orca\\setup-runner.sh'
|
||||
)
|
||||
).toBe("bash /home/jin/repo/.git/worktrees/feature/orca/setup-runner.sh")
|
||||
).toBe('bash /home/jin/repo/.git/worktrees/feature/orca/setup-runner.sh')
|
||||
})
|
||||
|
||||
it('uses cmd.exe for native Windows runner scripts', () => {
|
||||
|
|
@ -28,4 +28,14 @@ describe('buildSetupRunnerCommand', () => {
|
|||
'cmd.exe /c "C:\\repo\\.git\\orca\\setup-runner.cmd"'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses bash for POSIX runner paths on Windows clients', () => {
|
||||
vi.stubGlobal('navigator', {
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
|
||||
})
|
||||
|
||||
expect(buildSetupRunnerCommand('/home/dev/repo/.git/orca/setup-runner.sh')).toBe(
|
||||
'bash /home/dev/repo/.git/orca/setup-runner.sh'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ export function buildSetupRunnerCommand(
|
|||
platform: SetupRunnerCommandPlatform
|
||||
): string {
|
||||
if (platform === 'windows') {
|
||||
if (runnerScriptPath.startsWith('/')) {
|
||||
return `bash ${quotePosixArg(runnerScriptPath)}`
|
||||
}
|
||||
if (isWslUncPath(runnerScriptPath)) {
|
||||
const linuxPath = wslUncToLinuxPath(runnerScriptPath)
|
||||
return `bash ${quotePosixArg(linuxPath)}`
|
||||
|
|
|
|||
Loading…
Reference in New Issue