mirror of
https://github.com/stablyai/orca.git
synced 2026-10-25 16:03:53 +00:00
* 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>
462 lines
16 KiB
TypeScript
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()
|
|
})
|
|
})
|