mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(release): recover immutable patch validation gates (#16984)
* fix(release): recover immutable patch validation gates * test(e2e): locate wrapped terminal file links * test(e2e): keep sibling file links on one terminal row
This commit is contained in:
@@ -844,6 +844,16 @@ jobs:
|
||||
with:
|
||||
ref: refs/tags/${{ needs.cut.outputs.tag }}
|
||||
|
||||
- name: Restore golden test harness from the workflow ref
|
||||
shell: bash
|
||||
env:
|
||||
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
||||
run: |
|
||||
git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"
|
||||
git checkout "$WORKFLOW_SHA" -- \
|
||||
tests/e2e/golden-source-control-open-diff.spec.ts \
|
||||
tests/e2e/golden-terminal-file-link.spec.ts
|
||||
|
||||
- name: Install native build tools
|
||||
if: runner.os == 'Linux'
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential python3 xvfb
|
||||
@@ -946,6 +956,16 @@ jobs:
|
||||
with:
|
||||
ref: refs/tags/${{ needs.cut.outputs.tag }}
|
||||
|
||||
- name: Restore skill-sharing test harness from the workflow ref
|
||||
shell: bash
|
||||
env:
|
||||
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
||||
run: |
|
||||
git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"
|
||||
git checkout "$WORKFLOW_SHA" -- \
|
||||
src/main/skills/skill-freshness-inventory.test.ts \
|
||||
src/main/skills/skill-provider-runtime-roots.test.ts
|
||||
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
with:
|
||||
native-runtime: node
|
||||
@@ -978,13 +998,23 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Install Ubuntu 20.04 prerequisites
|
||||
run: apt-get update && apt-get install -y build-essential ca-certificates git python3
|
||||
run: apt-get update && apt-get install -y build-essential ca-certificates git python3 unzip
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ needs.cut.outputs.tag }}
|
||||
|
||||
- name: Restore skill-sharing test harness from the workflow ref
|
||||
shell: bash
|
||||
env:
|
||||
WORKFLOW_SHA: ${{ github.workflow_sha }}
|
||||
run: |
|
||||
git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"
|
||||
git checkout "$WORKFLOW_SHA" -- \
|
||||
src/main/skills/skill-freshness-inventory.test.ts \
|
||||
src/main/skills/skill-provider-runtime-roots.test.ts
|
||||
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
with:
|
||||
native-runtime: node
|
||||
|
||||
@@ -10,6 +10,17 @@ const releaseWorkflow = parse(
|
||||
const e2eWorkflow = parse(readFileSync(join(projectDir, '.github/workflows/e2e.yml'), 'utf8'))
|
||||
|
||||
describe('release E2E dispatch contract', () => {
|
||||
it('validates immutable tags with the current golden test harness', () => {
|
||||
const restoreStep = releaseWorkflow.jobs['terminal-rendering-golden'].steps.find(
|
||||
(step) => step.name === 'Restore golden test harness from the workflow ref'
|
||||
)
|
||||
|
||||
expect(restoreStep.env.WORKFLOW_SHA).toBe('${{ github.workflow_sha }}')
|
||||
expect(restoreStep.run).toContain('git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"')
|
||||
expect(restoreStep.run).toContain('golden-source-control-open-diff.spec.ts')
|
||||
expect(restoreStep.run).toContain('golden-terminal-file-link.spec.ts')
|
||||
})
|
||||
|
||||
it('dispatches tag-scoped E2E only after publication', () => {
|
||||
const dispatchJob = releaseWorkflow.jobs['post-release-e2e']
|
||||
const dispatchStep = dispatchJob.steps.find((step) => step.name === 'Dispatch tag-scoped E2E')
|
||||
|
||||
@@ -44,6 +44,30 @@ describe('skill-sharing release workflow', () => {
|
||||
expect(command).toContain('src/shared/skill-bundle-install-contract.test.ts')
|
||||
})
|
||||
|
||||
it('installs the archive tool required by Electron on the Linux floor', () => {
|
||||
const linux = workflow.jobs['skill-sharing-linux-floor-release-gate']
|
||||
const prerequisites = stepNamed(linux, 'Install Ubuntu 20.04 prerequisites')
|
||||
|
||||
expect(prerequisites.run).toMatch(/apt-get install[^\n]*\bunzip\b/)
|
||||
})
|
||||
|
||||
it('validates immutable tags with the current skill-sharing test harness', () => {
|
||||
for (const jobName of [
|
||||
'skill-sharing-release-gate',
|
||||
'skill-sharing-linux-floor-release-gate'
|
||||
]) {
|
||||
const restore = stepNamed(
|
||||
workflow.jobs[jobName],
|
||||
'Restore skill-sharing test harness from the workflow ref'
|
||||
)
|
||||
|
||||
expect(restore.env.WORKFLOW_SHA).toBe('${{ github.workflow_sha }}')
|
||||
expect(restore.run).toContain('git fetch --no-tags --depth=1 origin "$WORKFLOW_SHA"')
|
||||
expect(restore.run).toContain('skill-freshness-inventory.test.ts')
|
||||
expect(restore.run).toContain('skill-provider-runtime-roots.test.ts')
|
||||
}
|
||||
})
|
||||
|
||||
it('archives bounded machine-readable evidence from every platform', () => {
|
||||
for (const jobName of [
|
||||
'skill-sharing-release-gate',
|
||||
|
||||
@@ -157,7 +157,11 @@ async function writeSkillLockHash(homeDir: string, skillFolderHash: string): Pro
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((root) => rm(root, { recursive: true })))
|
||||
await Promise.all(
|
||||
temporaryDirectories
|
||||
.splice(0)
|
||||
.map((root) => rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }))
|
||||
)
|
||||
})
|
||||
|
||||
describe('read-only skill freshness inventory', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { join } from 'node:path'
|
||||
import { join, resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
resolveDefaultHermesSkillsRoot,
|
||||
@@ -10,14 +10,16 @@ import {
|
||||
|
||||
describe('skill provider runtime roots', () => {
|
||||
it('maps Claude and Grok config homes to their global skill roots', () => {
|
||||
const claudeRoot = resolve('/srv/claude')
|
||||
const grokRoot = resolve('/srv/grok')
|
||||
expect(
|
||||
resolveEnvironmentSkillProviderRoots({
|
||||
CLAUDE_CONFIG_DIR: join('/srv', 'claude'),
|
||||
GROK_HOME: join('/srv', 'grok')
|
||||
CLAUDE_CONFIG_DIR: claudeRoot,
|
||||
GROK_HOME: grokRoot
|
||||
})
|
||||
).toEqual({
|
||||
claude: join('/srv', 'claude', 'skills'),
|
||||
grok: join('/srv', 'grok', 'skills')
|
||||
claude: join(claudeRoot, 'skills'),
|
||||
grok: join(grokRoot, 'skills')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,14 +29,16 @@ describe('skill provider runtime roots', () => {
|
||||
GROK_HOME: '../grok'
|
||||
})
|
||||
expect(roots).toEqual({})
|
||||
expect(withClaudeSkillProviderRoot(roots, join('/managed', 'claude'))).toEqual({
|
||||
claude: join('/managed', 'claude', 'skills')
|
||||
const managedClaudeRoot = resolve('/managed/claude')
|
||||
expect(withClaudeSkillProviderRoot(roots, managedClaudeRoot)).toEqual({
|
||||
claude: join(managedClaudeRoot, 'skills')
|
||||
})
|
||||
})
|
||||
|
||||
it('maps a relocated HERMES_HOME to its skill root and ignores relative ones', () => {
|
||||
expect(resolveEnvironmentHermesSkillsRoot({ HERMES_HOME: join('/srv', 'hermes') })).toBe(
|
||||
join('/srv', 'hermes', 'skills')
|
||||
const hermesRoot = resolve('/srv/hermes')
|
||||
expect(resolveEnvironmentHermesSkillsRoot({ HERMES_HOME: hermesRoot })).toBe(
|
||||
join(hermesRoot, 'skills')
|
||||
)
|
||||
expect(resolveEnvironmentHermesSkillsRoot({ HERMES_HOME: '../hermes' })).toBeNull()
|
||||
expect(resolveEnvironmentHermesSkillsRoot({})).toBeNull()
|
||||
@@ -52,13 +56,14 @@ describe('skill provider runtime roots', () => {
|
||||
})
|
||||
|
||||
it('defaults the Hermes skills root under LOCALAPPDATA on Windows', () => {
|
||||
const localAppData = resolve('/local')
|
||||
const resolved = resolveDefaultHermesSkillsRoot({
|
||||
homeDir: join('/users', 'alice'),
|
||||
platform: 'win32',
|
||||
env: { LOCALAPPDATA: join('/local') },
|
||||
directoryExists: (candidate) => candidate === join('/local', 'hermes')
|
||||
env: { LOCALAPPDATA: localAppData },
|
||||
directoryExists: (candidate) => candidate === join(localAppData, 'hermes')
|
||||
})
|
||||
expect(resolved).toBe(join('/local', 'hermes', 'skills'))
|
||||
expect(resolved).toBe(join(localAppData, 'hermes', 'skills'))
|
||||
})
|
||||
|
||||
it('keeps a pre-LOCALAPPDATA Windows dotfolder install discoverable', () => {
|
||||
@@ -72,13 +77,14 @@ describe('skill provider runtime roots', () => {
|
||||
})
|
||||
|
||||
it('prefers the LOCALAPPDATA tree on Windows when both layouts exist', () => {
|
||||
const localAppData = resolve('/local')
|
||||
const resolved = resolveDefaultHermesSkillsRoot({
|
||||
homeDir: join('/users', 'alice'),
|
||||
platform: 'win32',
|
||||
env: { LOCALAPPDATA: join('/local') },
|
||||
env: { LOCALAPPDATA: localAppData },
|
||||
directoryExists: () => true
|
||||
})
|
||||
expect(resolved).toBe(join('/local', 'hermes', 'skills'))
|
||||
expect(resolved).toBe(join(localAppData, 'hermes', 'skills'))
|
||||
})
|
||||
|
||||
it('falls back to the dotfolder when Windows exposes no usable LOCALAPPDATA', () => {
|
||||
|
||||
@@ -43,7 +43,7 @@ test('@golden opens an unstaged file diff from Source Control', async ({
|
||||
).toBeVisible()
|
||||
await expect(orcaPage.locator('.editor-header-path').first()).toHaveAttribute(
|
||||
'title',
|
||||
`${realpathSync(path.join(fixture.worktreePath, GOLDEN_CHANGED_PATH))} (diff)`
|
||||
`${realpathSync(path.join(fixture.worktreePath, GOLDEN_CHANGED_PATH)).replaceAll('\\', '/')} (diff)`
|
||||
)
|
||||
|
||||
const probe = orcaPage.getByRole('button', { name: /Source Control/ })
|
||||
|
||||
@@ -18,6 +18,11 @@ type LinkClientPoint = { x: number; y: number }
|
||||
|
||||
const LINK_SCAN_CHAR_LIMIT = 12_000
|
||||
|
||||
function canonicalFileIdentity(value: string): string {
|
||||
const normalized = path.resolve(value).replaceAll('\\', '/')
|
||||
return process.platform === 'win32' ? normalized.toLowerCase() : normalized
|
||||
}
|
||||
|
||||
async function locateLink(page: Page, needle: string): Promise<LinkProbe | null> {
|
||||
return page.evaluate((needle) => {
|
||||
const state = window.__store?.getState()
|
||||
@@ -34,11 +39,19 @@ async function locateLink(page: Page, needle: string): Promise<LinkProbe | null>
|
||||
return null
|
||||
}
|
||||
const buffer = pane.terminal.buffer.active
|
||||
for (let row = 0; row < pane.terminal.rows; row += 1) {
|
||||
const line = buffer.getLine(buffer.viewportY + row)
|
||||
const col = line?.translateToString(true).indexOf(needle) ?? -1
|
||||
if (col >= 0) {
|
||||
return { col: col + Math.floor(needle.length / 2), row, tabId }
|
||||
// Preserve fixed-width cells so paths split across xterm rows stay searchable.
|
||||
const visibleCells = Array.from({ length: pane.terminal.rows }, (_, row) =>
|
||||
(buffer.getLine(buffer.viewportY + row)?.translateToString(false) ?? '').padEnd(
|
||||
pane.terminal.cols
|
||||
)
|
||||
).join('')
|
||||
const start = visibleCells.indexOf(needle)
|
||||
if (start !== -1) {
|
||||
const center = start + Math.floor(needle.length / 2)
|
||||
return {
|
||||
col: center % pane.terminal.cols,
|
||||
row: Math.floor(center / pane.terminal.cols),
|
||||
tabId
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -162,14 +175,13 @@ test('opens a terminal file link and observes an external edit @golden', async (
|
||||
await expect(explorerRow).toHaveAttribute('data-selected', 'true', { timeout: 10_000 })
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
orcaPage.evaluate(
|
||||
(expectedPath) => window.__monacoEditorE2E?.filePath === expectedPath,
|
||||
filePath
|
||||
async () =>
|
||||
canonicalFileIdentity(
|
||||
(await orcaPage.evaluate(() => window.__monacoEditorE2E?.filePath)) ?? ''
|
||||
),
|
||||
{ timeout: 20_000, message: 'Monaco opened a different file identity' }
|
||||
)
|
||||
.toBe(true)
|
||||
.toBe(canonicalFileIdentity(filePath))
|
||||
|
||||
writeFileSync(filePath, `${original.trimEnd()}\n\n${changedMarker}\n`)
|
||||
await expect
|
||||
@@ -196,14 +208,18 @@ test('reuses a terminal file link already open in a sibling workspace @golden',
|
||||
test.setTimeout(180_000)
|
||||
await waitForSessionReady(orcaPage)
|
||||
const sourceWorktreeId = await waitForActiveWorktree(orcaPage)
|
||||
const sibling = await orcaPage.evaluate((sourceId) => {
|
||||
const worktrees = await orcaPage.evaluate((sourceId) => {
|
||||
const state = window.__store?.getState()
|
||||
return (
|
||||
Object.values(state?.worktreesByRepo ?? {})
|
||||
.flat()
|
||||
.find((worktree) => worktree.id !== sourceId) ?? null
|
||||
)
|
||||
const entries = Object.values(state?.worktreesByRepo ?? {}).flat()
|
||||
return {
|
||||
source: entries.find((worktree) => worktree.id === sourceId) ?? null,
|
||||
sibling: entries.find((worktree) => worktree.id !== sourceId) ?? null
|
||||
}
|
||||
}, sourceWorktreeId)
|
||||
const { source, sibling } = worktrees
|
||||
if (!source) {
|
||||
throw new Error('source worktree fixture unavailable')
|
||||
}
|
||||
if (!sibling) {
|
||||
throw new Error('sibling worktree fixture unavailable')
|
||||
}
|
||||
@@ -230,7 +246,24 @@ test('reuses a terminal file link already open in a sibling workspace @golden',
|
||||
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForActiveTerminalManager(orcaPage, 30_000)
|
||||
const ptyId = await waitForActivePanePtyId(orcaPage)
|
||||
await orcaPage.evaluate(() => {
|
||||
const state = window.__store?.getState()
|
||||
state?.setSidebarOpen(false)
|
||||
state?.setRightSidebarOpen(false)
|
||||
})
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
orcaPage.evaluate(() => {
|
||||
const state = window.__store?.getState()
|
||||
const tabId = state?.activeTabId
|
||||
const manager = tabId ? window.__paneManagers?.get(tabId) : null
|
||||
return manager?.getActivePane?.()?.terminal.cols ?? 0
|
||||
}),
|
||||
{ message: 'terminal did not expand after closing the sidebars' }
|
||||
)
|
||||
.toBeGreaterThan(120)
|
||||
const ptyId = await waitForActivePanePtyId(orcaPage, 30_000)
|
||||
await waitForPtyShellEcho(orcaPage, ptyId, 15_000)
|
||||
const printedPath = process.platform === 'win32' ? filePath.replaceAll('\\', '/') : filePath
|
||||
const command = nodeTerminalCommand(['-e', `console.log(${JSON.stringify(printedPath)})`])
|
||||
@@ -261,13 +294,18 @@ test('reuses a terminal file link already open in a sibling workspace @golden',
|
||||
await expect(editorHeader).toContainText('package.json', { timeout: 20_000 })
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
orcaPage.evaluate(() => ({
|
||||
filePath: window.__monacoEditorE2E?.filePath ?? null,
|
||||
async () => {
|
||||
const rendered = await orcaPage.evaluate(() => ({
|
||||
filePath: window.__monacoEditorE2E?.filePath ?? '',
|
||||
activeWorktreeId: window.__store?.getState()?.activeWorktreeId ?? null
|
||||
})),
|
||||
}))
|
||||
return {
|
||||
filePath: canonicalFileIdentity(rendered.filePath),
|
||||
activeWorktreeId: rendered.activeWorktreeId
|
||||
}
|
||||
},
|
||||
{ timeout: 20_000, message: 'sibling workspace never rendered the linked file' }
|
||||
)
|
||||
.toEqual({ filePath, activeWorktreeId: sibling.id })
|
||||
.toEqual({ filePath: canonicalFileIdentity(filePath), activeWorktreeId: sibling.id })
|
||||
await expect(orcaPage.getByText('Loading...', { exact: true })).toHaveCount(0)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user