fix(changelog): filter non-app releases
This commit is contained in:
parent
1870be4a81
commit
562edef4dc
|
|
@ -13,6 +13,7 @@ const OUT_EN = "releases-en.json";
|
|||
const LATEST_EN_OUT = "latest-en.json";
|
||||
const LATEST_NOTES_OUT = "latest-notes.json";
|
||||
const EN_CACHE_URL = process.env.CHANGELOG_EN_CACHE_URL || "https://dl.dbxio.com/changelog/releases-en.json";
|
||||
const APP_RELEASE_TAG_PATTERN = /^v[0-9]+[.][0-9]+[.][0-9]+(?:[.-][0-9A-Za-z.-]+)?$/;
|
||||
|
||||
const SECTION_MAP = {
|
||||
新功能: "added",
|
||||
|
|
@ -121,11 +122,16 @@ export function buildReleaseSourceHash(release) {
|
|||
.digest("hex");
|
||||
}
|
||||
|
||||
export function isAppRelease(release) {
|
||||
return !release.draft && !release.prerelease && APP_RELEASE_TAG_PATTERN.test(release.tag_name || "");
|
||||
}
|
||||
|
||||
export function buildReleasesJson(releases, now = new Date()) {
|
||||
return {
|
||||
updatedAt: now.toISOString(),
|
||||
releases: releases
|
||||
.filter((r) => !r.draft && !r.prerelease && !r.tag_name.startsWith("agents-"))
|
||||
// This repository has independent app, agent, and package release streams.
|
||||
.filter(isAppRelease)
|
||||
.sort((a, b) => new Date(b.published_at) - new Date(a.published_at))
|
||||
.map((r) => ({
|
||||
tag: r.tag_name,
|
||||
|
|
@ -159,7 +165,7 @@ function buildLatestEnNotes(enReleasesJson) {
|
|||
|
||||
export function buildLatestReleaseNotes(releases) {
|
||||
const latest = releases
|
||||
.filter((release) => !release.draft && !release.prerelease && !release.tag_name.startsWith("agents-"))
|
||||
.filter(isAppRelease)
|
||||
.sort((a, b) => new Date(b.published_at) - new Date(a.published_at))[0];
|
||||
if (!latest) return null;
|
||||
return { version: latest.tag_name, notes: latest.body || "" };
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { buildLatestReleaseNotes } from "./sync-changelog.mjs";
|
||||
import { buildLatestReleaseNotes, buildReleasesJson } from "./sync-changelog.mjs";
|
||||
|
||||
test("buildLatestReleaseNotes returns the curated latest release body", () => {
|
||||
const result = buildLatestReleaseNotes([
|
||||
|
|
@ -26,3 +26,38 @@ test("buildLatestReleaseNotes returns the curated latest release body", () => {
|
|||
notes: "### 新功能\n- new\n\n### 下载安装\n- assets",
|
||||
});
|
||||
});
|
||||
|
||||
test("changelog excludes agent and package release streams", () => {
|
||||
const releases = [
|
||||
{
|
||||
tag_name: "packages-v0.4.42",
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
published_at: "2026-07-23T02:00:00Z",
|
||||
body: "### Changed\n- packages",
|
||||
},
|
||||
{
|
||||
tag_name: "agents-v0.2.64",
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
published_at: "2026-07-23T01:00:00Z",
|
||||
body: "### Changed\n- agents",
|
||||
},
|
||||
{
|
||||
tag_name: "v0.5.66",
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
published_at: "2026-07-23T00:00:00Z",
|
||||
body: "### Changed\n- app",
|
||||
},
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
buildReleasesJson(releases, new Date("2026-07-24T00:00:00Z")).releases.map((release) => release.tag),
|
||||
["v0.5.66"],
|
||||
);
|
||||
assert.deepEqual(buildLatestReleaseNotes(releases), {
|
||||
version: "v0.5.66",
|
||||
notes: "### Changed\n- app",
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -54,6 +54,39 @@ pub fn normalize_changelog_lang(lang: &str) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_app_release_tag(tag: &str) -> bool {
|
||||
let Some(version) = tag.strip_prefix('v') else {
|
||||
return false;
|
||||
};
|
||||
let mut parts = version.splitn(3, '.');
|
||||
let Some(major) = parts.next() else {
|
||||
return false;
|
||||
};
|
||||
let Some(minor) = parts.next() else {
|
||||
return false;
|
||||
};
|
||||
let Some(patch_and_suffix) = parts.next() else {
|
||||
return false;
|
||||
};
|
||||
if major.is_empty()
|
||||
|| minor.is_empty()
|
||||
|| !major.bytes().all(|byte| byte.is_ascii_digit())
|
||||
|| !minor.bytes().all(|byte| byte.is_ascii_digit())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let patch_len = patch_and_suffix.bytes().take_while(u8::is_ascii_digit).count();
|
||||
if patch_len == 0 {
|
||||
return false;
|
||||
}
|
||||
let suffix = &patch_and_suffix[patch_len..];
|
||||
suffix.is_empty()
|
||||
|| ((suffix.starts_with('.') || suffix.starts_with('-'))
|
||||
&& suffix.len() > 1
|
||||
&& suffix[1..].bytes().all(|byte| byte.is_ascii_alphanumeric() || byte == b'.' || byte == b'-'))
|
||||
}
|
||||
|
||||
pub async fn fetch_changelog(lang: &str) -> Result<ChangelogData, String> {
|
||||
let lang = normalize_changelog_lang(lang);
|
||||
let client = build_changelog_http_client()?;
|
||||
|
|
@ -68,7 +101,8 @@ pub async fn fetch_changelog(lang: &str) -> Result<ChangelogData, String> {
|
|||
.map_err(|e| format!("Failed to fetch changelog: {e}"))?;
|
||||
|
||||
let mut data: ChangelogData = resp.json().await.map_err(|e| format!("Failed to parse changelog: {e}"))?;
|
||||
data.releases.retain(|release| !release.tag.trim().is_empty());
|
||||
// Treat R2 as untrusted cached data so auxiliary release streams never leak into the app UI.
|
||||
data.releases.retain(|release| is_app_release_tag(release.tag.trim()));
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +120,7 @@ fn build_changelog_http_client() -> Result<reqwest::Client, String> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::normalize_changelog_lang;
|
||||
use super::{is_app_release_tag, normalize_changelog_lang};
|
||||
|
||||
#[test]
|
||||
fn normalizes_changelog_lang() {
|
||||
|
|
@ -96,4 +130,14 @@ mod tests {
|
|||
assert_eq!(normalize_changelog_lang("en"), "en");
|
||||
assert_eq!(normalize_changelog_lang("ja"), "en");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognizes_only_app_release_tags() {
|
||||
assert!(is_app_release_tag("v0.5.66"));
|
||||
assert!(is_app_release_tag("v1.2.3-hotfix.1"));
|
||||
assert!(!is_app_release_tag("packages-v0.4.42"));
|
||||
assert!(!is_app_release_tag("agents-v0.2.64"));
|
||||
assert!(!is_app_release_tag("v0.5"));
|
||||
assert!(!is_app_release_tag("v0.5.x"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { test } from "vitest";
|
||||
|
||||
import { isAppReleaseTag } from "./releaseTags";
|
||||
|
||||
test("recognizes only DBX app release tags", () => {
|
||||
assert.equal(isAppReleaseTag("v0.5.66"), true);
|
||||
assert.equal(isAppReleaseTag("v1.2.3-hotfix.1"), true);
|
||||
assert.equal(isAppReleaseTag("packages-v0.4.42"), false);
|
||||
assert.equal(isAppReleaseTag("agents-v0.2.64"), false);
|
||||
assert.equal(isAppReleaseTag("v0.5.x"), false);
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { requestJson } from "@/lib/httpJson";
|
||||
import { isAppReleaseTag } from "@/lib/releaseTags";
|
||||
|
||||
export type ChangelogItem = {
|
||||
title: string;
|
||||
|
|
@ -119,7 +120,8 @@ export async function fetchGitHubChangelog(): Promise<ChangelogData> {
|
|||
return {
|
||||
updatedAt: new Date().toISOString(),
|
||||
releases: releases
|
||||
.filter((release) => !release.draft && !release.prerelease && !release.tag_name.startsWith("agents-"))
|
||||
// GitHub contains separate app, agent, and package release streams.
|
||||
.filter((release) => !release.draft && !release.prerelease && isAppReleaseTag(release.tag_name))
|
||||
.map((release) => ({
|
||||
tag: release.tag_name,
|
||||
name: release.name || release.tag_name,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
const APP_RELEASE_TAG_PATTERN = /^v[0-9]+[.][0-9]+[.][0-9]+(?:[.-][0-9A-Za-z.-]+)?$/;
|
||||
|
||||
export function isAppReleaseTag(tag: string) {
|
||||
return APP_RELEASE_TAG_PATTERN.test(tag);
|
||||
}
|
||||
Loading…
Reference in New Issue