Files
orca/src/main/ipc/worktree-symlinks.test.ts
T
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00

462 lines
16 KiB
TypeScript

import {
mkdtempSync,
mkdirSync,
writeFileSync,
statSync,
lstatSync,
readlinkSync,
readFileSync,
rmSync,
symlinkSync,
existsSync,
chmodSync
} from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
createWorktreeLinkedPaths,
createWorktreeSymlinks,
removeWorktreeLinkedPaths,
removeWorktreeSymlinks
} from './worktree-symlinks'
type WorktreeLinkedPathOptionsForTest = NonNullable<Parameters<typeof createWorktreeLinkedPaths>[3]>
type ApfsCloneDepsForTest = NonNullable<WorktreeLinkedPathOptionsForTest['apfsCloneDeps']>
const posixIt = process.platform === 'win32' ? it.skip : it
function createApfsCloneDeps(options: {
uuid?: string
onCp?: (args: readonly string[]) => void
onDiskutil?: () => void
}): ApfsCloneDepsForTest {
const execFileAsync = vi.fn<ApfsCloneDepsForTest['execFileAsync']>(async (file, args) => {
if (file === '/bin/df') {
return {
stdout: `Filesystem 512-blocks Used Available Capacity Mounted on
/dev/disk3s1 100 50 50 50% /
`,
stderr: ''
}
}
if (file === '/usr/sbin/diskutil') {
options.onDiskutil?.()
return {
stdout: `<plist><dict><key>FilesystemName</key><string>APFS</string></dict></plist>`,
stderr: ''
}
}
if (file === '/bin/cp') {
options.onCp?.(args)
return { stdout: '', stderr: '' }
}
throw new Error(`Unexpected execFile command: ${file}`)
})
return {
execFileAsync,
randomUUID: () => options.uuid ?? 'test'
}
}
describe('createWorktreeSymlinks', () => {
let root: string
let primary: string
let worktree: string
let warn: ReturnType<typeof vi.spyOn>
let error: ReturnType<typeof vi.spyOn>
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'orca-symlinks-'))
primary = join(root, 'primary')
worktree = join(root, 'worktree')
mkdirSync(primary, { recursive: true })
mkdirSync(worktree, { recursive: true })
warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
error = vi.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
warn.mockRestore()
error.mockRestore()
rmSync(root, { recursive: true, force: true })
})
it('symlinks a file from primary into the worktree at the same relative path', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
await createWorktreeSymlinks(primary, worktree, ['.env'])
const linkStat = lstatSync(join(worktree, '.env'))
expect(linkStat.isSymbolicLink()).toBe(true)
expect(readlinkSync(join(worktree, '.env'))).toBe(join(primary, '.env'))
// Following the link yields the primary's contents.
expect(statSync(join(worktree, '.env')).isFile()).toBe(true)
})
it('symlinks a directory from primary into the worktree', async () => {
mkdirSync(join(primary, 'node_modules'))
writeFileSync(join(primary, 'node_modules', 'marker'), 'installed')
await createWorktreeSymlinks(primary, worktree, ['node_modules'])
expect(lstatSync(join(worktree, 'node_modules')).isSymbolicLink()).toBe(true)
expect(statSync(join(worktree, 'node_modules', 'marker')).isFile()).toBe(true)
})
it('creates parent directories lazily for nested paths', async () => {
mkdirSync(join(primary, 'apps', 'web'), { recursive: true })
writeFileSync(join(primary, 'apps', 'web', '.env'), 'X=1\n')
await createWorktreeSymlinks(primary, worktree, ['apps/web/.env'])
expect(lstatSync(join(worktree, 'apps', 'web', '.env')).isSymbolicLink()).toBe(true)
})
it('skips entries whose source is missing in the primary checkout', async () => {
await createWorktreeSymlinks(primary, worktree, ['node_modules'])
// No link created, no throw.
expect(() => lstatSync(join(worktree, 'node_modules'))).toThrow()
expect(error).not.toHaveBeenCalled()
})
it('preserves a pre-existing target in the worktree (no clobber)', async () => {
writeFileSync(join(primary, '.env'), 'FROM_PRIMARY=1\n')
writeFileSync(join(worktree, '.env'), 'FROM_WORKTREE=1\n')
await createWorktreeSymlinks(primary, worktree, ['.env'])
// The pre-existing regular file stays; no symlink was created.
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(false)
expect(statSync(join(worktree, '.env')).isFile()).toBe(true)
})
it('does not escape the primary checkout via a leading-slash path', async () => {
// Why: the helper strips leading slashes (so `/etc/passwd` becomes the
// relative `etc/passwd`). No file is created outside the worktree, and the
// resolved source — which falls inside `primary/etc/passwd` — is missing,
// so the entry is silently skipped rather than linking to `/etc/passwd`.
await createWorktreeSymlinks(primary, worktree, ['/etc/passwd'])
expect(() => lstatSync(join(worktree, 'etc', 'passwd'))).toThrow()
expect(error).not.toHaveBeenCalled()
})
it('rejects parent-directory traversal', async () => {
await createWorktreeSymlinks(primary, worktree, ['../secrets'])
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Skipping unsafe path "../secrets"')
)
})
it('rejects nested traversal via ..', async () => {
await createWorktreeSymlinks(primary, worktree, ['safe/../../escape'])
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Skipping unsafe path "safe/../../escape"')
)
})
it('rejects traversal using backslash separators (Windows form)', async () => {
// Why: users configuring paths on Windows (or pasting a mixed-separator
// value) could bypass a POSIX-only split. The guard normalizes across
// `/` and `\` so `..\escape` and `foo\..\..\escape` both get rejected.
await createWorktreeSymlinks(primary, worktree, ['..\\escape'])
await createWorktreeSymlinks(primary, worktree, ['foo\\..\\..\\escape'])
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Skipping unsafe path "..\\escape"')
)
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Skipping unsafe path "foo\\..\\..\\escape"')
)
})
it('strips a leading slash then treats the remainder as a relative path', async () => {
writeFileSync(join(primary, '.env'), 'X=1\n')
await createWorktreeSymlinks(primary, worktree, ['/.env'])
// Leading slash is stripped in the helper; the remaining `.env` is a valid relative path.
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(true)
expect(warn).not.toHaveBeenCalled()
})
it('skips empty and whitespace-only entries', async () => {
// The helper logs an "unsafe path" warn for these; nothing gets linked.
await createWorktreeSymlinks(primary, worktree, ['', ' '])
expect(error).not.toHaveBeenCalled()
})
it('continues processing later entries after one fails', async () => {
writeFileSync(join(primary, '.env'), 'X=1\n')
writeFileSync(join(primary, 'config.json'), '{}')
await createWorktreeSymlinks(primary, worktree, [
'../escape', // rejected
'missing-source', // no source, skipped
'.env', // succeeds
'config.json' // succeeds
])
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(true)
expect(lstatSync(join(worktree, 'config.json')).isSymbolicLink()).toBe(true)
})
it('is a no-op for an empty paths list', async () => {
await createWorktreeSymlinks(primary, worktree, [])
expect(warn).not.toHaveBeenCalled()
expect(error).not.toHaveBeenCalled()
})
it('uses APFS clone-copy for configured paths on macOS', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
const cloneWorktreePath = vi.fn(async (_source: string, target: string) => {
writeFileSync(target, 'SECRET=1\n')
})
await createWorktreeLinkedPaths(primary, worktree, ['.env'], {
platform: 'darwin',
cloneWorktreePath
})
expect(cloneWorktreePath).toHaveBeenCalledWith(
join(primary, '.env'),
join(worktree, '.env'),
false
)
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(false)
expect(statSync(join(worktree, '.env')).isFile()).toBe(true)
})
it('does not overwrite a file target that appears before APFS clone-copy is published', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
const target = join(worktree, '.env')
const deps = createApfsCloneDeps({
uuid: 'file-race',
onCp: (args) => {
const tempTarget = args.at(-1)
if (!tempTarget) {
throw new Error('Missing APFS clone temp target')
}
writeFileSync(tempTarget, 'SECRET=1\n')
writeFileSync(target, 'RACE=1\n')
}
})
await createWorktreeLinkedPaths(primary, worktree, ['.env'], {
platform: 'darwin',
apfsCloneDeps: deps
})
expect(readFileSync(target, 'utf8')).toBe('RACE=1\n')
expect(existsSync(join(worktree, '.orca-apfs-clone-file-race'))).toBe(false)
expect(warn).not.toHaveBeenCalled()
expect(error).not.toHaveBeenCalled()
})
it('does not replace a directory target that appears before APFS clone-copy reserves it', async () => {
mkdirSync(join(primary, 'node_modules'))
writeFileSync(join(primary, 'node_modules', 'primary-marker'), 'PRIMARY\n')
const target = join(worktree, 'node_modules')
let createdRacedTarget = false
const deps = createApfsCloneDeps({
onDiskutil: () => {
if (!createdRacedTarget) {
createdRacedTarget = true
mkdirSync(target)
writeFileSync(join(target, 'user-marker'), 'USER\n')
}
},
onCp: () => {
throw new Error('APFS clone-copy should not run after the target appears')
}
})
await createWorktreeLinkedPaths(primary, worktree, ['node_modules'], {
platform: 'darwin',
apfsCloneDeps: deps
})
expect(readFileSync(join(target, 'user-marker'), 'utf8')).toBe('USER\n')
expect(existsSync(join(target, 'primary-marker'))).toBe(false)
expect(deps.execFileAsync).not.toHaveBeenCalledWith('/bin/cp', expect.anything())
expect(warn).not.toHaveBeenCalled()
expect(error).not.toHaveBeenCalled()
})
it('does not overwrite or remove a nested target after APFS clone-copy hits a conflict', async () => {
const source = join(primary, 'node_modules')
mkdirSync(source)
writeFileSync(join(primary, 'node_modules', 'primary-marker'), 'PRIMARY\n')
const target = join(worktree, 'node_modules')
let cpArgs: readonly string[] | undefined
const deps = createApfsCloneDeps({
onCp: (args) => {
cpArgs = args
writeFileSync(join(target, 'primary-marker'), 'USER\n')
throw new Error('clone-copy skipped an existing nested target')
}
})
await createWorktreeLinkedPaths(primary, worktree, ['node_modules'], {
platform: 'darwin',
apfsCloneDeps: deps
})
expect(cpArgs).toEqual(['-n', '-c', '-R', source, worktree])
expect(readFileSync(join(target, 'primary-marker'), 'utf8')).toBe('USER\n')
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] APFS clone-copy unavailable'),
expect.any(Error)
)
expect(error).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Failed to link "node_modules"'),
expect.any(Error)
)
})
posixIt('preserves the source directory mode after APFS clone-copy reserves it', async () => {
const source = join(primary, 'node_modules')
mkdirSync(source)
chmodSync(source, 0o700)
const target = join(worktree, 'node_modules')
const deps = createApfsCloneDeps({
onCp: () => {
writeFileSync(join(target, 'marker'), 'CLONED\n')
}
})
await createWorktreeLinkedPaths(primary, worktree, ['node_modules'], {
platform: 'darwin',
apfsCloneDeps: deps
})
expect(statSync(target).mode & 0o777).toBe(0o700)
expect(readFileSync(join(target, 'marker'), 'utf8')).toBe('CLONED\n')
})
it('falls back to symlink when macOS clone-copy is unavailable', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
const cloneWorktreePath = vi.fn(async () => {
throw new Error('clonefile unsupported')
})
await createWorktreeLinkedPaths(primary, worktree, ['.env'], {
platform: 'darwin',
cloneWorktreePath
})
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(true)
expect(readlinkSync(join(worktree, '.env'))).toBe(join(primary, '.env'))
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] APFS clone-copy unavailable'),
expect.any(Error)
)
})
it('does not delete a target that appears while APFS clone-copy is failing', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
const cloneWorktreePath = vi.fn(async (_source: string, target: string) => {
writeFileSync(target, 'RACE=1\n')
throw new Error('clonefile failed after target appeared')
})
await createWorktreeLinkedPaths(primary, worktree, ['.env'], {
platform: 'darwin',
cloneWorktreePath
})
expect(readFileSync(join(worktree, '.env'), 'utf8')).toBe('RACE=1\n')
expect(error).toHaveBeenCalledWith(
expect.stringContaining('[worktree-symlinks] Failed to link ".env"'),
expect.any(Error)
)
})
it('keeps symlink sources as symlinks instead of APFS clone-copying their targets', async () => {
writeFileSync(join(primary, '.env.real'), 'SECRET=1\n')
symlinkSync(join(primary, '.env.real'), join(primary, '.env'), 'file')
const cloneWorktreePath = vi.fn(async () => {
throw new Error('clone should not be called for symlink sources')
})
await createWorktreeLinkedPaths(primary, worktree, ['.env'], {
platform: 'darwin',
cloneWorktreePath
})
expect(cloneWorktreePath).not.toHaveBeenCalled()
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(true)
expect(readlinkSync(join(worktree, '.env'))).toBe(join(primary, '.env'))
})
})
describe('removeWorktreeSymlinks', () => {
let root: string
let primary: string
let worktree: string
let error: ReturnType<typeof vi.spyOn>
beforeEach(() => {
root = mkdtempSync(join(tmpdir(), 'orca-unlink-'))
primary = join(root, 'primary')
worktree = join(root, 'worktree')
mkdirSync(primary, { recursive: true })
mkdirSync(worktree, { recursive: true })
error = vi.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
error.mockRestore()
rmSync(root, { recursive: true, force: true })
})
it('unlinks configured symlinks from the worktree', async () => {
writeFileSync(join(primary, '.env'), 'SECRET=1\n')
mkdirSync(join(primary, 'node_modules'))
symlinkSync(join(primary, '.env'), join(worktree, '.env'), 'file')
symlinkSync(join(primary, 'node_modules'), join(worktree, 'node_modules'), 'dir')
await removeWorktreeSymlinks(worktree, ['.env', 'node_modules'])
expect(existsSync(join(worktree, '.env'))).toBe(false)
expect(existsSync(join(worktree, 'node_modules'))).toBe(false)
// Source is untouched.
expect(statSync(join(primary, '.env')).isFile()).toBe(true)
expect(statSync(join(primary, 'node_modules')).isDirectory()).toBe(true)
})
it('leaves a regular file at the configured path alone', async () => {
// Why: a user who created a real file at `.env` (instead of symlinking)
// must not lose it just because `.env` is in the configured list.
writeFileSync(join(worktree, '.env'), 'USER_WROTE_THIS=1\n')
await removeWorktreeSymlinks(worktree, ['.env'])
expect(lstatSync(join(worktree, '.env')).isSymbolicLink()).toBe(false)
expect(statSync(join(worktree, '.env')).isFile()).toBe(true)
})
it('leaves APFS clone-copied regular files for git removal to judge', async () => {
writeFileSync(join(worktree, '.env'), 'CLONED=1\n')
await removeWorktreeLinkedPaths(worktree, ['.env'])
expect(existsSync(join(worktree, '.env'))).toBe(true)
})
it('ignores missing entries', async () => {
await removeWorktreeSymlinks(worktree, ['.env', 'node_modules'])
expect(error).not.toHaveBeenCalled()
})
it('rejects unsafe paths without touching the filesystem', async () => {
// Parent-dir traversal is silently skipped; no unlink attempted.
writeFileSync(join(root, 'outside-file'), 'DO_NOT_DELETE')
await removeWorktreeSymlinks(worktree, ['../outside-file'])
expect(existsSync(join(root, 'outside-file'))).toBe(true)
})
it('is a no-op for an empty paths list', async () => {
await removeWorktreeSymlinks(worktree, [])
expect(error).not.toHaveBeenCalled()
})
})