feat(release): stamp canary Windows builds as yy.mmdd.hh.mmss
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
// msix-shared — the native quad (the build time) is the package version for
|
||||
// stable and canary both. The git-backed lookup is deterministic here because
|
||||
// node:child_process.execFileSync is mocked.
|
||||
// msix-shared — native package versions come from the immutable build time.
|
||||
// Stable keeps the Store quad; canary uses yy.mmdd.hh.mmss. The git-backed
|
||||
// lookup is deterministic here because node:child_process.execFileSync is mocked.
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import os from 'node:os'
|
||||
@@ -48,7 +48,7 @@ test('explicit stable tag owns the package version, independent of checkout meta
|
||||
}
|
||||
})
|
||||
|
||||
test('a stable tag and a canary stamp the same quad when built together', () => {
|
||||
test('stable and canary use their own native build-time quads', () => {
|
||||
const desktop = makeFakeDesktop('0.27.1')
|
||||
const epoch = Math.floor(Date.UTC(2026, 7, 29, 1, 2, 3) / 1000)
|
||||
execFileSync.mockImplementation((cmd, args) => {
|
||||
@@ -59,7 +59,7 @@ test('a stable tag and a canary stamp the same quad when built together', () =>
|
||||
const stable = msix.appIdentity(desktop, 'v0.27.1')
|
||||
const canary = msix.appIdentity(desktop, 'v0.27.2-canary.20260829010203')
|
||||
assert.equal(stable.version, '2026.5761.123.0')
|
||||
assert.equal(canary.version, stable.version)
|
||||
assert.equal(canary.version, '26.829.1.203')
|
||||
assert.equal(canary.fileVersion, '0.27.2-canary.20260829010203')
|
||||
} finally {
|
||||
fs.rmSync(desktop, { recursive: true, force: true })
|
||||
|
||||
@@ -8,24 +8,31 @@ import { nativeQuad } from '../../../scripts/msix-shared.mjs'
|
||||
// holds 14*60+3 = 843 seconds.
|
||||
const EPOCH = Date.parse('2026-09-22T00:14:03Z') / 1000
|
||||
|
||||
test('a stable release and a canary built at the same instant share one quad', () => {
|
||||
test('stable keeps the store quad while canary uses yy.mmdd.hh.mmss', () => {
|
||||
assert.equal(nativeQuad('v0.21.5', EPOCH), '2026.6336.843.0')
|
||||
assert.equal(nativeQuad('v0.21.4+canary.20260922T001403Z', EPOCH), '2026.6336.843.0')
|
||||
assert.equal(nativeQuad('v0.21.4+canary.20260922T001403Z', EPOCH), '26.922.0.1403')
|
||||
})
|
||||
|
||||
test('a later build sorts above an earlier one, stable or canary', () => {
|
||||
const earlier = nativeQuad('v0.21.5', EPOCH).split('.').map(Number)
|
||||
const later = nativeQuad('v0.21.4+canary.20260922T001404Z', EPOCH + 1).split('.').map(Number)
|
||||
test('a later canary sorts above an earlier canary across a month boundary', () => {
|
||||
const earlier = nativeQuad(
|
||||
'v0.21.4+canary.20260131T235959Z',
|
||||
Date.parse('2026-01-31T23:59:59Z') / 1000
|
||||
).split('.').map(Number)
|
||||
const later = nativeQuad(
|
||||
'v0.21.4+canary.20260201T000000Z',
|
||||
Date.parse('2026-02-01T00:00:00Z') / 1000
|
||||
).split('.').map(Number)
|
||||
const first = later.findIndex((value, index) => value !== earlier[index])
|
||||
assert.ok(first >= 0)
|
||||
assert.ok(later[first] > earlier[first])
|
||||
})
|
||||
|
||||
test('every field stays inside 16 bits and the revision stays zero', () => {
|
||||
test('every field stays inside 16 bits', () => {
|
||||
for (const stamp of ['2026-01-01T00:00:00Z', '2026-12-31T23:59:59Z', '2028-12-31T23:59:59Z']) {
|
||||
const parts = nativeQuad('v0.21.5', Date.parse(stamp) / 1000).split('.').map(Number)
|
||||
assert.equal(parts.length, 4)
|
||||
assert.ok(parts.every(value => value >= 0 && value <= 65535))
|
||||
assert.equal(parts[3], 0)
|
||||
for (const ref of ['v0.21.5', `v0.21.4+canary.${stamp.replace(/[-:T]/g, '').slice(0, 15)}Z`]) {
|
||||
const parts = nativeQuad(ref, Date.parse(stamp) / 1000).split('.').map(Number)
|
||||
assert.equal(parts.length, 4)
|
||||
assert.ok(parts.every(value => value >= 0 && value <= 65535))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -109,18 +109,35 @@ export function storePackageVersionAt(epochSeconds) {
|
||||
return `${year}.${hourOfYear}.${secondOfHour}.0`
|
||||
}
|
||||
|
||||
/** The native quad for a release, stable or canary: ``year.hourOfYear.secondOfHour.0``.
|
||||
/** Canary package quad: ``yy.mmdd.hh.mmss`` in UTC.
|
||||
* @param {number} epochSeconds immutable build time in UTC
|
||||
* @returns {string}
|
||||
*/
|
||||
export function canaryPackageVersionAt(epochSeconds) {
|
||||
const date = new Date(epochSeconds * 1000)
|
||||
if (!Number.isInteger(epochSeconds) || !Number.isFinite(date.getTime())) {
|
||||
throw new Error('Canary package version needs a valid immutable build timestamp')
|
||||
}
|
||||
const yy = date.getUTCFullYear() % 100
|
||||
const mmdd = (date.getUTCMonth() + 1) * 100 + date.getUTCDate()
|
||||
const hh = date.getUTCHours()
|
||||
const mmss = date.getUTCMinutes() * 100 + date.getUTCSeconds()
|
||||
return `${yy}.${mmdd}.${hh}.${mmss}`
|
||||
}
|
||||
|
||||
One derivation for every Windows consumer. The quad is the build time, so a
|
||||
later build always sorts above an earlier one and a canary shares its stable's
|
||||
quad when they are built at the same instant. There is no minutes-since-stable
|
||||
counter, so there is no 45-day cap.
|
||||
@param {string} _ref the release ref; accepted so callers name what they stamp
|
||||
/** The native quad for a release.
|
||||
|
||||
Stable Store packages retain ``year.hourOfYear.secondOfHour.0``. Canary uses
|
||||
``yy.mmdd.hh.mmss`` so its four numeric fields show the UTC build time directly
|
||||
while remaining monotonic across month boundaries.
|
||||
@param {string} ref the release ref whose native package is being stamped
|
||||
@param {number} epochSeconds the build time, UTC
|
||||
@returns {string}
|
||||
*/
|
||||
export function nativeQuad(_ref, epochSeconds) {
|
||||
return storePackageVersionAt(epochSeconds)
|
||||
export function nativeQuad(ref, epochSeconds) {
|
||||
return /(?:\+|-)canary\./.test(ref)
|
||||
? canaryPackageVersionAt(epochSeconds)
|
||||
: storePackageVersionAt(epochSeconds)
|
||||
}
|
||||
|
||||
/** The build time of a release tag: the stamp embedded in a canary tag, or
|
||||
|
||||
@@ -52,7 +52,7 @@ def canary(tmp_path, r2_server, monkeypatch):
|
||||
f"import {{nativeQuad}} from {json.dumps((ROOT / 'scripts/msix-shared.mjs').as_uri())};"
|
||||
f"console.log(nativeQuad({json.dumps(tag)}, Date.parse('2026-09-13T00:10:00Z') / 1000))",
|
||||
], text=True).strip()
|
||||
assert windows_version == "2026.6120.600.0"
|
||||
assert windows_version == "26.913.0.1000"
|
||||
tools = tmp_path / "tools"
|
||||
tools.mkdir()
|
||||
driver = tools / "driver.py"
|
||||
@@ -222,7 +222,7 @@ def test_published_canary_workflow_advances_only_after_every_gate(canary, r2_ser
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
resolved = ChannelReader(env["CLOUDFLARE_R2_PUBLIC_URL"], repository=env["GITHUB_REPOSITORY"]).resolve("canary")
|
||||
assert resolved.manifest is not None
|
||||
assert resolved.manifest["request"]["windowsVersion"] == "2026.6120.600.0"
|
||||
assert resolved.manifest["request"]["windowsVersion"] == "26.913.0.1000"
|
||||
assert resolved.manifest["request"]["commit"] == env["RELEASE_COMMIT"]
|
||||
assert len(resolved.manifest["packages"]) == 4
|
||||
# Bootstrap reads actual receipt-bound artifacts and the promoted native feeds,
|
||||
@@ -264,14 +264,14 @@ def test_published_canary_workflow_advances_only_after_every_gate(canary, r2_ser
|
||||
_git("tag", newer_tag, cwd=clone)
|
||||
_git("push", "origin", newer_tag, cwd=clone)
|
||||
newer = {**env, "RELEASE_TAG": newer_tag, "HERMES_PAYLOAD_TAG": newer_tag,
|
||||
"FIXTURE_WINDOWS_VERSION": "2026.6120.660.0"}
|
||||
"FIXTURE_WINDOWS_VERSION": "26.913.0.1100"}
|
||||
stage_canary(clone, identity, newer, run)
|
||||
release.write_text(json.dumps({**published, "tagName": newer_tag}))
|
||||
result = run(script, **newer)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
advanced = ChannelReader(env["CLOUDFLARE_R2_PUBLIC_URL"], repository=env["GITHUB_REPOSITORY"]).resolve("canary")
|
||||
assert advanced.manifest is not None
|
||||
assert advanced.manifest["request"]["windowsVersion"] == "2026.6120.660.0"
|
||||
assert advanced.manifest["request"]["windowsVersion"] == "26.913.0.1100"
|
||||
assert advanced.terminal["head"]["sequence"] > resolved.terminal["head"]["sequence"]
|
||||
after = dict(r2_server.store)
|
||||
release.write_text(json.dumps(published))
|
||||
|
||||
Reference in New Issue
Block a user