mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 16:02:55 +00:00
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as a re-export barrel so the import sites did not have to change. This removes the barrel: every consumer now imports from the module that actually declares the type, and `src/shared/types.ts` is deleted. Barrels hide where a type lives, make every consumer look like it depends on the whole domain, and let an unrelated edit invalidate a module that ~2,000 files transitively import. 2,323 import declarations across 2,321 files. Rewritten mechanically: each specifier was resolved to an absolute path via the TypeScript AST and recomputed, rather than string-substituted, so alias forms (`@/../../shared/ types`) and per-specifier `type` modifiers survive. Four cases the mechanical pass had to handle, each found by a gate rather than by reading the diff: - Modules inside `src/shared` import the barrel as `./types`, not `shared/types`. A pre-filter on the latter string skipped 176 of them and left imports dangling at a deleted file, which surfaced as confusing `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>` errors rather than "module not found". - The barrel RENAMED one type on the way through (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name in the owning module has to be re-aliased at each consumer. - Three test files put `;(globalThis as ...)` on the line after the import. TypeScript parses that `;` as the import statement's terminator, so replacing through `statement.getEnd()` deletes it and breaks ASI. The rewrite now stops at the module specifier. - A file that already imported directly from a module got a SECOND import from it, because the barrel re-exported those same names — which trips `import/no-duplicates` under `--deny-warnings`. A post-pass merges declarations sharing a specifier and type-only-ness; the `import type` plus `import` pair from one module is left alone, since that form is allowed. Splitting one barrel import into several genuinely adds lines, which pushed `terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character import must wrap, and neither local type collapses onto one line (101 and 116 characters). Rather than contort a type declaration to fit a line budget, `collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` — they are pure structural operations on the layout tree and independent of PTY ownership. `visible-worktrees.ts` similarly loses its own mini-barrel re-export of `isDefaultBranchWorkspace`, with the four real consumers repointed at the declaring module. No `max-lines` bypass added. Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted first — these projects are `composite: true` and reuse stale caches); the full `pnpm lint` green, not just bare oxlint — the narrower local check is what let the duplicate imports reach CI; max-lines ratchet OK at 344.
168 lines
4.6 KiB
TypeScript
168 lines
4.6 KiB
TypeScript
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type * as NodeProcess from 'node:process'
|
|
import type { Repo } from '../shared/repo-types'
|
|
import type { Store } from './persistence'
|
|
|
|
const { execFileMock, listRepoWorktreesMock } = vi.hoisted(() => ({
|
|
execFileMock: vi.fn(),
|
|
listRepoWorktreesMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('node:child_process', () => ({
|
|
execFile: execFileMock
|
|
}))
|
|
|
|
vi.mock('node:process', async () => {
|
|
const actual = await vi.importActual<typeof NodeProcess>('node:process')
|
|
return { ...actual, platform: 'darwin' }
|
|
})
|
|
|
|
vi.mock('./repo-worktrees', () => ({
|
|
createFolderWorktree: (repo: Repo) => ({
|
|
path: repo.path,
|
|
head: '',
|
|
branch: '',
|
|
isBare: false,
|
|
isMainWorktree: true
|
|
}),
|
|
listRepoWorktrees: listRepoWorktreesMock
|
|
}))
|
|
|
|
vi.mock('./providers/ssh-filesystem-dispatch', () => ({
|
|
getSshFilesystemProvider: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./providers/ssh-git-dispatch', () => ({
|
|
getSshGitProvider: vi.fn()
|
|
}))
|
|
|
|
import { analyzeWorkspaceSpace } from './workspace-space-analysis'
|
|
|
|
function createStore(repos: Repo[]): Store {
|
|
return {
|
|
getRepos: () => repos,
|
|
getWorktreeMeta: () => undefined
|
|
} as unknown as Store
|
|
}
|
|
|
|
describe('analyzeWorkspaceSpace local du timeout', () => {
|
|
let tempDir: string | null = null
|
|
|
|
beforeEach(async () => {
|
|
tempDir = await mkdtemp(join(tmpdir(), 'orca-space-du-timeout-'))
|
|
execFileMock.mockReset()
|
|
listRepoWorktreesMock.mockReset()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
vi.useRealTimers()
|
|
if (tempDir) {
|
|
await rm(tempDir, { recursive: true, force: true })
|
|
tempDir = null
|
|
}
|
|
})
|
|
|
|
it('falls back when du never reports completion', async () => {
|
|
const repoPath = join(tempDir!, 'repo')
|
|
await mkdir(repoPath, { recursive: true })
|
|
await writeFile(join(repoPath, 'app.ts'), 'console.log("ok")\n')
|
|
|
|
const repo: Repo = {
|
|
id: 'repo-1',
|
|
path: repoPath,
|
|
displayName: 'orca',
|
|
badgeColor: '#000',
|
|
addedAt: 0
|
|
}
|
|
listRepoWorktreesMock.mockResolvedValue([
|
|
{
|
|
path: repoPath,
|
|
head: 'a',
|
|
branch: 'refs/heads/main',
|
|
isBare: false,
|
|
isMainWorktree: true
|
|
}
|
|
])
|
|
const killMock = vi.fn()
|
|
execFileMock.mockImplementation(() => ({ kill: killMock }))
|
|
|
|
vi.useFakeTimers()
|
|
let settled = false
|
|
const scanPromise = analyzeWorkspaceSpace(createStore([repo])).then((scan) => {
|
|
settled = true
|
|
return scan
|
|
})
|
|
|
|
await vi.waitFor(() =>
|
|
expect(execFileMock).toHaveBeenCalledWith(
|
|
'du',
|
|
['-k', '-d', '1', repoPath],
|
|
expect.any(Object),
|
|
expect.any(Function)
|
|
)
|
|
)
|
|
await vi.advanceTimersByTimeAsync(120_000)
|
|
|
|
await vi.waitFor(() => expect(settled).toBe(true))
|
|
await expect(scanPromise).resolves.toMatchObject({
|
|
scannedWorktreeCount: 1,
|
|
unavailableWorktreeCount: 0,
|
|
worktrees: [expect.objectContaining({ status: 'ok', path: repoPath })]
|
|
})
|
|
expect(killMock).toHaveBeenCalled()
|
|
})
|
|
|
|
it('runs one local du traversal at a time across repos', async () => {
|
|
const repoPaths = [join(tempDir!, 'repo-one'), join(tempDir!, 'repo-two')]
|
|
await Promise.all(repoPaths.map((repoPath) => mkdir(repoPath, { recursive: true })))
|
|
const repos: Repo[] = repoPaths.map((repoPath, index) => ({
|
|
id: `repo-${index}`,
|
|
path: repoPath,
|
|
displayName: `repo-${index}`,
|
|
badgeColor: '#000',
|
|
addedAt: 0
|
|
}))
|
|
listRepoWorktreesMock.mockImplementation(async (repo: Repo) => [
|
|
{
|
|
path: repo.path,
|
|
head: 'a',
|
|
branch: 'refs/heads/main',
|
|
isBare: false,
|
|
isMainWorktree: true
|
|
}
|
|
])
|
|
const completions: (() => void)[] = []
|
|
let active = 0
|
|
let peak = 0
|
|
execFileMock.mockImplementation(
|
|
(
|
|
_file: string,
|
|
args: string[],
|
|
_options: unknown,
|
|
callback: (error: Error | null, stdout: string) => void
|
|
) => {
|
|
const rootPath = args.at(-1)!
|
|
active += 1
|
|
peak = Math.max(peak, active)
|
|
completions.push(() => {
|
|
active -= 1
|
|
callback(null, `1\t${rootPath}\n`)
|
|
})
|
|
return { kill: vi.fn() }
|
|
}
|
|
)
|
|
|
|
const scan = analyzeWorkspaceSpace(createStore(repos))
|
|
await vi.waitFor(() => expect(execFileMock).toHaveBeenCalledTimes(1))
|
|
completions.shift()?.()
|
|
await vi.waitFor(() => expect(execFileMock).toHaveBeenCalledTimes(2))
|
|
completions.shift()?.()
|
|
|
|
await expect(scan).resolves.toMatchObject({ scannedWorktreeCount: 2 })
|
|
expect(peak).toBe(1)
|
|
})
|
|
})
|