mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
* fix(ci): stop hourly versions dropping below a tagged or already-shipped build Hourly/daily/adhoc based their X.Y.Z on GitHub releases, not git tags. When v1.4.202 was tagged and then its GitHub release vanished, the next hourlies shipped as 1.4.202-hourly — below both stable 1.4.202 and the 1.4.203-hourly builds already installed, so electron-updater stopped offering updates. Read main's v* tags and already-published channel tags instead. * docs(ci): record that 1.4.202's release was unpublished for a bug The leftover tag is what hourly must still honor; this was not a failed cut.
128 lines
5.9 KiB
JavaScript
128 lines
5.9 KiB
JavaScript
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { parse } from 'yaml'
|
|
import { runProcessSync } from '../../src/shared/child-process/run-process'
|
|
|
|
const projectDir = resolve(import.meta.dirname, '../..')
|
|
|
|
const readWorkflow = (relativePath) => parse(readFileSync(join(projectDir, relativePath), 'utf8'))
|
|
|
|
// Every step that mirrors this repo's whole ref namespace onto a runner disk to
|
|
// prove a commit is reachable from a branch or tag before signing it.
|
|
const REF_MIRRORS = [
|
|
['.github/workflows/adhoc-mac-build.yml', 'build-adhoc-mac', 'Vet the requested ref'],
|
|
['.github/workflows/dev-channel-win-build.yml', 'build-win', 'Vet the requested inputs']
|
|
]
|
|
|
|
describe('ref-mirroring vet steps', () => {
|
|
it.each(['daily', 'hourly', 'adhoc'])('%s builds only need the current commit', (channel) => {
|
|
const job = readWorkflow(`.github/workflows/${channel}-mac-build.yml`).jobs[
|
|
`build-${channel}-mac`
|
|
]
|
|
const checkout = job.steps.find((step) => step.uses === 'actions/checkout@v6')
|
|
expect(checkout.with['fetch-depth']).toBe(1)
|
|
expect(job.steps.some((step) => step.run?.includes('gh release list'))).toBe(true)
|
|
expect(
|
|
job.steps.some((step) => step.run?.includes('ORCA_PUBLISHED_VERSIONS="$published"'))
|
|
).toBe(true)
|
|
})
|
|
|
|
// Why matching-refs rather than `gh release list` on the main repo: a tagged
|
|
// stable still owns its number after its GitHub release is unpublished for a
|
|
// bug, and that unpublish must not drag the channel backwards.
|
|
it.each(['daily', 'hourly', 'adhoc'])(
|
|
'%s versions from git tags, not main GitHub releases',
|
|
(channel) => {
|
|
const step = readWorkflow(`.github/workflows/${channel}-mac-build.yml`).jobs[
|
|
`build-${channel}-mac`
|
|
].steps.find((candidate) => candidate.name === `Compute ${channel} version`)
|
|
expect(step.run).toContain('git/matching-refs/tags/v')
|
|
expect(step.run).not.toMatch(
|
|
/gh release list[\s\S]*--repo "\$GITHUB_REPOSITORY"[\s\S]*--json tagName/
|
|
)
|
|
if (channel !== 'adhoc') {
|
|
expect(step.run).toContain('channel_tags=')
|
|
}
|
|
}
|
|
)
|
|
|
|
it('retains release-cut history for version reservation and retry ancestry', () => {
|
|
const checkout = readWorkflow('.github/workflows/release-cut.yml').jobs.cut.steps.find(
|
|
(step) => step.uses === 'actions/checkout@v6'
|
|
)
|
|
expect(checkout.with['fetch-depth']).toBe(0)
|
|
})
|
|
|
|
it('resolves identical dev identities in full and depth-one checkouts without local tags', () => {
|
|
const directory = mkdtempSync(join(tmpdir(), 'orca-checkout-identity-'))
|
|
const source = join(directory, 'source')
|
|
const shallow = join(directory, 'shallow')
|
|
const run = (program, args, cwd) => {
|
|
const result = runProcessSync({ program, args, cwd })
|
|
expect(result.code, result.stderr).toBe(0)
|
|
return result.stdout.trim()
|
|
}
|
|
const git = (args, cwd = directory) => run('git', args, cwd)
|
|
try {
|
|
git(['init', source])
|
|
git(['config', 'user.name', 'CI test'], source)
|
|
git(['config', 'user.email', 'ci@example.invalid'], source)
|
|
writeFileSync(join(source, 'package.json'), JSON.stringify({ version: '1.4.165-rc.0' }))
|
|
git(['add', 'package.json'], source)
|
|
git(['-c', 'commit.gpgsign=false', 'commit', '-m', 'initial'], source)
|
|
git(['tag', 'v1.4.167'], source)
|
|
git(['-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-m', 'head'], source)
|
|
git(['clone', '--depth=1', '--no-tags', pathToFileURL(source).href, shallow])
|
|
expect(git(['rev-list', '--count', 'HEAD'], shallow)).toBe('1')
|
|
expect(git(['tag', '--list'], shallow)).toBe('')
|
|
const script = `
|
|
const result = [];
|
|
for (const [channel, exported] of [['daily', 'Daily'], ['hourly', 'Hourly'], ['adhoc', 'Adhoc']]) {
|
|
const module = await import(${JSON.stringify(pathToFileURL(join(projectDir, 'config/scripts/')).href)} + channel + '-build-version.mjs');
|
|
const date = new Date('2026-09-12T00:00:00Z');
|
|
result.push(channel === 'adhoc'
|
|
? module.getAdhocBuildIdentity(date, 'branch', ['v1.4.167'])
|
|
: module['get' + exported + 'BuildIdentity'](date, { publishedVersions: ['v1.4.167'], releaseNames: [] }));
|
|
}
|
|
process.stdout.write(JSON.stringify(result));
|
|
`
|
|
const identities = (cwd) => run(process.execPath, ['--input-type=module', '-e', script], cwd)
|
|
expect(identities(shallow)).toBe(identities(source))
|
|
expect(
|
|
JSON.parse(identities(shallow)).every((identity) => identity.version.startsWith('1.4.168-'))
|
|
).toBe(true)
|
|
} finally {
|
|
rmSync(directory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
it('checks out only the vetted commit without remirroring refs', () => {
|
|
const steps = readWorkflow('.github/workflows/adhoc-mac-build.yml').jobs['build-adhoc-mac']
|
|
.steps
|
|
const checkout = steps.find((step) => step.name === 'Checkout the requested ref')
|
|
expect(checkout.with.ref).toBe('${{ steps.vetted.outputs.sha }}')
|
|
expect(checkout.with['fetch-depth']).toBe(1)
|
|
expect(checkout.with['persist-credentials']).toBe(false)
|
|
})
|
|
|
|
// Why: macOS and Windows runner disks are case-insensitive, and this repo has
|
|
// branches that differ only in casing. The files backend cannot store both, and
|
|
// it fails the whole fetch rather than the one ref — so the vet step dies before
|
|
// any build runs. reftable keys refs in a table instead of file paths.
|
|
it.each(REF_MIRRORS)(
|
|
'%s creates its scratch repo with the reftable backend',
|
|
(path, job, step) => {
|
|
const run = readWorkflow(path).jobs[job].steps.find(
|
|
(candidate) => candidate.name === step
|
|
).run
|
|
|
|
expect(run).toContain('+refs/heads/*:refs/heads/*')
|
|
expect(run).toMatch(/git init\b[^\n]*--ref-format=reftable/)
|
|
expect(run).not.toMatch(/git init -q --bare "\$scratch"/)
|
|
}
|
|
)
|
|
})
|