mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* refactor(tooling): organize test runtime and maintenance owners * test(tooling): preserve relocated runtime fixture dependency edges * refactor(tooling): preserve xterm routing owner checks * ci(tooling): include routing owner in sparse checkout * test(tooling): preserve copied workflow dependency closures * fix(tooling): normalize hashed JSON assets in release parity * fix(tooling): distinguish asset aliases from literal references * fix(tooling): frame normalized asset reference payloads * fix(tooling): normalize decoded JSON asset references * refactor(tooling): organize build, CI and release owners * fix(tooling): align moved generator and workflow path contracts * fix(tooling): update generated plugin bundle owner banners * test(tooling): create relocated mobile verifier fixture directories * test(tooling): copy headless detector dependency closure * test(tooling): align native batch policy fixtures with owner path * fix(tooling): link moved reliability command to historical evidence * fix(tooling): preserve nested sandbox owners and canonical guide source * fix(tooling): target release version owners in checkout fixture
137 lines
4.3 KiB
JavaScript
137 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
// Removes per-worktree `pnpm dev` profiles (see dev-instance-isolation.mjs) whose worktree is gone.
|
|
import { lstat, readdir, readFile, readlink, realpath, rm } from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import {
|
|
getWorktreeDevProfilesRoot,
|
|
readWorktreeProfileMarker,
|
|
sameFilesystemPath,
|
|
WORKTREE_PROFILE_NAME_PATTERN
|
|
} from '../development/instance-isolation/dev-instance-isolation.mjs'
|
|
import { localRepositoryWorktrees } from './local-repository-worktrees.mjs'
|
|
import { parseReclaimArguments } from './reclaim-worktree-command.mjs'
|
|
import {
|
|
isProcessAlive,
|
|
readDaemonPids
|
|
} from '../development/remote-session-cleanup/remote-agent-session-process-cleanup.mjs'
|
|
|
|
async function exists(filePath) {
|
|
try {
|
|
await lstat(filePath)
|
|
return true
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
return false
|
|
}
|
|
throw error
|
|
}
|
|
}
|
|
|
|
async function canonical(filePath) {
|
|
try {
|
|
return await realpath(filePath)
|
|
} catch {
|
|
return path.resolve(filePath)
|
|
}
|
|
}
|
|
|
|
/** PIDs that may still hold the profile: the app (Chromium's lock), its runtime, and terminal daemons. */
|
|
async function profileOwnerPids(userDataPath) {
|
|
const pids = [...readDaemonPids(userDataPath)]
|
|
try {
|
|
// POSIX Chromium writes SingletonLock as a symlink to `<host>-<pid>`.
|
|
const match = /-(\d+)$/.exec(await readlink(path.join(userDataPath, 'SingletonLock')))
|
|
if (match) {
|
|
pids.push(Number(match[1]))
|
|
}
|
|
} catch {}
|
|
try {
|
|
const runtime = JSON.parse(await readFile(path.join(userDataPath, 'orca-runtime.json'), 'utf8'))
|
|
if (Number.isInteger(runtime?.pid)) {
|
|
pids.push(runtime.pid)
|
|
}
|
|
} catch {}
|
|
return pids
|
|
}
|
|
|
|
export async function reclaimDevWorktreeProfiles({
|
|
repoRoot = process.cwd(),
|
|
apply = false,
|
|
excludeWorktree,
|
|
profilesRoot = getWorktreeDevProfilesRoot(),
|
|
repository,
|
|
isAlive = isProcessAlive,
|
|
report = console.log
|
|
} = {}) {
|
|
const { commonDirectory, worktrees } = repository ?? (await localRepositoryWorktrees(repoRoot))
|
|
const liveWorktrees = await Promise.all(worktrees.map(canonical))
|
|
const result = { entries: 0, protected: 0 }
|
|
let names
|
|
try {
|
|
names = await readdir(profilesRoot)
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
return result
|
|
}
|
|
throw error
|
|
}
|
|
for (const name of names) {
|
|
const profileDir = path.join(profilesRoot, name)
|
|
if (!WORKTREE_PROFILE_NAME_PATTERN.test(name) || !(await lstat(profileDir)).isDirectory()) {
|
|
continue
|
|
}
|
|
const marker = readWorktreeProfileMarker(profileDir)
|
|
// Profiles from other repositories belong to their own repository's cleanup.
|
|
if (
|
|
!marker?.gitCommonDir ||
|
|
!sameFilesystemPath(await canonical(marker.gitCommonDir), commonDirectory)
|
|
) {
|
|
continue
|
|
}
|
|
if (
|
|
(excludeWorktree && sameFilesystemPath(marker.repoRoot, excludeWorktree)) ||
|
|
liveWorktrees.some((worktree) => sameFilesystemPath(worktree, marker.repoRoot)) ||
|
|
(await exists(marker.repoRoot))
|
|
) {
|
|
continue
|
|
}
|
|
if ((await profileOwnerPids(profileDir)).some((pid) => isAlive(pid))) {
|
|
result.protected += 1
|
|
report(`retained dev profile ${JSON.stringify(profileDir)} (a process still uses it)`)
|
|
continue
|
|
}
|
|
if (apply) {
|
|
try {
|
|
await rm(profileDir, { recursive: true, force: true })
|
|
} catch (error) {
|
|
// Windows keeps a running profile's lock files open, so removal fails instead of racing.
|
|
result.protected += 1
|
|
report(`retained dev profile ${JSON.stringify(profileDir)} (${error.message})`)
|
|
continue
|
|
}
|
|
}
|
|
result.entries += 1
|
|
report(
|
|
`${apply ? 'removed' : 'would remove'} dev profile ${JSON.stringify(profileDir)} for removed worktree ${JSON.stringify(marker.repoRoot)}`
|
|
)
|
|
}
|
|
report(
|
|
`${apply ? 'Removed' : 'Would remove'} ${result.entries} dev profile(s) of removed worktrees; retained ${result.protected} in use.`
|
|
)
|
|
return result
|
|
}
|
|
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename)) {
|
|
const options = parseReclaimArguments(process.argv.slice(2))
|
|
if (options.help) {
|
|
console.log(
|
|
'Usage: node config/scripts/worktree-storage/reclaim-dev-worktree-profiles.mjs [--apply] [--repo DIRECTORY]'
|
|
)
|
|
} else {
|
|
reclaimDevWorktreeProfiles(options).catch((error) => {
|
|
console.error(error.message)
|
|
process.exitCode = 1
|
|
})
|
|
}
|
|
}
|