mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
* feat(updater): name hourly releases by version, build number, time, and sha Hourly releases were titled with their raw tag (`v1.4.163-hourly.202607312054`), which reads as one opaque digit run and does not say which commit it came from. Title them `1.4.163 • 01 • 07-31 13:54 • e698241` instead, and show that same string in the in-app build picker by having the picker render the release's stored name rather than deriving its own label. Composing it in one place means the two surfaces cannot drift. The build number is monotonic across the channel. It is read as the highest number already in use rather than as a count of releases: the prune step trims to 72, so a count would roll backwards after three days and reissue numbers. Drafts count toward it — unlike in the freshness check, which asks whether a commit shipped, this asks whether a number is free, and a stranded draft still holds one. Times are Pacific while the tag's stamp stays UTC. The stamp is a sort key and a local one would repeat an hour at every DST fall-back, making two distinct builds compare equal; the title is only ever read. * fix(updater): fail the hourly build when the release name is missing The workflow checks out `ref: main`, but a workflow_dispatch runs the workflow file from whatever branch was dispatched. A branch that edits this step while main still carries the old script produces an empty name and an untitled release — silent, and only visible once someone opens the releases page. Verified by hitting exactly that on run 30665586904. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
73 lines
3.3 KiB
JavaScript
73 lines
3.3 KiB
JavaScript
import { describe, expect, it } from 'vitest'
|
|
import { createHourlyBuildVersion, formatHourlyReleaseName } from './hourly-build-version.mjs'
|
|
import { compareAppVersions } from '../../src/shared/app-version'
|
|
|
|
describe('createHourlyBuildVersion', () => {
|
|
it('stamps the version with a zero-padded UTC timestamp', () => {
|
|
expect(createHourlyBuildVersion('1.4.160', new Date('2026-07-28T04:05:00Z'))).toBe(
|
|
'1.4.160-hourly.202607280405'
|
|
)
|
|
})
|
|
|
|
// Why: main's package.json carries the in-flight RC tail. Keeping it would make
|
|
// every hourly semver-NEWER than the RC it was cut from (1.4.160-rc.3-hourly.X >
|
|
// 1.4.160-rc.3), so an ordinary RC-channel check would offer untested hourly
|
|
// builds to RC users. Dropping it parks hourlies below both rc.N and stable,
|
|
// reachable only by an explicit pinned jump.
|
|
it('drops an in-flight rc tail so hourlies never outrank the rc series', () => {
|
|
const version = createHourlyBuildVersion('1.4.160-rc.3', new Date('2026-07-28T14:00:00Z'))
|
|
expect(version).toBe('1.4.160-hourly.202607281400')
|
|
expect(compareAppVersions(version, '1.4.160-rc.3')).toBeLessThan(0)
|
|
expect(compareAppVersions('1.4.160-rc.3-hourly.202607281400', '1.4.160-rc.3')).toBeGreaterThan(
|
|
0
|
|
)
|
|
})
|
|
|
|
it('rejects invalid input', () => {
|
|
expect(() => createHourlyBuildVersion('nope', new Date())).toThrow(/valid semver/)
|
|
expect(() => createHourlyBuildVersion('1.4.160', new Date('nope'))).toThrow(/invalid/)
|
|
})
|
|
})
|
|
|
|
describe('formatHourlyReleaseName', () => {
|
|
const name = (iso, buildNumber = 1, commit = 'e698241abcde') =>
|
|
formatHourlyReleaseName('1.4.163-hourly.x', buildNumber, commit, new Date(iso))
|
|
|
|
it('renders version, number, Pacific timestamp, and short sha', () => {
|
|
expect(name('2026-07-31T20:54:00Z')).toBe('1.4.163 • 01 • 07-31 13:54 • e698241')
|
|
})
|
|
|
|
// Why both sides of DST: the tag's stamp is UTC and the title is Pacific, so
|
|
// the offset between them is not a constant. A test pinned to one season would
|
|
// pass all summer and start failing in November.
|
|
it('follows the Pacific offset across DST', () => {
|
|
expect(name('2026-01-15T02:30:00Z')).toBe('1.4.163 • 01 • 01-14 18:30 • e698241')
|
|
expect(name('2026-07-31T07:00:00Z')).toBe('1.4.163 • 01 • 07-31 00:00 • e698241')
|
|
})
|
|
|
|
// Why: hour12: false renders midnight as "24" on some ICU builds, which would
|
|
// read as an hour that does not exist and sort oddly beside 00:xx.
|
|
it('renders midnight as 00, never 24', () => {
|
|
expect(name('2026-07-31T07:00:00Z')).toContain(' 00:00 ')
|
|
})
|
|
|
|
it('pads to two digits and grows past them', () => {
|
|
expect(name('2026-07-31T20:54:00Z', 9)).toContain(' • 09 • ')
|
|
expect(name('2026-07-31T20:54:00Z', 42)).toContain(' • 42 • ')
|
|
expect(name('2026-07-31T20:54:00Z', 1234)).toContain(' • 1234 • ')
|
|
})
|
|
|
|
it('rejects a build number that is not a positive integer', () => {
|
|
expect(() => name('2026-07-31T20:54:00Z', 0)).toThrow(/positive integer/)
|
|
expect(() => name('2026-07-31T20:54:00Z', -1)).toThrow(/positive integer/)
|
|
expect(() => name('2026-07-31T20:54:00Z', 1.5)).toThrow(/positive integer/)
|
|
expect(() => name('2026-07-31T20:54:00Z', Number.NaN)).toThrow(/positive integer/)
|
|
})
|
|
|
|
it('rejects an invalid timestamp', () => {
|
|
expect(() => formatHourlyReleaseName('1.4.163', 1, 'abcdefg', new Date('nope'))).toThrow(
|
|
/invalid/
|
|
)
|
|
})
|
|
})
|