mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
* fix(skills): keep the disposal verdict when staging cleanup fails `begin()` ended with `await this.removeOwnershipIfDisposed()` inside its `finally`, so when a caller raced `dispose()` the rejection it received was whatever that opportunistic `rmdir` threw -- not `skill-upload-service-disposed`. A caller could not tell "the service shut down" from "the filesystem broke", and the Windows release gate saw it as `EPERM: operation not permitted, rmdir`. Two causes, both fixed here: - The EPERM itself: an in-flight operation and disposal each call `ownership.remove()`, so two `rm -rf` run concurrently against the same owner directory. On POSIX the loser reads ENOENT and `force: true` swallows it; on Windows the loser reads a delete-pending directory and gets EPERM. `SkillUploadStagingOwnership.remove()` now joins one removal and forgets it on failure so a later caller still retries. - The masking: cleanup in a `finally` no longer replaces the outcome of the call it is cleaning up after. Disposal retries staging removal and reports its own failure, matching `removeUnpublished`/`retainFailedCleanup` in this class. Both regressions are pinned platform-independently: one injects a failing ownership removal and asserts the racing `begin` still rejects with `skill-upload-service-disposed` while `dispose()` reports the cleanup failure; the other models Windows delete-pending rmdir in the `node:fs/promises` mock, which turns a second removal into EPERM on every platform. * ci(release-cut): retry the installs that fetch node-gyp headers `golden e2e windows` installs with lifecycle scripts enabled, so pnpm runs node-gyp for the `native/windows-registry` workspace project, which downloads that Node version's headers from nodejs.org. A single `read ECONNRESET` on that fetch failed a blocking release gate, and the release build job one screen below already wraps its install in `nick-fields/retry@v4` for exactly this class of failure. Both remaining unretried installs in this workflow (the blocking platform golden and the non-blocking rendering-evidence lane) now use the same wrapper, and a contract test keeps every release-cut install retryable.
89 lines
4.0 KiB
JavaScript
89 lines
4.0 KiB
JavaScript
import { readFileSync } from 'node:fs'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { parse } from 'yaml'
|
|
|
|
const read = (path) => parse(readFileSync(path, 'utf8'))
|
|
const workflow = (name) => read(`.github/workflows/${name}.yml`)
|
|
const action = read('.github/actions/install-node-dependencies/action.yml')
|
|
|
|
describe('CI dependency download caches', () => {
|
|
it('scopes desktop stores to the root lockfile and lets mixed installs opt in', () => {
|
|
expect(action.inputs['cache-dependency-path'].default).toBe('pnpm-lock.yaml')
|
|
for (const step of action.runs.steps.filter((step) => step.uses === 'actions/setup-node@v6')) {
|
|
expect(step.with.cache).toBe('pnpm')
|
|
expect(step.with['cache-dependency-path']).toBe('${{ inputs.cache-dependency-path }}')
|
|
}
|
|
const install = action.runs.steps.find((step) => step.name === 'Install dependencies')
|
|
expect(install.if).toBeUndefined()
|
|
expect(install.run).toContain('pnpm install --frozen-lockfile --ignore-scripts')
|
|
expect(install.run).toContain(
|
|
'diff --exit-code -- package.json pnpm-lock.yaml pnpm-workspace.yaml'
|
|
)
|
|
const mobile = workflow('mobile').jobs.verify.steps.find((step) =>
|
|
step.uses?.includes('install-node-dependencies')
|
|
)
|
|
expect(mobile.with['cache-dependency-path'].trim().split('\n')).toEqual([
|
|
'pnpm-lock.yaml',
|
|
'mobile/pnpm-lock.yaml'
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('release install targets', () => {
|
|
const macCpuFlag = '--cpu=current,x64,arm64'
|
|
// Both shapes: `run:` steps and steps wrapped in nick-fields/retry (`with.command`).
|
|
const installCommand = (step) => step.with?.command ?? step.run
|
|
const installSteps = (name) =>
|
|
Object.values(workflow(name).jobs)
|
|
.flatMap((job) => job.steps ?? [])
|
|
.filter((step) => installCommand(step)?.includes('pnpm install '))
|
|
const installCommands = (name) => installSteps(name).map(installCommand)
|
|
|
|
it.each(['adhoc-mac-build', 'daily-mac-build', 'hourly-mac-build', 'release-mac-build'])(
|
|
'%s installs both mac CPU variants for the x64+arm64 package config',
|
|
(name) => {
|
|
const installs = installCommands(name)
|
|
expect(installs.length).toBeGreaterThan(0)
|
|
expect(installs.some((command) => command.includes(macCpuFlag))).toBe(true)
|
|
}
|
|
)
|
|
|
|
// A transient `read ECONNRESET` fetching this Node version's headers for
|
|
// native/windows-registry's node-gyp rebuild failed a blocking golden gate and the cut.
|
|
it('retries every release-cut install so one transient download cannot fail a cut', () => {
|
|
const installs = installSteps('release-cut')
|
|
expect(installs.length).toBeGreaterThan(0)
|
|
for (const step of installs) {
|
|
expect(step.uses).toBe('nick-fields/retry@v4')
|
|
expect(step.with.max_attempts).toBeGreaterThan(1)
|
|
}
|
|
})
|
|
|
|
it.each(['release-cut', 'dev-channel-win-build', 'windows-signing-rehearsal'])(
|
|
'%s keeps installs scoped to the runner host',
|
|
(name) => {
|
|
const installs = installCommands(name)
|
|
expect(installs.length).toBeGreaterThan(0)
|
|
for (const command of installs) {
|
|
expect(command).not.toContain('--os=')
|
|
expect(command).not.toContain('--cpu=')
|
|
}
|
|
}
|
|
)
|
|
|
|
it('offers the mac CPU targets for local packaging without touching the lockfile', () => {
|
|
const script = JSON.parse(readFileSync('package.json', 'utf8')).scripts['install:release']
|
|
expect(script).toContain('--frozen-lockfile')
|
|
expect(script).toContain(macCpuFlag)
|
|
})
|
|
|
|
it('keeps installed Windows addon checks in the Windows CI lane', () => {
|
|
const steps = Object.values(workflow('pr').jobs).flatMap((job) => job.steps ?? [])
|
|
const test = steps.find((step) => step.name === 'Test Windows-specific boundaries')
|
|
expect(test.run).toContain('config/scripts/windows-process-tree-gyp-path.test.mjs')
|
|
expect(test.run).toContain('config/scripts/windows-process-tree-gyp-rebuild.test.mjs')
|
|
expect(test.run).toContain('config/scripts/package-electron-runtime-contract.test.mjs')
|
|
expect(test.run).toContain('config/scripts/electron-builder-runtime-resources.test.mjs')
|
|
})
|
|
})
|