源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
buildFallbackReleaseSummary,
|
||||
classifyCommit,
|
||||
normalizeSectionTitle,
|
||||
normalizeVersion,
|
||||
resolveChannel
|
||||
} from "../../apps/desktop/scripts/generate-release-summary.mjs";
|
||||
import {
|
||||
GITHUB_RELEASE_BODY_MAX_LENGTH,
|
||||
RELEASE_NOTES_TRUNCATION_NOTICE
|
||||
} from "../../apps/desktop/scripts/lib/githubReleaseBody.mjs";
|
||||
import { resolvePreviousReleaseTag } from "../../apps/desktop/scripts/lib/previousReleaseTag.mjs";
|
||||
import {
|
||||
REVIEW_END,
|
||||
REVIEW_START,
|
||||
SECTION_END,
|
||||
SECTION_START,
|
||||
SUGGESTION_START,
|
||||
buildApprovedReleaseSummary,
|
||||
buildUpdatedReleaseBody
|
||||
} from "../../apps/desktop/scripts/upsert-release-summary.mjs";
|
||||
import { validateReleaseSummary } from "../../apps/desktop/scripts/validate-release-summary.mjs";
|
||||
|
||||
function validReleaseSummary(overrides = {}) {
|
||||
return {
|
||||
schemaVersion: "tutti.desktop.release.summary.v1",
|
||||
tag: "v1.2.4-rc.1",
|
||||
version: "1.2.4-rc.1",
|
||||
channel: "rc",
|
||||
prerelease: true,
|
||||
targetCommit: "0123456789abcdef",
|
||||
compare: { from: "v1.2.4-rc.0", range: "v1.2.4-rc.0..HEAD", to: "HEAD" },
|
||||
generatedAt: "2026-07-21T00:00:00.000Z",
|
||||
summarySource: "fallback",
|
||||
zh: {
|
||||
headline: "本次版本优化发布流程。",
|
||||
sections: [{ title: "发布与更新", items: ["验证候选版本。"] }],
|
||||
qaFocus: ["验证更新。"]
|
||||
},
|
||||
en: {
|
||||
headline: "This release improves the release flow.",
|
||||
sections: [
|
||||
{ title: "Release and Updates", items: ["Validate the candidate."] }
|
||||
],
|
||||
qaFocus: ["Verify updates."]
|
||||
},
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
test("desktop release summary classifies commit messages for human sections", () => {
|
||||
assert.equal(
|
||||
classifyCommit("abc1234 feat(update): add channel picker"),
|
||||
"功能变更"
|
||||
);
|
||||
assert.equal(
|
||||
classifyCommit("abc1234 fix(release): avoid rc latest"),
|
||||
"问题修复"
|
||||
);
|
||||
assert.equal(
|
||||
classifyCommit("abc1234 chore(release): update desktop workflow"),
|
||||
"发布与更新"
|
||||
);
|
||||
});
|
||||
|
||||
test("desktop release summary maps technical headings to user-facing sections", () => {
|
||||
assert.equal(normalizeSectionTitle("核心功能与架构", "zh"), "功能变更");
|
||||
assert.equal(normalizeSectionTitle("后端与服务端改进", "zh"), "体验优化");
|
||||
assert.equal(
|
||||
normalizeSectionTitle("Core Features & Architecture", "en"),
|
||||
"Feature Updates"
|
||||
);
|
||||
assert.equal(
|
||||
normalizeSectionTitle("Backend & Service Enhancements", "en"),
|
||||
"Experience Improvements"
|
||||
);
|
||||
});
|
||||
|
||||
test("desktop release summary resolves channel from version shape", () => {
|
||||
assert.equal(normalizeVersion("v1.2.4"), "1.2.4");
|
||||
assert.equal(normalizeVersion("tutti-desktop-v1.2.4-rc.1"), "1.2.4-rc.1");
|
||||
assert.equal(normalizeVersion("tutti-desktop-v1.2.4-beta.1"), "1.2.4-beta.1");
|
||||
assert.equal(resolveChannel({ version: "1.2.4" }), "stable");
|
||||
assert.equal(resolveChannel({ version: "1.2.4-rc.1" }), "rc");
|
||||
assert.equal(resolveChannel({ version: "1.2.4-beta.1" }), "beta");
|
||||
});
|
||||
|
||||
test("GitHub release notes compare prereleases against the latest same-channel tag", () => {
|
||||
const previousTag = resolvePreviousReleaseTag({
|
||||
channel: "rc",
|
||||
tag: "v0.2.2-rc.9",
|
||||
tags: ["v0.2.2-rc.9", "v0.2.2-rc.8", "v0.2.2-beta.5", "v0.2.0"],
|
||||
version: "0.2.2-rc.9"
|
||||
});
|
||||
|
||||
assert.equal(previousTag, "v0.2.2-rc.8");
|
||||
});
|
||||
|
||||
test("GitHub release notes fall back to the latest stable tag", () => {
|
||||
const previousTag = resolvePreviousReleaseTag({
|
||||
channel: "beta",
|
||||
tag: "v0.3.0-beta.0",
|
||||
tags: ["v0.3.0-beta.0", "v0.2.2-rc.8", "v0.2.1", "v0.2.0"],
|
||||
version: "0.3.0-beta.0"
|
||||
});
|
||||
|
||||
assert.equal(previousTag, "v0.2.1");
|
||||
});
|
||||
|
||||
test("desktop release summary fallback emits zh and en sections", () => {
|
||||
const summary = buildFallbackReleaseSummary({
|
||||
commits: [
|
||||
"abc1234 feat(update): add release channel picker",
|
||||
"def5678 fix(release): keep rc out of latest metadata"
|
||||
]
|
||||
});
|
||||
|
||||
assert.equal(summary.source, "fallback");
|
||||
assert.match(summary.zh.headline, /桌面端/);
|
||||
assert.ok(summary.zh.sections.length >= 2);
|
||||
assert.ok(summary.en.sections.length >= 2);
|
||||
});
|
||||
|
||||
test("desktop release summary creates editable review and replaceable suggestion sections", () => {
|
||||
const nextBody = buildUpdatedReleaseBody({
|
||||
existingBody: [
|
||||
"## What's Changed",
|
||||
"- Raw GitHub note",
|
||||
"",
|
||||
SECTION_START,
|
||||
"old summary",
|
||||
SECTION_END
|
||||
].join("\n"),
|
||||
summary: {
|
||||
zh: {
|
||||
headline: "本次版本聚焦发布链路稳定性。",
|
||||
sections: [
|
||||
{ title: "发布与更新", items: ["稳定包入口只指向正式版。"] }
|
||||
],
|
||||
qaFocus: ["验证下载入口。"]
|
||||
},
|
||||
en: {
|
||||
headline: "This release focuses on release stability.",
|
||||
sections: [
|
||||
{
|
||||
title: "Release and Updates",
|
||||
items: ["Stable downloads only point to official releases."]
|
||||
}
|
||||
],
|
||||
qaFocus: ["Verify the download entry."]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assert.equal(nextBody.match(new RegExp(REVIEW_START, "g"))?.length, 1);
|
||||
assert.equal(nextBody.match(new RegExp(SUGGESTION_START, "g"))?.length, 1);
|
||||
assert.match(nextBody, /本次版本聚焦发布链路稳定性/);
|
||||
assert.match(nextBody, /Stable downloads only point to official releases/);
|
||||
assert.match(nextBody, /Release Notes/);
|
||||
assert.doesNotMatch(nextBody, /QA Focus/);
|
||||
assert.doesNotMatch(nextBody, /Verify the download entry/);
|
||||
assert.match(nextBody, /Raw GitHub note/);
|
||||
assert.doesNotMatch(nextBody, /old summary/);
|
||||
});
|
||||
|
||||
test("desktop release summary preserves human review when a candidate is rebuilt", () => {
|
||||
const humanReview = [
|
||||
REVIEW_START,
|
||||
"## Release Notes",
|
||||
"### 中文",
|
||||
"人工确认标题",
|
||||
"#### 问题修复",
|
||||
"- 人工确认内容",
|
||||
"### English",
|
||||
"Reviewed headline",
|
||||
"#### Bug Fixes",
|
||||
"- Reviewed item",
|
||||
REVIEW_END
|
||||
].join("\n");
|
||||
const nextBody = buildUpdatedReleaseBody({
|
||||
existingBody: `${humanReview}\n\n## What's Changed\n- keep me`,
|
||||
summary: validReleaseSummary({
|
||||
zh: {
|
||||
headline: "新的机器建议",
|
||||
sections: [{ title: "功能变更", items: ["机器内容"] }],
|
||||
qaFocus: []
|
||||
}
|
||||
})
|
||||
});
|
||||
assert.match(nextBody, /人工确认内容/);
|
||||
assert.match(nextBody, /新的机器建议/);
|
||||
assert.match(nextBody, /keep me/);
|
||||
});
|
||||
|
||||
test("approved summary is parsed from the human review instead of generated suggestions", () => {
|
||||
const body = buildUpdatedReleaseBody({
|
||||
existingBody: "",
|
||||
summary: validReleaseSummary()
|
||||
}).replace("验证候选版本。", "人工筛选后的更新内容");
|
||||
const approved = buildApprovedReleaseSummary({
|
||||
body,
|
||||
generatedSummary: validReleaseSummary(),
|
||||
reviewedAt: "2026-08-17T01:00:00.000Z"
|
||||
});
|
||||
assert.equal(approved.summarySource, "human-reviewed");
|
||||
assert.deepEqual(approved.zh.sections[0].items, ["人工筛选后的更新内容"]);
|
||||
assert.equal(approved.generatedAt, "2026-08-17T01:00:00.000Z");
|
||||
});
|
||||
|
||||
test("approved summary accepts reviewed items written as plain Markdown lines", () => {
|
||||
const body = [
|
||||
REVIEW_START,
|
||||
"## Release Notes",
|
||||
"### 中文",
|
||||
"人工确认标题",
|
||||
"#### 功能变更",
|
||||
"默认开启快捷提示词管理",
|
||||
"支持会话分叉",
|
||||
"### English",
|
||||
"Reviewed headline",
|
||||
"#### Feature Changes",
|
||||
"Quick prompt management is enabled by default",
|
||||
"Conversation forking is supported",
|
||||
REVIEW_END
|
||||
].join("\n");
|
||||
const approved = buildApprovedReleaseSummary({
|
||||
body,
|
||||
generatedSummary: validReleaseSummary()
|
||||
});
|
||||
|
||||
assert.deepEqual(approved.zh.sections[0].items, [
|
||||
"默认开启快捷提示词管理",
|
||||
"支持会话分叉"
|
||||
]);
|
||||
assert.deepEqual(approved.en.sections[0].items, [
|
||||
"Quick prompt management is enabled by default",
|
||||
"Conversation forking is supported"
|
||||
]);
|
||||
});
|
||||
|
||||
test("desktop release summary trims only generated notes at GitHub's body limit", () => {
|
||||
const oversizedNotes = [
|
||||
"## What's Changed",
|
||||
...Array.from(
|
||||
{ length: 2_000 },
|
||||
(_, index) => `- generated note ${index}: ${"x".repeat(80)}`
|
||||
)
|
||||
].join("\n");
|
||||
const nextBody = buildUpdatedReleaseBody({
|
||||
existingBody: oversizedNotes,
|
||||
summary: validReleaseSummary({
|
||||
en: {
|
||||
headline: "A concise release summary.",
|
||||
sections: [{ title: "Bug Fixes", items: ["Kept releases reliable."] }],
|
||||
qaFocus: []
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
assert.ok(nextBody.length <= GITHUB_RELEASE_BODY_MAX_LENGTH);
|
||||
assert.match(nextBody, /A concise release summary/);
|
||||
assert.match(nextBody, /generated note 0:/);
|
||||
assert.doesNotMatch(nextBody, /generated note 1999:/);
|
||||
assert.ok(nextBody.includes(RELEASE_NOTES_TRUNCATION_NOTICE));
|
||||
});
|
||||
|
||||
test("desktop release summary validator accepts a complete staged summary", () => {
|
||||
const summary = validReleaseSummary();
|
||||
assert.equal(
|
||||
validateReleaseSummary(summary, {
|
||||
tag: summary.tag,
|
||||
channel: summary.channel,
|
||||
targetCommit: summary.targetCommit
|
||||
}),
|
||||
summary
|
||||
);
|
||||
});
|
||||
|
||||
test("desktop release summary validator accepts a human-reviewed summary", () => {
|
||||
assert.doesNotThrow(() =>
|
||||
validateReleaseSummary(
|
||||
validReleaseSummary({ summarySource: "human-reviewed" })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test("desktop release summary validator rejects inconsistent or incomplete summaries", () => {
|
||||
assert.throws(
|
||||
() => validateReleaseSummary(validReleaseSummary({ prerelease: false })),
|
||||
/prerelease flag/
|
||||
);
|
||||
assert.throws(
|
||||
() => validateReleaseSummary(validReleaseSummary({ en: { headline: "" } })),
|
||||
/en.headline/
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user