mirror of
https://github.com/stablyai/orca.git
synced 2026-10-28 16:02:55 +00:00
Merge remote-tracking branch 'origin/main' into brennanb2025/workspace-cleanup-dialog
# Conflicts: # src/main/ipc/workspace-cleanup-disconnected-ssh.ts # src/main/ipc/workspace-cleanup-scan.ts # src/preload/api-types.ts
This commit is contained in:
@@ -22,7 +22,7 @@ const BUDGETS = {
|
||||
maxTimerDriftMs: 150,
|
||||
// Why: mirrors MAX_TIMER_DRIFT_UNDER_LOAD_MS in artificial-opencode-terminal-load.spec.ts
|
||||
// so injected multi-pane redraw rows are not judged against the unloaded ceiling.
|
||||
maxTimerDriftUnderLoadMs: 2_500,
|
||||
maxTimerDriftUnderLoadMs: 3_500,
|
||||
maxScrollLatencyMs: 150,
|
||||
maxRestoreLatencyMs: 1000,
|
||||
maxRendererQueuedChars: 2 * 1024 * 1024,
|
||||
|
||||
@@ -132,14 +132,14 @@ describe('check-terminal-perf-report-budgets', () => {
|
||||
|
||||
it('fails multi-pane redraw scenarios that exceed the under-load timer-drift budget', () => {
|
||||
const failPath = writeReport(
|
||||
['panes=50', 'frames=60', 'median=12.0ms', 'worst=40.0ms', 'maxTimerDrift=2501.0ms'].join(
|
||||
['panes=50', 'frames=60', 'median=12.0ms', 'worst=40.0ms', 'maxTimerDrift=3501.0ms'].join(
|
||||
' '
|
||||
),
|
||||
'opencode-cross-workspace-typing'
|
||||
)
|
||||
const failResult = runChecker(failPath)
|
||||
expect(failResult.status).toBe(1)
|
||||
expect(failResult.stderr).toContain('timer drift 2501ms exceeded budget 2500ms')
|
||||
expect(failResult.stderr).toContain('timer drift 3501ms exceeded budget 3500ms')
|
||||
})
|
||||
|
||||
it('fails malformed metric values instead of treating them as absent', () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { BUNDLED_SKILL_GUIDES } from '../../src/cli/bundled-skill-guides'
|
||||
import {
|
||||
@@ -18,6 +20,7 @@ import {
|
||||
|
||||
const projectDir = path.resolve(import.meta.dirname, '..', '..')
|
||||
const temporaryDirectories = []
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
async function createFixture() {
|
||||
const root = await mkdtemp(path.join(tmpdir(), 'orca-bundled-skill-guides-'))
|
||||
@@ -92,6 +95,56 @@ describe('bundled skill guide generator', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('uses the exported recipe id variable in per-workspace environment examples', async () => {
|
||||
const source = await readFile(
|
||||
path.join(projectDir, 'skill-guides', 'orca-per-workspace-env.md'),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
expect(source).toContain('ORCA_RECIPE_ID')
|
||||
expect(source).not.toContain('ORCA_VM_RECIPE_ID')
|
||||
expect(source).toContain('recipe_id="${recipe_id//./-}"')
|
||||
expect(source).toContain('max_recipe_id_length=$((128 - ${#instance_id} - 6))')
|
||||
expect(source).toContain('name="orca-${recipe_id:0:max_recipe_id_length}-${instance_id}"')
|
||||
})
|
||||
|
||||
it.skipIf(process.platform === 'win32')(
|
||||
'keeps Vercel sandbox names valid while preserving the instance suffix',
|
||||
async () => {
|
||||
const source = await readFile(
|
||||
path.join(projectDir, 'skill-guides', 'orca-per-workspace-env.md'),
|
||||
'utf8'
|
||||
)
|
||||
const startMarker = 'recipe_id="${ORCA_RECIPE_ID:-vercel-sandbox}"'
|
||||
const endMarker = 'name="orca-${recipe_id:0:max_recipe_id_length}-${instance_id}"'
|
||||
const start = source.indexOf(startMarker)
|
||||
const endStart = source.indexOf(endMarker, start)
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
expect(endStart).toBeGreaterThan(start)
|
||||
const script = `${source.slice(start, endStart + endMarker.length)}\nprintf '%s' "$name"`
|
||||
const renderName = async (recipeId, instanceId) =>
|
||||
(
|
||||
await execFileAsync('bash', ['-u', '-c', script], {
|
||||
env: { ...process.env, ORCA_RECIPE_ID: recipeId, ORCA_VM_INSTANCE_ID: instanceId }
|
||||
})
|
||||
).stdout
|
||||
|
||||
const instanceId = 'orca-123e4567-e89b-12d3-a456-426614174000'
|
||||
const dotted = await renderName('provider.cloud_sandbox', instanceId)
|
||||
const maximum = await renderName(`a${'.'.repeat(63)}`, instanceId)
|
||||
const longInstanceId = 'i'.repeat(100)
|
||||
const capped = await renderName(
|
||||
'provider.cloud_sandbox.with.a.long.recipe.identifier',
|
||||
longInstanceId
|
||||
)
|
||||
|
||||
expect(dotted).toBe(`orca-provider-cloud_sandbox-${instanceId}`)
|
||||
expect(maximum).toMatch(/^[a-zA-Z0-9_-]{1,128}$/u)
|
||||
expect(capped).toHaveLength(128)
|
||||
expect(capped.endsWith(`-${longInstanceId}`)).toBe(true)
|
||||
}
|
||||
)
|
||||
|
||||
it('embeds canonical names, discovery descriptions, Markdown, and append-only aliases', async () => {
|
||||
expect(BUNDLED_SKILL_GUIDES.map((guide) => guide.name)).toEqual(
|
||||
[...CANONICAL_GUIDE_NAMES].sort((left, right) => left.localeCompare(right, 'en'))
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { parse } from 'yaml'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const workflow = parse(readFileSync('.github/workflows/pr.yml', 'utf8'))
|
||||
const headlessLinuxGuide = readFileSync('docs/reference/headless-linux-server.md', 'utf8')
|
||||
|
||||
function readSystemdUnitBlocks(doc, unitName) {
|
||||
const escapedUnitName = unitName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
return [...doc.matchAll(new RegExp(`^# /etc/systemd/system/${escapedUnitName}$`, 'gm'))].map(
|
||||
(match) => {
|
||||
const start = match.index + match[0].length
|
||||
const end = doc.indexOf('```', start)
|
||||
const nextUnitHeaderOffset = doc.slice(start).search(/^# \/etc\/systemd\/system\/.+$/m)
|
||||
const nextUnitHeader = nextUnitHeaderOffset === -1 ? -1 : start + nextUnitHeaderOffset
|
||||
if (end === -1 || (nextUnitHeader !== -1 && end > nextUnitHeader)) {
|
||||
throw new Error(`Missing closing code fence for ${unitName}`)
|
||||
}
|
||||
return doc.slice(start, end)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
describe('headless serve shutdown PR gate', () => {
|
||||
it('reads only exact, closed systemd unit blocks', () => {
|
||||
expect(
|
||||
readSystemdUnitBlocks('# /etc/systemd/system/orca-serveXservice\n```', 'orca-serve.service')
|
||||
).toEqual([])
|
||||
expect(() =>
|
||||
readSystemdUnitBlocks('# /etc/systemd/system/orca-serve.service\n', 'orca-serve.service')
|
||||
).toThrow('Missing closing code fence for orca-serve.service')
|
||||
expect(() =>
|
||||
readSystemdUnitBlocks(
|
||||
'# /etc/systemd/system/orca-serve.service\n' +
|
||||
'KillMode=mixed\n' +
|
||||
'# /etc/systemd/system/other.service\n```',
|
||||
'orca-serve.service'
|
||||
)
|
||||
).toThrow('Missing closing code fence for orca-serve.service')
|
||||
})
|
||||
|
||||
it('packages an x64 AppImage before running the Docker signal oracle', () => {
|
||||
const steps = workflow.jobs.package.steps
|
||||
const packageStep = steps.find((step) => step.name === 'Package unpacked app')
|
||||
const shutdownStep = steps.find((step) => step.name === 'Verify headless serve signal shutdown')
|
||||
|
||||
expect(packageStep.run).toContain('--linux AppImage --x64 --publish never')
|
||||
expect(shutdownStep.run).toBe(
|
||||
'node config/scripts/run-headless-serve-shutdown-docker.mjs --appimage dist/orca-linux.AppImage'
|
||||
)
|
||||
expect(steps.indexOf(shutdownStep)).toBeGreaterThan(steps.indexOf(packageStep))
|
||||
})
|
||||
|
||||
it('keeps owned Xvfb alive during the documented systemd graceful stop', () => {
|
||||
const serveUnits = readSystemdUnitBlocks(headlessLinuxGuide, 'orca-serve.service')
|
||||
const ownedXvfbUnits = serveUnits.filter((unit) => !/^Environment=DISPLAY=/m.test(unit))
|
||||
const managedXvfbUnits = serveUnits.filter((unit) => /^Environment=DISPLAY=/m.test(unit))
|
||||
|
||||
expect(ownedXvfbUnits).toHaveLength(1)
|
||||
expect(ownedXvfbUnits[0]).toMatch(/^ExecStart=.*orca-linux\.AppImage serve.*$/m)
|
||||
expect(ownedXvfbUnits[0]).toMatch(/^KillMode=mixed$/m)
|
||||
expect(managedXvfbUnits).toHaveLength(1)
|
||||
expect(managedXvfbUnits[0]).not.toMatch(/^KillMode=/m)
|
||||
})
|
||||
})
|
||||
@@ -115,10 +115,6 @@ export const CROSS_LOCALE_KEY_OVERRIDES = {
|
||||
zh: '评审中',
|
||||
ja: 'レビュー中'
|
||||
},
|
||||
'auto.components.skills.SkillsPage.b088e0785d': {
|
||||
zh: '测试版',
|
||||
ja: 'ベータ'
|
||||
},
|
||||
'auto.components.status.bar.WorkspaceSpaceCompactPanel.c361440dc0': {
|
||||
zh: '测试版',
|
||||
ja: 'ベータ'
|
||||
|
||||
@@ -312,11 +312,6 @@ const BASE_LOCALE_KEY_OVERRIDES = {
|
||||
zh: '显示中',
|
||||
ja: '表示中'
|
||||
},
|
||||
'auto.components.skills.SkillsPage.a68dee6a32': {
|
||||
ko: '스킬 검색',
|
||||
zh: '搜索技能',
|
||||
ja: 'スキルを検索'
|
||||
},
|
||||
'auto.components.editor.RichMarkdownSlashMenu.550189b06c': {
|
||||
ko: '블록 검색',
|
||||
zh: '搜索块',
|
||||
@@ -435,11 +430,6 @@ const BASE_LOCALE_KEY_OVERRIDES = {
|
||||
zh: '代理位置',
|
||||
ja: 'Agent の場所'
|
||||
},
|
||||
'auto.components.skills.SkillsPage.38e0951c3a': {
|
||||
ko: '에이전트 스킬',
|
||||
zh: '代理技能',
|
||||
ja: 'Agent のスキル'
|
||||
},
|
||||
'auto.components.sidebar.SidebarNav.e518f544b1': {
|
||||
ko: '감지된 에이전트 없음',
|
||||
zh: '未检测到代理',
|
||||
|
||||
@@ -4445,9 +4445,6 @@
|
||||
"auto.components.sidebar.worktree.list.groups.6798dc7c94": {
|
||||
"ko": "리뷰 중"
|
||||
},
|
||||
"auto.components.skills.SkillsPage.b088e0785d": {
|
||||
"ko": "베타"
|
||||
},
|
||||
"auto.components.source.control.SourceControlActionVariableChips.4bf6d88039": {
|
||||
"ko": "(비어 있음)"
|
||||
},
|
||||
|
||||
@@ -221,16 +221,20 @@ describe('orchestration skill guidance', () => {
|
||||
expect(skill).toContain('the named owner edits files and creates the PR')
|
||||
})
|
||||
|
||||
it('keeps worker_done post-completion guidance idle instead of polling', () => {
|
||||
it('keeps post-completion workers idle without subordinating the user', () => {
|
||||
const skill = readSkill()
|
||||
const agentGuidance = getSection(skill, 'Agent Guidance')
|
||||
|
||||
expect(agentGuidance).toContain('After sending `worker_done`, end your turn')
|
||||
expect(agentGuidance).toContain('After sending `worker_done`, end that dispatched turn')
|
||||
expect(agentGuidance).toContain('idle at the agent prompt')
|
||||
expect(agentGuidance).toContain('Do not autonomously start more work, poll')
|
||||
expect(agentGuidance).toContain('A direct user instruction takes precedence')
|
||||
expect(agentGuidance).toContain('follow it without coordinator approval or a fresh Dispatch')
|
||||
expect(agentGuidance).toContain('never refuse it because of worker/coordinator roles')
|
||||
expect(agentGuidance).toContain("do not reuse the settled Dispatch's lifecycle IDs")
|
||||
expect(agentGuidance).toContain(
|
||||
'do not start more work, poll, or attempt to close the terminal yourself'
|
||||
'A coordinator-supervised follow-up still arrives with a fresh preamble + TASK block'
|
||||
)
|
||||
expect(agentGuidance).toContain('fresh preamble + TASK block delivered as new terminal input')
|
||||
expect(skill).not.toContain('post-completion polling messages')
|
||||
expect(skill).not.toContain('every 2 minutes')
|
||||
})
|
||||
@@ -305,7 +309,7 @@ describe('orchestration skill guidance', () => {
|
||||
/\b(?:after|on|upon) (?:a |the )?(?:tui-?idle|idle state|timeout|heartbeat)\b[^.]*\brelease/iu
|
||||
)
|
||||
expect(agentGuidance).toContain(
|
||||
'do not start more work, poll, or attempt to close the terminal yourself'
|
||||
'Do not autonomously start more work, poll, or attempt to close the terminal yourself'
|
||||
)
|
||||
expect(agentGuidance).not.toMatch(/worker-release[^.]*\byourself\b/iu)
|
||||
})
|
||||
|
||||
@@ -568,16 +568,18 @@ describe('Electron runtime package contract', () => {
|
||||
'pnpm run test:e2e:terminal-rendering-golden'
|
||||
)
|
||||
expect(goldenRunSteps.get('linux')?.run).toContain(
|
||||
'pnpm run test:e2e:posix-profile-index-golden'
|
||||
'pnpm run --if-present test:e2e:posix-profile-index-golden'
|
||||
)
|
||||
expect(goldenRunSteps.get('mac')?.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
|
||||
expect(goldenRunSteps.get('mac')?.run).toContain('pnpm run test:e2e:posix-profile-index-golden')
|
||||
expect(goldenRunSteps.get('mac')?.run).toContain(
|
||||
'pnpm run --if-present test:e2e:posix-profile-index-golden'
|
||||
)
|
||||
expect(goldenRunSteps.get('windows')).toMatchObject({
|
||||
if: "runner.os == 'Windows'",
|
||||
shell: 'pwsh'
|
||||
})
|
||||
expect(goldenRunSteps.get('windows').run).toContain(
|
||||
'pnpm run test:e2e:windows-fresh-startup-golden'
|
||||
'pnpm run --if-present test:e2e:windows-fresh-startup-golden'
|
||||
)
|
||||
expect(goldenWorkflow.on.pull_request).toBeUndefined()
|
||||
expect(goldenWorkflow.on.workflow_dispatch).toBeDefined()
|
||||
@@ -592,12 +594,16 @@ describe('Electron runtime package contract', () => {
|
||||
(step) => step.name === 'Run terminal rendering golden on Linux'
|
||||
)
|
||||
expect(releaseLinuxRunStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
|
||||
expect(releaseLinuxRunStep.run).toContain('pnpm run test:e2e:posix-profile-index-golden')
|
||||
expect(releaseLinuxRunStep.run).toContain(
|
||||
'pnpm run --if-present test:e2e:posix-profile-index-golden'
|
||||
)
|
||||
const releaseMacRunStep = releaseGoldenJob.steps.find(
|
||||
(step) => step.name === 'Run terminal rendering golden on macOS'
|
||||
)
|
||||
expect(releaseMacRunStep.run).toContain('pnpm run test:e2e:terminal-rendering-golden')
|
||||
expect(releaseMacRunStep.run).toContain('pnpm run test:e2e:posix-profile-index-golden')
|
||||
expect(releaseMacRunStep.run).toContain(
|
||||
'pnpm run --if-present test:e2e:posix-profile-index-golden'
|
||||
)
|
||||
const releaseWindowsRunStep = releaseGoldenJob.steps.find(
|
||||
(step) => step.name === 'Run fresh-startup golden on Windows'
|
||||
)
|
||||
@@ -605,7 +611,9 @@ describe('Electron runtime package contract', () => {
|
||||
if: "runner.os == 'Windows'",
|
||||
shell: 'pwsh'
|
||||
})
|
||||
expect(releaseWindowsRunStep.run).toContain('pnpm run test:e2e:windows-fresh-startup-golden')
|
||||
expect(releaseWindowsRunStep.run).toContain(
|
||||
'pnpm run --if-present test:e2e:windows-fresh-startup-golden'
|
||||
)
|
||||
expect(releaseEvidenceJob['continue-on-error']).toBe(true)
|
||||
expect(
|
||||
releaseEvidenceJob.strategy.matrix.include.map(({ platform }) => platform).sort()
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const appImageArg = valueAfter('--appimage')
|
||||
const platform = valueAfter('--platform') ?? 'linux/amd64'
|
||||
const signalTarget = valueAfter('--signal-target') ?? 'app'
|
||||
const entrypoint = valueAfter('--entrypoint') ?? 'app'
|
||||
const intDelivery = valueAfter('--int-delivery') ?? 'foreground-process-group'
|
||||
const launcherExecOverlay = args.includes('--launcher-exec-overlay')
|
||||
if (!appImageArg) {
|
||||
fail('Usage: run-headless-serve-shutdown-docker.mjs --appimage /path/to/orca.AppImage')
|
||||
}
|
||||
if (!['app', 'serving-electron'].includes(signalTarget)) {
|
||||
fail(`Unsupported --signal-target: ${signalTarget}`)
|
||||
}
|
||||
if (!['app', 'launcher'].includes(entrypoint)) {
|
||||
fail(`Unsupported --entrypoint: ${entrypoint}`)
|
||||
}
|
||||
if (!['pid', 'foreground-process-group'].includes(intDelivery)) {
|
||||
fail(`Unsupported --int-delivery: ${intDelivery}`)
|
||||
}
|
||||
if (intDelivery === 'foreground-process-group' && signalTarget !== 'app') {
|
||||
fail('--int-delivery foreground-process-group requires --signal-target app')
|
||||
}
|
||||
if (launcherExecOverlay && entrypoint !== 'launcher') {
|
||||
fail('--launcher-exec-overlay requires --entrypoint launcher')
|
||||
}
|
||||
|
||||
const appImage = resolve(appImageArg)
|
||||
const shutdownDockerDirectory = resolve('config', 'docker', 'headless-serve-shutdown')
|
||||
const shutdownDockerfile = resolve(shutdownDockerDirectory, 'Dockerfile')
|
||||
if (!existsSync(appImage)) {
|
||||
fail(`AppImage not found: ${appImage}`)
|
||||
}
|
||||
|
||||
const suffix = `${process.pid}-${Date.now()}`
|
||||
const image = `orca-headless-serve-shutdown:${suffix}`
|
||||
const artifactVolume = `orca-headless-serve-shutdown-${suffix}`
|
||||
const sha256 = createHash('sha256').update(readFileSync(appImage)).digest('hex')
|
||||
|
||||
try {
|
||||
docker([
|
||||
'build',
|
||||
'--platform',
|
||||
platform,
|
||||
'-f',
|
||||
shutdownDockerfile,
|
||||
'-t',
|
||||
image,
|
||||
shutdownDockerDirectory
|
||||
])
|
||||
docker(['volume', 'create', artifactVolume])
|
||||
docker([
|
||||
'run',
|
||||
'--rm',
|
||||
'--platform',
|
||||
platform,
|
||||
'--entrypoint',
|
||||
'bash',
|
||||
'-v',
|
||||
`${appImage}:/input/orca.AppImage:ro`,
|
||||
'-v',
|
||||
`${artifactVolume}:/artifacts`,
|
||||
image,
|
||||
'-lc',
|
||||
[
|
||||
'7z x /input/orca.AppImage -o/artifacts/root -y >/dev/null',
|
||||
launcherExecOverlay
|
||||
? "sed -i 's/^ELECTRON_RUN_AS_NODE=1 /export ELECTRON_RUN_AS_NODE=1\\nexec /' /artifacts/root/resources/bin/orca-ide"
|
||||
: ':',
|
||||
'chmod -R a+rX /artifacts/root'
|
||||
].join(' && ')
|
||||
])
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
type: 'appimage_under_test',
|
||||
appImage,
|
||||
sha256,
|
||||
platform,
|
||||
signalTarget,
|
||||
entrypoint,
|
||||
intDelivery,
|
||||
launcherExecOverlay
|
||||
})
|
||||
)
|
||||
const failedSignals = []
|
||||
for (const signal of ['INT', 'TERM']) {
|
||||
const result = docker(
|
||||
[
|
||||
'run',
|
||||
'--rm',
|
||||
'--init',
|
||||
'--platform',
|
||||
platform,
|
||||
'--shm-size',
|
||||
'256m',
|
||||
'--name',
|
||||
`orca-headless-serve-shutdown-${signal.toLowerCase()}-${suffix}`,
|
||||
'-e',
|
||||
`ORCA_SIGNAL_TARGET=${signalTarget}`,
|
||||
'-e',
|
||||
`ORCA_TEST_ENTRYPOINT=${entrypoint}`,
|
||||
'-e',
|
||||
`ORCA_INT_DELIVERY=${intDelivery}`,
|
||||
'-v',
|
||||
`${artifactVolume}:/artifacts:ro`,
|
||||
image,
|
||||
signal
|
||||
],
|
||||
{ allowFailure: true }
|
||||
)
|
||||
process.stdout.write(result.stdout)
|
||||
process.stderr.write(result.stderr)
|
||||
if (result.status !== 0) {
|
||||
failedSignals.push(`${signal}:${result.status}`)
|
||||
}
|
||||
}
|
||||
if (failedSignals.length > 0) {
|
||||
fail(`Shutdown oracle failed: ${failedSignals.join(', ')}`)
|
||||
}
|
||||
console.log('Headless serve packaged shutdown Docker validation passed.')
|
||||
} finally {
|
||||
docker(['volume', 'rm', artifactVolume], { allowFailure: true })
|
||||
docker(['image', 'rm', image], { allowFailure: true })
|
||||
}
|
||||
|
||||
function valueAfter(flag) {
|
||||
const index = args.indexOf(flag)
|
||||
return index === -1 ? null : (args[index + 1] ?? null)
|
||||
}
|
||||
|
||||
function docker(dockerArgs, options = {}) {
|
||||
const result = spawnSync('docker', dockerArgs, {
|
||||
cwd: process.cwd(),
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 20 * 1024 * 1024
|
||||
})
|
||||
if (result.error) {
|
||||
throw result.error
|
||||
}
|
||||
if (result.status !== 0 && !options.allowFailure) {
|
||||
process.stdout.write(result.stdout)
|
||||
process.stderr.write(result.stderr)
|
||||
fail(`docker ${dockerArgs[0]} failed with status ${result.status}`)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function fail(message) {
|
||||
console.error(message)
|
||||
process.exitCode = 1
|
||||
throw new Error(message)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { performance } from 'node:perf_hooks'
|
||||
import { planWorktreeSortOrderUpdates } from '../../src/shared/worktree-sort-order-update.ts'
|
||||
import { planWorktreeSortOrderUpdates } from '../../src/shared/worktree/sort-order-update.ts'
|
||||
import { reuseEqualCatalogRows } from '../../src/renderer/src/store/slices/worktree-catalog-reconciliation.ts'
|
||||
|
||||
const WORKTREE_COUNT = 655
|
||||
|
||||
Reference in New Issue
Block a user