fix: sort alpha releases chronologically
This commit is contained in:
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -42,6 +42,21 @@ jobs:
|
||||
output = subprocess.check_output(command, text=True).strip()
|
||||
return [line for line in output.splitlines() if line]
|
||||
|
||||
alpha_pattern = re.compile(r"^alpha-v(\d{4}\.\d{1,2}\.\d{1,2})-alpha\.(\d+)$")
|
||||
|
||||
def parse_alpha_tag(tag: str) -> tuple[str, int] | None:
|
||||
match = alpha_pattern.fullmatch(tag)
|
||||
if not match:
|
||||
return None
|
||||
calendar_version, sequence = match.groups()
|
||||
return calendar_version, int(sequence)
|
||||
|
||||
def alpha_version(calendar_version: str, sequence: int) -> str:
|
||||
return f"{calendar_version}-alpha.{sequence}"
|
||||
|
||||
def alpha_tag(calendar_version: str, sequence: int) -> str:
|
||||
return f"alpha-v{calendar_version}-alpha.{sequence:04d}"
|
||||
|
||||
existing_tags = [
|
||||
tag for tag in lines(["git", "tag", "--points-at", "HEAD"])
|
||||
if tag.startswith("alpha-v")
|
||||
@@ -49,7 +64,8 @@ jobs:
|
||||
|
||||
if existing_tags:
|
||||
tag = existing_tags[0]
|
||||
version = tag.removeprefix("alpha-v")
|
||||
parsed = parse_alpha_tag(tag)
|
||||
version = alpha_version(*parsed) if parsed is not None else tag.removeprefix("alpha-v")
|
||||
else:
|
||||
today = datetime.now(timezone.utc).date()
|
||||
stable_date = None
|
||||
@@ -71,8 +87,8 @@ jobs:
|
||||
calendar_version = f"{alpha_date.year}.{alpha_date.month}.{alpha_date.day}"
|
||||
sequence = len(lines(["git", "tag", "--list", f"alpha-v{calendar_version}-alpha.*"])) + 1
|
||||
|
||||
version = f"{calendar_version}-alpha.{sequence}"
|
||||
tag = f"alpha-v{version}"
|
||||
version = alpha_version(calendar_version, sequence)
|
||||
tag = alpha_tag(calendar_version, sequence)
|
||||
|
||||
display_match = re.fullmatch(r"(\d{4})\.(\d{1,2})\.(\d{1,2})-alpha\.(\d+)", version)
|
||||
display_version = (
|
||||
@@ -275,7 +291,27 @@ jobs:
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
PREV_TAG=$(git tag --list 'alpha-v*' --sort=-version:refname | head -n 1 || echo "")
|
||||
PREV_TAG=$(python3 <<'PY'
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
current_tag = '${{ needs.version.outputs.tag }}'
|
||||
pattern = re.compile(r'^alpha-v(\d{4})\.(\d{1,2})\.(\d{1,2})-alpha\.(\d+)$')
|
||||
|
||||
output = subprocess.check_output(['git', 'tag', '--list', 'alpha-v*'], text=True).strip()
|
||||
tags = [line for line in output.splitlines() if line and line != current_tag]
|
||||
|
||||
parsed_tags = []
|
||||
for tag in tags:
|
||||
match = pattern.fullmatch(tag)
|
||||
if not match:
|
||||
continue
|
||||
year, month, day, sequence = map(int, match.groups())
|
||||
parsed_tags.append(((year, month, day, sequence), tag))
|
||||
|
||||
print(max(parsed_tags)[1] if parsed_tags else '')
|
||||
PY
|
||||
)
|
||||
if [ -z "$PREV_TAG" ]; then
|
||||
NOTES=$(git log --oneline --no-merges -20)
|
||||
else
|
||||
|
||||
@@ -669,6 +669,6 @@ Managed by `useSettings` hook and `SettingsPanel` component. `default_ai_agent`
|
||||
- **`download_and_install_app_update`** — Channel-aware download/install with streamed progress events.
|
||||
|
||||
### CI/CD
|
||||
- **`.github/workflows/release.yml`** — Alpha prereleases from every push to `main` using calendar-semver technical versions (`YYYY.M.D-alpha.N`) and clean `Alpha YYYY.M.D.N` release names. Publishes `alpha/latest.json` and refreshes the legacy `latest.json` / `latest-canary.json` aliases to the alpha feed.
|
||||
- **`.github/workflows/release.yml`** — Alpha prereleases from every push to `main` using calendar-semver technical versions (`YYYY.M.D-alpha.N`) and clean `Alpha YYYY.M.D.N` release names. GitHub alpha tags zero-pad the prerelease sequence (`alpha-vYYYY.M.D-alpha.NNNN`) so GitHub release ordering stays chronological while the shipped app version remains `YYYY.M.D-alpha.N`. Publishes `alpha/latest.json` and refreshes the legacy `latest.json` / `latest-canary.json` aliases to the alpha feed.
|
||||
- **`.github/workflows/release-stable.yml`** — Stable releases from `stable-vYYYY.M.D` tags. Publishes `stable/latest.json`.
|
||||
- **Beta cohorts** are handled in PostHog targeting only. There is no beta updater feed.
|
||||
|
||||
@@ -782,6 +782,7 @@ Every push to `main` triggers `.github/workflows/release.yml`:
|
||||
```
|
||||
push to main
|
||||
→ version job: compute calendar alpha version YYYY.M.D-alpha.N
|
||||
and a GitHub-sorted tag alpha-vYYYY.M.D-alpha.NNNN
|
||||
→ use today's UTC date unless the latest stable-vYYYY.M.D tag already uses today
|
||||
→ if stable already uses today, advance alpha to the next calendar day so semver still increases
|
||||
→ build job:
|
||||
@@ -789,7 +790,7 @@ push to main
|
||||
→ upload signed .app.tar.gz + .sig updater artifacts
|
||||
→ release job:
|
||||
→ generate alpha-latest.json
|
||||
→ publish GitHub prerelease alpha-v<version> named Tolaria Alpha YYYY.M.D.N
|
||||
→ publish GitHub prerelease alpha-vYYYY.M.D-alpha.NNNN named Tolaria Alpha YYYY.M.D.N
|
||||
→ pages job:
|
||||
→ build static HTML release history page
|
||||
→ publish alpha/latest.json
|
||||
@@ -819,7 +820,7 @@ push stable-vYYYY.M.D tag
|
||||
### Versioning
|
||||
|
||||
- Stable promotions use git tags in the form `stable-vYYYY.M.D` and stamp the technical version `YYYY.M.D`.
|
||||
- Alpha builds stamp the technical version `YYYY.M.D-alpha.N` and display it as `Alpha YYYY.M.D.N`.
|
||||
- Alpha builds stamp the technical version `YYYY.M.D-alpha.N` and display it as `Alpha YYYY.M.D.N`. The GitHub release tag zero-pads the sequence as `alpha-vYYYY.M.D-alpha.NNNN` so GitHub release ordering remains chronological.
|
||||
- If the latest stable tag already uses today's date, alpha advances to the next calendar day before assigning `-alpha.N` so Alpha remains semver-newer than Stable across channel switches.
|
||||
- The workflows stamp the computed version into `tauri.conf.json` and `Cargo.toml` at build time.
|
||||
- This keeps display strings clean while preserving semver monotonicity when a user switches between Stable and Alpha.
|
||||
|
||||
@@ -60,6 +60,35 @@ describe('buildReleaseHistoryPage', () => {
|
||||
expect(html).toContain('<p>First paragraph<br>with a line break.</p><p>Second paragraph</p>')
|
||||
})
|
||||
|
||||
it('sorts releases within each channel by published date descending even when the payload order is wrong', () => {
|
||||
const html = buildReleaseHistoryPage([
|
||||
{
|
||||
body: 'Older alpha release',
|
||||
name: 'Tolaria Alpha 2026.4.20.9',
|
||||
prerelease: true,
|
||||
published_at: '2026-04-20T09:44:02Z',
|
||||
tag_name: 'alpha-v2026.4.20-alpha.9',
|
||||
},
|
||||
{
|
||||
body: 'Newest alpha release',
|
||||
name: 'Tolaria Alpha 2026.4.20.12',
|
||||
prerelease: true,
|
||||
published_at: '2026-04-20T16:53:41Z',
|
||||
tag_name: 'alpha-v2026.4.20-alpha.12',
|
||||
},
|
||||
{
|
||||
body: 'Middle alpha release',
|
||||
name: 'Tolaria Alpha 2026.4.20.10',
|
||||
prerelease: true,
|
||||
published_at: '2026-04-20T10:32:01Z',
|
||||
tag_name: 'alpha-v2026.4.20-alpha.10',
|
||||
},
|
||||
])
|
||||
|
||||
expect(html.indexOf('Tolaria Alpha 2026.4.20.12')).toBeLessThan(html.indexOf('Tolaria Alpha 2026.4.20.10'))
|
||||
expect(html.indexOf('Tolaria Alpha 2026.4.20.10')).toBeLessThan(html.indexOf('Tolaria Alpha 2026.4.20.9'))
|
||||
})
|
||||
|
||||
it('filters draft releases and shows an empty state for channels without published builds', () => {
|
||||
const html = buildReleaseHistoryPage([
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ type ReleaseEntry = {
|
||||
githubUrl: string | null
|
||||
notesHtml: string
|
||||
publishedLabel: string
|
||||
publishedTimestamp: number
|
||||
tagName: string
|
||||
title: string
|
||||
}
|
||||
@@ -419,6 +420,15 @@ function formatPublishedLabel(value: unknown): string {
|
||||
})
|
||||
}
|
||||
|
||||
function parsePublishedTimestamp(value: unknown): number {
|
||||
const text = normalizeText(value)
|
||||
if (text === null) return Number.NEGATIVE_INFINITY
|
||||
|
||||
const publishedAt = new Date(text)
|
||||
const timestamp = publishedAt.getTime()
|
||||
return Number.isNaN(timestamp) ? Number.NEGATIVE_INFINITY : timestamp
|
||||
}
|
||||
|
||||
function isDownloadableAsset(name: string): boolean {
|
||||
return name.endsWith('.dmg') || name.endsWith('.app.tar.gz') || name.endsWith('.zip')
|
||||
}
|
||||
@@ -471,6 +481,7 @@ function normalizeReleaseEntry(release: GitHubReleasePayload): [ReleaseChannel,
|
||||
githubUrl: normalizeUrl(release.html_url),
|
||||
notesHtml: resolveReleaseNotesHtml(release.body_html, release.body),
|
||||
publishedLabel: formatPublishedLabel(release.published_at),
|
||||
publishedTimestamp: parsePublishedTimestamp(release.published_at),
|
||||
tagName,
|
||||
title,
|
||||
}]
|
||||
@@ -490,6 +501,10 @@ function collectReleaseSections(payload: unknown): ReleaseSections {
|
||||
sections[channel].push(release)
|
||||
}
|
||||
|
||||
for (const channel of ['stable', 'alpha'] as const) {
|
||||
sections[channel].sort((left, right) => right.publishedTimestamp - left.publishedTimestamp)
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user