Files
orca/config/scripts/dev-profile-seed.test.mjs
T
Neil fef00e4461 feat(dev): give each worktree its own pnpm dev profile and ports (#33416)
Linked worktrees now get a per-worktree userData profile under
orca-dev-wt/<hash>, a deterministic collision-checked renderer port
(15200+) and CDP port (9333+), so parallel `pnpm dev` runs no longer hit
the single-instance lock. The main checkout keeps the shared orca-dev
profile; --shared-profile / --worktree-profile / ORCA_DEV_PROFILE override.

The runner prints `ORCA_DEV_INSTANCE {json}` on stdout and mirrors it to
out/dev-instance.json so agents can attach over CDP. orca-dev.mjs and the
terminal wrappers follow the profile the worktree's dev app used.

Adds profile seeding (pnpm dev:seed-profile, pnpm dev --seed-profile,
pnpm dev:fresh --seed-from): an allowlisted slim copy of projects,
workspaces, settings, SSH targets, and the account session, written as
orca-data.json for Orca's own import; the source is only read.

Storage reclaim removes per-worktree profiles once their worktree is gone
and no app, runtime, or daemon still uses them. dev-fresh-profile.sh is
replaced by a cross-platform dev-fresh-profile.mjs.
2026-10-24 23:08:52 -07:00

104 lines
3.9 KiB
JavaScript

import { createHash } from 'node:crypto'
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { DatabaseSync } from 'node:sqlite'
import { tmpdir } from 'node:os'
import path from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { seedDevProfile } from './dev-profile-seed.mjs'
const roots = []
afterEach(() => {
for (const root of roots.splice(0)) {
rmSync(root, { recursive: true, force: true })
}
})
function hashTree(dir) {
const hash = createHash('sha256')
for (const entry of readdirSync(dir, { recursive: true, withFileTypes: true })) {
if (entry.isFile()) {
const file = path.join(entry.parentPath, entry.name)
hash.update(path.relative(dir, file)).update(readFileSync(file))
}
}
return hash.digest('hex')
}
function createSourceProfile() {
const root = mkdtempSync(path.join(tmpdir(), 'orca-seed-source-'))
roots.push(root)
const profileDir = path.join(root, 'profiles', 'local-default')
mkdirSync(profileDir, { recursive: true })
writeFileSync(
path.join(root, 'orca-profile-index.json'),
JSON.stringify({
schemaVersion: 1,
activeProfileId: 'local-default',
profiles: [{ id: 'local-default' }]
})
)
writeFileSync(path.join(profileDir, 'account-session.json.enc'), '{"ciphertext":"x"}')
writeFileSync(path.join(root, 'SingletonLock'), 'locked')
mkdirSync(path.join(root, 'Code Cache'))
writeFileSync(path.join(root, 'Code Cache', 'blob'), 'cache')
const db = new DatabaseSync(path.join(profileDir, 'profile-state.db'))
db.exec(
'CREATE TABLE profile_state_documents (domain TEXT PRIMARY KEY, payload TEXT NOT NULL, domain_version INTEGER, revision INTEGER, updated_at INTEGER, content_hash TEXT)'
)
const insert = db.prepare("INSERT INTO profile_state_documents VALUES (?, ?, 1, 1, 0, 'hash')")
insert.run('repos', JSON.stringify([{ id: 'repo-1', path: '/src/app' }]))
insert.run('settings', JSON.stringify({ theme: 'dark' }))
insert.run('sshTargets', JSON.stringify([{ id: 'ssh-1' }]))
insert.run('workspaceSession', JSON.stringify({ tabsByWorktree: { a: [] } }))
insert.run('sshRemotePtyLeases', JSON.stringify([{ ptyId: 'p' }]))
insert.run('automations', JSON.stringify([{ id: 'nightly' }]))
db.close()
return root
}
describe('seedDevProfile', () => {
it('writes an importable slim profile and never changes the source', async () => {
const source = createSourceProfile()
const before = hashTree(source)
const target = path.join(mkdtempSync(path.join(tmpdir(), 'orca-seed-target-')), 'profile')
roots.push(path.dirname(target))
await seedDevProfile({ sourceDir: source, targetDir: target, report: () => {} })
expect(hashTree(source)).toBe(before)
const seeded = JSON.parse(
readFileSync(path.join(target, 'profiles', 'local-default', 'orca-data.json'), 'utf8')
)
expect(seeded).toEqual({
repos: [{ id: 'repo-1', path: '/src/app' }],
settings: { theme: 'dark' },
sshTargets: [{ id: 'ssh-1' }]
})
expect(
readFileSync(
path.join(target, 'profiles', 'local-default', 'account-session.json.enc'),
'utf8'
)
).toBe('{"ciphertext":"x"}')
expect(readdirSync(target).sort()).toEqual([
'orca-dev-seed.json',
'orca-profile-index.json',
'profiles'
])
})
it('refuses to overwrite a profile that already has state', async () => {
const source = createSourceProfile()
const target = mkdtempSync(path.join(tmpdir(), 'orca-seed-target-'))
roots.push(target)
writeFileSync(path.join(target, 'orca-profile-index.json'), '{"existing":true}')
await expect(
seedDevProfile({ sourceDir: source, targetDir: target, report: () => {} })
).rejects.toThrow('already has state')
expect(readFileSync(path.join(target, 'orca-profile-index.json'), 'utf8')).toBe(
'{"existing":true}'
)
})
})