feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,135 @@
|
||||
import { promises as fs } from 'node:fs'
|
||||
import { isAbsolute, join, resolve } from 'node:path'
|
||||
import { configureSandbox } from '../src/main/sandbox/configuration'
|
||||
import { sandboxStatus, verifiedBinary } from '../src/main/sandbox/service'
|
||||
import { releaseRunLease, sandboxRecords, sandboxOperation, sandboxBrokerOperation } from '../src/main/sandbox/maintenance'
|
||||
import { restoreTransaction } from '../src/main/sandbox/transactions'
|
||||
import { readSandboxPolicy, effectiveSandboxPolicy, updateSandboxPolicy } from '../src/main/sandbox/policy-store'
|
||||
import { runSandboxSelfTest } from '../src/main/sandbox/self-test'
|
||||
import { importSandboxToolchain, listSandboxToolchains } from '../src/main/sandbox/toolchains'
|
||||
import { importSandboxDependencies } from '../src/main/sandbox/dependency-cache'
|
||||
import { sandboxGitState, resetSandboxGit } from '../src/main/sandbox/git-repository'
|
||||
import { workspaceAction } from '../src/main/sandbox/workspace-service'
|
||||
import type { SandboxWorkspaceAction } from '../src/shared/sandbox'
|
||||
import { gitHostAction } from '../src/main/sandbox/git-host-service'
|
||||
import { gitRemoteOperation, reconcileGitRemote } from '../src/main/sandbox/git-remote'
|
||||
import { sandboxGitTool } from '../src/main/agent/tools/sandbox-git'
|
||||
import { importGitCredential, listGitCredentials, removeGitCredential, type GitCredential } from '../src/main/sandbox/git-credentials'
|
||||
import { regularBytes } from '../src/main/sandbox/workspace-files'
|
||||
import { executeSandboxCommand } from '../src/main/sandbox/service'
|
||||
import { cleanupGitControls } from '../src/main/sandbox/git-control'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
function option(name: string): string | undefined {
|
||||
const index = args.indexOf(name)
|
||||
if (index < 0) return undefined
|
||||
const value = args[index + 1]
|
||||
if (!value || value.startsWith('--')) throw new Error(`Missing ${name} value`)
|
||||
args.splice(index, 2)
|
||||
return value
|
||||
}
|
||||
const stateRoot = option('--state-root'), resourceDirectory = option('--resources') ?? resolve('resources/sandbox/win32-x64')
|
||||
const workspace = option('--workspace')
|
||||
const expectedRevision = option('--expected-revision'), rulesFile = option('--rules')
|
||||
const source = option('--source')
|
||||
const sessionId = option('--session'), draftRevision = option('--draft-revision')
|
||||
const requestFile = option('--request'), credentialFile = option('--credential-file'), remoteUrl = option('--url')
|
||||
const credentialId = option('--credential-id')
|
||||
const approvedIndex = args.indexOf('--approve')
|
||||
const explicitlyApproved = approvedIndex >= 0
|
||||
if (explicitlyApproved) args.splice(approvedIndex, 1)
|
||||
const command = args.shift() ?? 'status'
|
||||
if (stateRoot && !isAbsolute(stateRoot)) throw new Error('--state-root must be an absolute directory')
|
||||
if (workspace && !isAbsolute(workspace)) throw new Error('--workspace must be an absolute directory')
|
||||
if (!['status', 'self-test'].includes(command) && !stateRoot) throw new Error('Stateful maintenance requires an explicit --state-root (the app userData/sandbox directory)')
|
||||
const temporary = !stateRoot ? await fs.mkdtemp(resolve('.tmp/sandbox-maintenance-')) : stateRoot
|
||||
configureSandbox({ stateRoot: temporary, resourceDirectory })
|
||||
if (command === 'status') console.log(JSON.stringify(await sandboxStatus(), null, 2))
|
||||
else if (command === 'toolchains') console.log(JSON.stringify(await listSandboxToolchains(), null, 2))
|
||||
else if (command.startsWith('git-host-')) {
|
||||
const action = command.slice('git-host-'.length)
|
||||
if (!workspace || !sessionId || !['status', 'init', 'import', 'changes', 'commit', 'discard', 'recover'].includes(action)) throw new Error('Usage: git-host-status|init|import|changes|commit|discard|recover --workspace ROOT --session ID --state-root ROOT [--approve]')
|
||||
console.log(JSON.stringify(await gitHostAction(action as 'status' | 'init' | 'import' | 'changes' | 'commit' | 'discard' | 'recover', { workspaceRoot: workspace, sessionId, approve: async () => explicitlyApproved }, draftRevision), null, 2))
|
||||
}
|
||||
else if (command === 'git-remote' || command === 'git-run' || command === 'git-remote-operation' || command === 'git-remote-reconcile') {
|
||||
if (!workspace || !sessionId) throw new Error('Git requires explicit --workspace and --session')
|
||||
const context = { workspaceRoot: workspace, sessionId, approve: async () => explicitlyApproved }
|
||||
if (command === 'git-remote-operation' || command === 'git-remote-reconcile') console.log(JSON.stringify(await (command === 'git-remote-reconcile' ? reconcileGitRemote(context, args.shift() ?? '', credentialId) : gitRemoteOperation(context, args.shift() ?? '')), null, 2))
|
||||
else {
|
||||
if (!requestFile || !isAbsolute(requestFile)) throw new Error('Git requires an absolute --request JSON file')
|
||||
const request = JSON.parse((await regularBytes(requestFile, 64 * 1024)).toString('utf8'))
|
||||
if (command === 'git-run') {
|
||||
if (!Array.isArray(request) || !request.length || request.some(value => typeof value !== 'string')) throw new Error('git-run request must be an argv array')
|
||||
console.log(JSON.stringify(await executeSandboxCommand(`git ${request.join(' ')}`, { ...context, runtimes: ['git'], workspaceMode: 'persistent', gitArguments: request, preserveGitConflicts: true }), null, 2))
|
||||
} else {
|
||||
if (!request || typeof request !== 'object' || Array.isArray(request) || !['ls-remote', 'clone', 'fetch', 'pull', 'push'].includes(request.action)) throw new Error('git-remote request requires a supported remote action')
|
||||
console.log(JSON.stringify(await sandboxGitTool.run(request, context), null, 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (command === 'git-credential-add') {
|
||||
if (!remoteUrl || !credentialFile || !isAbsolute(credentialFile)) throw new Error('Usage: git-credential-add --url REMOTE_URL --credential-file ABSOLUTE_JSON --state-root ROOT')
|
||||
const credential = JSON.parse((await regularBytes(credentialFile, 64 * 1024)).toString('utf8')) as GitCredential
|
||||
console.log(JSON.stringify(await importGitCredential(remoteUrl, credential), null, 2))
|
||||
}
|
||||
else if (command === 'git-credentials') console.log(JSON.stringify(await listGitCredentials(), null, 2))
|
||||
else if (command === 'git-cleanup') console.log(JSON.stringify(await cleanupGitControls(), null, 2))
|
||||
else if (command === 'git-credential-remove') { await removeGitCredential(args.shift() ?? ''); console.log(JSON.stringify({ removed: true })) }
|
||||
else if (command.startsWith('workspace-')) {
|
||||
const action = command.slice('workspace-'.length) as SandboxWorkspaceAction
|
||||
if (!['status', 'changes', 'commit', 'discard', 'recover'].includes(action) || !workspace || !sessionId) throw new Error('Usage: workspace-status|changes|commit|discard|recover --workspace ROOT --session ID --state-root ROOT [--draft-revision UUID] [--approve]')
|
||||
console.log(JSON.stringify(await workspaceAction(action, { workspaceRoot: workspace, sessionId, approve: async () => explicitlyApproved }, draftRevision), null, 2))
|
||||
}
|
||||
else if (command === 'toolchain-add') {
|
||||
const kind = args.shift()
|
||||
if (!kind || !source) throw new Error('Usage: toolchain-add node|python|git --source ABSOLUTE_EXE --state-root ROOT')
|
||||
console.log(JSON.stringify(await importSandboxToolchain(kind, source), null, 2))
|
||||
}
|
||||
else if (command === 'git-state' || command === 'git-reset') {
|
||||
if (!workspace) throw new Error('Usage: git-state|git-reset --workspace ABSOLUTE_ROOT --state-root ROOT')
|
||||
if (command === 'git-reset') await resetSandboxGit(workspace)
|
||||
console.log(JSON.stringify(await sandboxGitState(workspace), null, 2))
|
||||
}
|
||||
else if (command === 'dependency-add') {
|
||||
const kind = args.shift()
|
||||
if (!kind || !workspace || !isAbsolute(workspace)) throw new Error('Usage: dependency-add node|python --workspace ABSOLUTE_ROOT --state-root ROOT')
|
||||
console.log(JSON.stringify(await importSandboxDependencies(workspace, kind), null, 2))
|
||||
}
|
||||
else if (command === 'policy') console.log(JSON.stringify(workspace ? await effectiveSandboxPolicy(workspace) : await readSandboxPolicy(), null, 2))
|
||||
else if (command === 'operation') {
|
||||
const id = args.shift()
|
||||
if (!id) throw new Error('Usage: operation RUN_UUID --state-root ROOT')
|
||||
console.log(JSON.stringify(await sandboxOperation(id), null, 2))
|
||||
}
|
||||
else if (command === 'broker-operation') {
|
||||
const runId = args.shift(), requestId = args.shift()
|
||||
if (!runId || !requestId) throw new Error('Usage: broker-operation RUN_UUID REQUEST_UUID --state-root ROOT')
|
||||
console.log(JSON.stringify(await sandboxBrokerOperation(runId, requestId), null, 2))
|
||||
}
|
||||
else if (command === 'policy-set') {
|
||||
if (!expectedRevision || !rulesFile) throw new Error('Usage: policy-set --expected-revision REVISION --rules RULES_JSON --state-root ROOT [--workspace ROOT]')
|
||||
const stat = await fs.stat(rulesFile)
|
||||
if (!stat.isFile() || stat.size > 64 * 1024) throw new Error('Rules input must be a JSON file smaller than 64 KiB')
|
||||
const rules = JSON.parse(await fs.readFile(rulesFile, 'utf8'))
|
||||
console.log(JSON.stringify(await updateSandboxPolicy({ expectedRevision, rules, ...(workspace ? { workspaceRoot: workspace } : {}) }), null, 2))
|
||||
}
|
||||
else if (command === 'audit' || command === 'transactions') console.log(JSON.stringify(await sandboxRecords(command === 'audit' ? 'runs' : 'transactions'), null, 2))
|
||||
else if (command === 'restore') {
|
||||
const id = args.shift()
|
||||
if (!id || !workspace || !isAbsolute(workspace)) throw new Error('Usage: restore <transaction-id> --workspace <absolute-root> --state-root <absolute-state>')
|
||||
await restoreTransaction(id, workspace)
|
||||
console.log(JSON.stringify({ restored: id, workspace }))
|
||||
} else if (command === 'cleanup') {
|
||||
const binary = await verifiedBinary()
|
||||
const root = join(temporary, 'runs')
|
||||
const directories = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
|
||||
const results = []
|
||||
for (const entry of directories) {
|
||||
if (entry.isDirectory() && !entry.isSymbolicLink() && /^[0-9a-f-]{36}$/.test(entry.name)) results.push({ id: entry.name, status: await releaseRunLease(binary, join(root, entry.name)) })
|
||||
}
|
||||
console.log(JSON.stringify(results, null, 2))
|
||||
} else if (command === 'self-test') {
|
||||
const result = await runSandboxSelfTest()
|
||||
if (!result.passed) throw new Error('Native sandbox self-test failed')
|
||||
console.log(JSON.stringify({ ...result, stateRoot: temporary }))
|
||||
} else throw new Error('Commands: status, self-test, policy, policy-set, toolchains, toolchain-add, dependency-add, git-state, git-reset, operation, broker-operation, audit, transactions, restore, cleanup')
|
||||
Reference in new issue
Block a user