mirror of
https://github.com/linshenkx/prompt-optimizer.git
synced 2026-05-07 14:06:53 +08:00
399 lines
13 KiB
JavaScript
399 lines
13 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import releaseNotes from './release-notes.js';
|
|
|
|
const {
|
|
buildCommitDraft,
|
|
buildReleaseNotesTemplate,
|
|
createReleaseNotesFiles,
|
|
renderGitHubReleaseBody,
|
|
validateReleaseArtifacts,
|
|
} = releaseNotes;
|
|
|
|
function createTempRepo() {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'prompt-optimizer-release-notes-'));
|
|
}
|
|
|
|
function writeFile(root, relativePath, content) {
|
|
const filePath = path.join(root, relativePath);
|
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
fs.writeFileSync(filePath, content);
|
|
}
|
|
|
|
function runGit(root, args) {
|
|
return execFileSync('git', args, {
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim();
|
|
}
|
|
|
|
function commitFile(root, relativePath, content, message) {
|
|
writeFile(root, relativePath, content);
|
|
runGit(root, ['add', relativePath]);
|
|
runGit(root, ['commit', '-m', message]);
|
|
}
|
|
|
|
function createTaggedRepo() {
|
|
const root = createTempRepo();
|
|
runGit(root, ['init']);
|
|
runGit(root, ['config', 'user.name', 'Test User']);
|
|
runGit(root, ['config', 'user.email', 'test@example.com']);
|
|
|
|
commitFile(root, 'notes.txt', 'first\n', 'chore: initial release');
|
|
runGit(root, ['tag', 'v1.0.0']);
|
|
|
|
commitFile(root, 'notes.txt', 'second\n', 'feat: add second change');
|
|
runGit(root, ['tag', 'v1.1.0']);
|
|
|
|
commitFile(root, 'notes.txt', 'third\n', 'fix: add third change');
|
|
runGit(root, ['tag', 'v1.2.0']);
|
|
|
|
return root;
|
|
}
|
|
|
|
function buildValidEnglishReleaseNotes(version, options = {}) {
|
|
const summary =
|
|
options.includeSummary === false
|
|
? ''
|
|
: `## Summary
|
|
- Text-to-image evaluation is now easier to review and compare.
|
|
- Reference-image workflows are smoother and safer across the app.
|
|
|
|
`;
|
|
|
|
return `# Prompt Optimizer v${version}
|
|
|
|
${summary}## Highlights
|
|
- Faster image prompt evaluation with clearer outputs.
|
|
|
|
## Product Updates
|
|
### Desktop
|
|
- Improved packaging metadata for GitHub Releases.
|
|
### Web
|
|
- Refined release surfaces for bilingual documentation.
|
|
### Extension
|
|
- Synced release messaging with the desktop workflow.
|
|
### Core/Infra
|
|
- Added release note validation for tagged builds.
|
|
|
|
## Fixes
|
|
- Prevented empty release bodies from being published.
|
|
|
|
## Breaking Changes / Upgrade Notes
|
|
- None.
|
|
|
|
## Developer Notes
|
|
- Release notes now live in the repository and sync to GitHub Releases.
|
|
`;
|
|
}
|
|
|
|
function buildValidChineseReleaseNotes(version, options = {}) {
|
|
const summary =
|
|
options.includeSummary === false
|
|
? ''
|
|
: `## 概括
|
|
- 文生图评估现在更容易查看和比较。
|
|
- 参考图工作流在整个应用里都更顺手、更安全。
|
|
|
|
`;
|
|
|
|
return `# Prompt Optimizer v${version}
|
|
|
|
${summary}## 亮点
|
|
- 图像提示词评估更快,结果表达也更清晰。
|
|
|
|
## 产品更新
|
|
### Desktop
|
|
- 改进了 GitHub Release 使用的桌面端打包元数据。
|
|
### Web
|
|
- 优化了双语发布文档的展示入口。
|
|
### Extension
|
|
- 让扩展端发布说明与桌面端流程保持一致。
|
|
### Core/Infra
|
|
- 为打标签发布增加了版本说明校验。
|
|
|
|
## 修复
|
|
- 避免发布出空的 Release 正文。
|
|
|
|
## 破坏性变更 / 升级说明
|
|
- 无。
|
|
|
|
## 开发者说明
|
|
- 版本说明现在以仓库文件为准,并同步到 GitHub Releases。
|
|
`;
|
|
}
|
|
|
|
test('buildReleaseNotesTemplate creates the English release note skeleton with summary', () => {
|
|
const template = buildReleaseNotesTemplate({
|
|
version: '2.8.0',
|
|
locale: 'en',
|
|
commitDraft: ['- feat(ui): polish release notes', '- fix(ci): validate note files'],
|
|
});
|
|
|
|
assert.match(template, /^# Prompt Optimizer v2\.8\.0/m);
|
|
assert.match(template, /^## Summary$/m);
|
|
assert.match(template, /^## Highlights$/m);
|
|
assert.match(template, /^### Desktop$/m);
|
|
assert.match(template, /^## Developer Notes$/m);
|
|
assert.match(template, /feat\(ui\): polish release notes/);
|
|
});
|
|
|
|
test('buildReleaseNotesTemplate creates the Chinese release note skeleton with 概括', () => {
|
|
const template = buildReleaseNotesTemplate({
|
|
version: '2.8.0',
|
|
locale: 'zh-CN',
|
|
commitDraft: ['- feat(core): ship bilingual release notes'],
|
|
});
|
|
|
|
assert.match(template, /^# Prompt Optimizer v2\.8\.0/m);
|
|
assert.match(template, /^## 概括$/m);
|
|
assert.match(template, /^## 亮点$/m);
|
|
assert.match(template, /^### Desktop$/m);
|
|
assert.match(template, /^## 开发者说明$/m);
|
|
assert.match(template, /feat\(core\): ship bilingual release notes/);
|
|
});
|
|
|
|
test('createReleaseNotesFiles writes split English and Chinese release note files', () => {
|
|
const root = createTempRepo();
|
|
|
|
const result = createReleaseNotesFiles({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
commitDraft: ['- feat(core): ship bilingual release notes'],
|
|
});
|
|
|
|
assert.equal(result.created, true);
|
|
assert.equal(result.filePaths.en, path.join(root, 'releases', 'v2.8.0.en.md'));
|
|
assert.equal(result.filePaths.zhCN, path.join(root, 'releases', 'v2.8.0.zh-CN.md'));
|
|
assert.equal(fs.existsSync(result.filePaths.en), true);
|
|
assert.equal(fs.existsSync(result.filePaths.zhCN), true);
|
|
});
|
|
|
|
test('validateReleaseArtifacts accepts split release notes with summaries and changelog links', () => {
|
|
const root = createTempRepo();
|
|
writeFile(
|
|
root,
|
|
'CHANGELOG.md',
|
|
[
|
|
'# Changelog',
|
|
'',
|
|
'## [2.8.0] - 2026-04-04',
|
|
'- EN: Bilingual release notes become the source of truth. See [Release Notes (EN)](releases/v2.8.0.en.md).',
|
|
'- 中文:双语版本说明成为唯一发布来源。参见 [版本说明(中文)](releases/v2.8.0.zh-CN.md)。',
|
|
'',
|
|
'## [2.1.0] - 2025-01-19',
|
|
'- Legacy entry kept for compatibility.',
|
|
'',
|
|
].join('\n')
|
|
);
|
|
writeFile(root, 'releases/v2.8.0.en.md', buildValidEnglishReleaseNotes('2.8.0'));
|
|
writeFile(root, 'releases/v2.8.0.zh-CN.md', buildValidChineseReleaseNotes('2.8.0'));
|
|
|
|
const result = validateReleaseArtifacts({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.deepEqual(result.errors, []);
|
|
});
|
|
|
|
test('validateReleaseArtifacts ignores no-change desktop and extension subsections for product order', () => {
|
|
const root = createTempRepo();
|
|
writeFile(
|
|
root,
|
|
'CHANGELOG.md',
|
|
[
|
|
'# Changelog',
|
|
'',
|
|
'## [2.8.0] - 2026-04-04',
|
|
'- EN: Bilingual release notes become the source of truth. See [Release Notes (EN)](releases/v2.8.0.en.md).',
|
|
'- 中文:双语版本说明成为唯一发布来源。参见 [版本说明(中文)](releases/v2.8.0.zh-CN.md)。',
|
|
'',
|
|
].join('\n')
|
|
);
|
|
writeFile(
|
|
root,
|
|
'releases/v2.8.0.en.md',
|
|
buildValidEnglishReleaseNotes('2.8.0')
|
|
.replace(
|
|
[
|
|
'### Desktop',
|
|
'- Improved packaging metadata for GitHub Releases.',
|
|
'### Web',
|
|
'- Refined release surfaces for bilingual documentation.',
|
|
'### Extension',
|
|
'- Synced release messaging with the desktop workflow.',
|
|
'### Core/Infra',
|
|
].join('\n'),
|
|
[
|
|
'### Web',
|
|
'- Refined release surfaces for bilingual documentation.',
|
|
'### Extension',
|
|
'- No extension-specific user-facing changes landed in this patch release.',
|
|
'### Desktop',
|
|
'- No desktop-specific user-facing changes landed in this patch release.',
|
|
'### Core/Infra',
|
|
].join('\n')
|
|
)
|
|
);
|
|
writeFile(
|
|
root,
|
|
'releases/v2.8.0.zh-CN.md',
|
|
buildValidChineseReleaseNotes('2.8.0')
|
|
.replace(
|
|
[
|
|
'### Desktop',
|
|
'- 改进了 GitHub Release 使用的桌面端打包元数据。',
|
|
'### Web',
|
|
'- 优化了双语发布文档的展示入口。',
|
|
'### Extension',
|
|
'- 让扩展端发布说明与桌面端流程保持一致。',
|
|
'### Core/Infra',
|
|
].join('\n'),
|
|
[
|
|
'### Web',
|
|
'- 优化了双语发布文档的展示入口。',
|
|
'### Extension',
|
|
'- 本次补丁没有扩展端专属的用户可见变化。',
|
|
'### Desktop',
|
|
'- 本次补丁没有桌面端专属的用户可见变化。',
|
|
'### Core/Infra',
|
|
].join('\n')
|
|
)
|
|
);
|
|
|
|
const result = validateReleaseArtifacts({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
});
|
|
|
|
assert.equal(result.ok, true);
|
|
assert.deepEqual(result.errors, []);
|
|
});
|
|
|
|
test('validateReleaseArtifacts blocks release when a language file or summary is missing', () => {
|
|
const root = createTempRepo();
|
|
writeFile(
|
|
root,
|
|
'CHANGELOG.md',
|
|
[
|
|
'# Changelog',
|
|
'',
|
|
'## [2.8.0] - 2026-04-04',
|
|
'- EN: Missing the Chinese file link.',
|
|
'- 中文:这里只有概括,没有双语文件链接。',
|
|
'',
|
|
].join('\n')
|
|
);
|
|
writeFile(root, 'releases/v2.8.0.en.md', buildValidEnglishReleaseNotes('2.8.0'));
|
|
writeFile(
|
|
root,
|
|
'releases/v2.8.0.zh-CN.md',
|
|
buildValidChineseReleaseNotes('2.8.0', { includeSummary: false }).replace('无。', 'TODO')
|
|
);
|
|
|
|
const result = validateReleaseArtifacts({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
});
|
|
|
|
assert.equal(result.ok, false);
|
|
assert.match(result.errors.join('\n'), /releases\/v2\.8\.0\.en\.md/);
|
|
assert.match(result.errors.join('\n'), /releases\/v2\.8\.0\.zh-CN\.md/);
|
|
assert.match(result.errors.join('\n'), /"## 概括"/);
|
|
assert.match(result.errors.join('\n'), /placeholder content/i);
|
|
});
|
|
|
|
test('validateReleaseArtifacts can validate a matching non-top changelog entry for historical backfills', () => {
|
|
const root = createTempRepo();
|
|
writeFile(
|
|
root,
|
|
'CHANGELOG.md',
|
|
[
|
|
'# Changelog',
|
|
'',
|
|
'## [2.8.0] - 2026-04-04',
|
|
'- EN: Current release stays on top. See [Release Notes (EN)](releases/v2.8.0.en.md).',
|
|
'- 中文:当前版本仍然位于顶部。参见 [版本说明(中文)](releases/v2.8.0.zh-CN.md)。',
|
|
'',
|
|
'## [2.6.0] - 2026-03-09',
|
|
'- EN: Historical backfill for MiniMax support. See [Release Notes (EN)](releases/v2.6.0.en.md).',
|
|
'- 中文:为 MiniMax 支持补齐历史版本说明。参见 [版本说明(中文)](releases/v2.6.0.zh-CN.md)。',
|
|
'',
|
|
].join('\n')
|
|
);
|
|
writeFile(root, 'releases/v2.6.0.en.md', buildValidEnglishReleaseNotes('2.6.0'));
|
|
writeFile(root, 'releases/v2.6.0.zh-CN.md', buildValidChineseReleaseNotes('2.6.0'));
|
|
|
|
const blockingResult = validateReleaseArtifacts({
|
|
cwd: root,
|
|
version: '2.6.0',
|
|
});
|
|
assert.equal(blockingResult.ok, false);
|
|
assert.match(blockingResult.errors.join('\n'), /top entry must be version 2\.6\.0/);
|
|
|
|
const historicalResult = validateReleaseArtifacts({
|
|
cwd: root,
|
|
version: '2.6.0',
|
|
requireTopEntry: false,
|
|
});
|
|
assert.equal(historicalResult.ok, true);
|
|
assert.deepEqual(historicalResult.errors, []);
|
|
});
|
|
|
|
test('renderGitHubReleaseBody renders English first, then Chinese, with macOS note and guide links', () => {
|
|
const root = createTempRepo();
|
|
writeFile(root, 'releases/v2.8.0.en.md', buildValidEnglishReleaseNotes('2.8.0'));
|
|
writeFile(root, 'releases/v2.8.0.zh-CN.md', buildValidChineseReleaseNotes('2.8.0'));
|
|
|
|
const body = renderGitHubReleaseBody({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
repository: 'linshenkx/prompt-optimizer',
|
|
});
|
|
|
|
assert.match(body, /^## English$/m);
|
|
assert.match(body, /^### Summary$/m);
|
|
assert.match(body, /^### macOS Security Note$/m);
|
|
assert.match(body, /^Installation guide: \[English\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/mkdocs\/docs\/en\/deployment\/desktop\.md\) \| \[中文\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/mkdocs\/docs\/zh\/deployment\/desktop\.md\)$/m);
|
|
assert.match(body, /\[Full release notes \(EN\)\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/releases\/v2\.8\.0\.en\.md\)/);
|
|
assert.match(body, /^---$/m);
|
|
assert.match(body, /^## 中文$/m);
|
|
assert.match(body, /^### 概括$/m);
|
|
assert.match(body, /^### macOS 安全提示$/m);
|
|
assert.match(body, /^安装文档:\[English\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/mkdocs\/docs\/en\/deployment\/desktop\.md\) \| \[中文\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/mkdocs\/docs\/zh\/deployment\/desktop\.md\)$/m);
|
|
assert.match(body, /\[完整发布说明(中文)\]\(https:\/\/github\.com\/linshenkx\/prompt-optimizer\/blob\/v2\.8\.0\/releases\/v2\.8\.0\.zh-CN\.md\)/);
|
|
});
|
|
|
|
test('renderGitHubReleaseBody falls back to full text when summaries are absent', () => {
|
|
const root = createTempRepo();
|
|
writeFile(root, 'releases/v2.8.0.en.md', buildValidEnglishReleaseNotes('2.8.0', { includeSummary: false }));
|
|
writeFile(root, 'releases/v2.8.0.zh-CN.md', buildValidChineseReleaseNotes('2.8.0', { includeSummary: false }));
|
|
|
|
const body = renderGitHubReleaseBody({
|
|
cwd: root,
|
|
version: '2.8.0',
|
|
repository: 'linshenkx/prompt-optimizer',
|
|
});
|
|
|
|
assert.match(body, /^# Prompt Optimizer v2\.8\.0/m);
|
|
assert.doesNotMatch(body, /^\[Full release notes \(EN\)\]/m);
|
|
});
|
|
|
|
test('buildCommitDraft uses the adjacent older tag for historical tagged versions', () => {
|
|
const root = createTaggedRepo();
|
|
|
|
const draft = buildCommitDraft(root, '1.1.0');
|
|
|
|
assert.equal(draft[0], '- Range: v1.0.0..v1.1.0');
|
|
assert.equal(draft.length, 2);
|
|
assert.match(draft[1], /^- feat: add second change \([0-9a-f]+\)$/);
|
|
});
|