build(release): add repo-driven bilingual release notes

This commit is contained in:
linshen committed 2026-04-05 08:24:26 +08:00
1 parent b9cd3cd9d5
commit bd546ebe0f
24 files changed
+1799 -189

No files matched your search

+40 -183
View File
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
inputs:
version:
description: '版本号(例如: v2.0.3)'
description: '当前所选 ref 上待发布的版本号(例如: v2.8.0)'
required: true
type: string
@@ -19,9 +19,36 @@ jobs:
test:
uses: ./.github/workflows/test.yml
validate-release-notes:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- name: 设置 Node.js 环境
uses: actions/setup-node@v6
with:
node-version: ${{ env.NODE_VERSION }}
- name: 校验版本说明文件
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ inputs.version }}"
echo "📝 Using manual input version: $VERSION"
else
VERSION="${GITHUB_REF#refs/tags/}"
echo "🏷️ Using git tag version: $VERSION"
fi
echo "Validating release notes for $VERSION"
node scripts/release-notes.js check "$VERSION"
# 构建 Windows 版本
build-windows:
needs: test
needs: [test, validate-release-notes]
runs-on: windows-latest
steps:
- name: 检出代码
@@ -142,7 +169,7 @@ jobs:
# 构建 macOS 版本
build-macos:
needs: test
needs: [test, validate-release-notes]
runs-on: macos-latest
steps:
- name: 检出代码
@@ -265,7 +292,7 @@ jobs:
# 构建 Linux 版本
build-linux:
needs: test
needs: [test, validate-release-notes]
runs-on: ubuntu-latest
steps:
- name: 检出代码
@@ -421,17 +448,17 @@ jobs:
if [[ $VERSION =~ -(alpha|beta|rc) ]]; then
IS_PRERELEASE=true
VERSION_TYPE="prerelease"
RELEASE_NAME_PREFIX="Preview "
RELEASE_NAME_PREFIX="Preview Prompt Optimizer "
echo "🧪 Detected prerelease version"
elif [[ $VERSION =~ -(hotfix|patch) ]]; then
IS_PRERELEASE=false
VERSION_TYPE="hotfix"
RELEASE_NAME_PREFIX="Hotfix "
RELEASE_NAME_PREFIX="Hotfix Prompt Optimizer "
echo "🔧 Detected hotfix version"
else
IS_PRERELEASE=false
VERSION_TYPE="release"
RELEASE_NAME_PREFIX="Release "
RELEASE_NAME_PREFIX="Release Prompt Optimizer "
echo "🚀 Detected stable release version"
fi
@@ -456,191 +483,21 @@ jobs:
echo "Final artifact list after cleaning:"
find ./artifacts -type f | sort
- name: 生成 Release Notes
- name: 生成 Release 正文
id: release_notes
run: |
# 获取当前tag
CURRENT_TAG=${{ steps.version.outputs.version }}
echo "Current tag: $CURRENT_TAG"
# 获取所有tags并调试输出
echo "All tags (sorted):"
git tag --sort=-version:refname | head -10
# 改进的上一个tag获取逻辑
# 1. 先尝试获取所有正式版本tag (不包含预览版)
STABLE_TAGS=$(git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' || true)
echo "Stable tags found: $STABLE_TAGS"
# 2. 获取所有tag (包含预览版)
ALL_TAGS=$(git tag --sort=-version:refname)
# 3. 根据当前tag类型选择合适的比较策略
PREVIOUS_TAG=""
if [[ $CURRENT_TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
# 当前是正式版,找上一个正式版
PREVIOUS_TAG=$(echo "$STABLE_TAGS" | grep -v "^$CURRENT_TAG$" | head -1 || true)
echo "Current is stable release, previous stable tag: $PREVIOUS_TAG"
else
# 当前是预览版,找上一个任意版本
PREVIOUS_TAG=$(echo "$ALL_TAGS" | grep -v "^$CURRENT_TAG$" | head -1 || true)
echo "Current is prerelease, previous tag: $PREVIOUS_TAG"
fi
# 生成commit历史(处理多行和长commit)
COMMITS=""
if [ -n "$PREVIOUS_TAG" ] && git rev-parse "$PREVIOUS_TAG" >/dev/null 2>&1 && git rev-parse "$CURRENT_TAG" >/dev/null 2>&1; then
echo "Generating commits from $PREVIOUS_TAG to $CURRENT_TAG"
COMMITS=$(git log --pretty=format:"%s|%h" $PREVIOUS_TAG..$CURRENT_TAG | while IFS='|' read -r subject hash; do
# 截断过长的commit message(保留前80个字符)
if [ ${#subject} -gt 80 ]; then
subject="${subject:0:77}..."
fi
echo "- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash}))"
done)
elif git rev-parse "$CURRENT_TAG" >/dev/null 2>&1; then
echo "No previous tag found or invalid, showing commits for current tag"
COMMITS=$(git log --pretty=format:"%s|%h" --max-count=10 | while IFS='|' read -r subject hash; do
# 截断过长的commit message(保留前80个字符)
if [ ${#subject} -gt 80 ]; then
subject="${subject:0:77}..."
fi
echo "- $subject ([${hash}](https://github.com/${{ github.repository }}/commit/${hash}))"
done)
fi
# 检查是否有commits
if [ -z "$COMMITS" ]; then
echo "No commits found, using fallback message"
COMMITS="- 首次发布"
else
echo "Generated commits:"
echo "$COMMITS"
fi
# 限制commits数量(最多显示20个)
COMMITS_LIMITED=$(echo "$COMMITS" | head -20)
COMMITS_COUNT=$(echo "$COMMITS" | wc -l)
if [ $COMMITS_COUNT -gt 20 ]; then
COMMITS_LIMITED="$COMMITS_LIMITED
... 以及其他 $((COMMITS_COUNT - 20)) 个提交"
fi
# 获取本次发布的贡献者(排除主要作者)
CONTRIBUTORS=""
if [ -n "$PREVIOUS_TAG" ] && git rev-parse "$PREVIOUS_TAG" >/dev/null 2>&1; then
# 获取贡献者列表,排除主要作者和机器人
CONTRIBUTORS=$(git log --pretty=format:"%an|%ae" $PREVIOUS_TAG..$CURRENT_TAG 2>/dev/null | \
sort -u | \
grep -v -E "^(linshen|linshenkx|且炼时光)\|" | \
grep -v -E "\[bot\]@" | \
grep -v -E "^GitButler\|" | \
cut -d'|' -f1 | \
sort -u || true)
else
CONTRIBUTORS=$(git log --pretty=format:"%an|%ae" --max-count=50 2>/dev/null | \
sort -u | \
grep -v -E "^(linshen|linshenkx|且炼时光)\|" | \
grep -v -E "\[bot\]@" | \
grep -v -E "^GitButler\|" | \
cut -d'|' -f1 | \
sort -u || true)
fi
# 格式化贡献者列表
CONTRIBUTORS_SECTION=""
if [ -n "$CONTRIBUTORS" ]; then
CONTRIBUTORS_LIST=$(echo "$CONTRIBUTORS" | while read -r name; do
[ -n "$name" ] && echo "- @$name"
done | grep -v "^$" || true)
if [ -n "$CONTRIBUTORS_LIST" ]; then
CONTRIBUTORS_SECTION="
### 🙏 致谢
感谢以下贡献者对本次发布的贡献:
$CONTRIBUTORS_LIST
"
echo "Found contributors: $CONTRIBUTORS"
fi
fi
# 根据版本类型设置不同的图标和描述
VERSION_ICON="🚀"
VERSION_DESC="稳定版本"
STABILITY_NOTE=""
if [[ $CURRENT_TAG =~ -(alpha|beta|rc) ]]; then
VERSION_ICON="🧪"
VERSION_DESC="预览版本"
STABILITY_NOTE="
> ⚠️ **注意**: 这是一个预览版本,可能包含实验性功能。建议在非生产环境中使用。"
elif [[ $CURRENT_TAG =~ -(hotfix|patch) ]]; then
VERSION_ICON="🔧"
VERSION_DESC="紧急修复版本"
STABILITY_NOTE="
> 🔧 **修复版本**: 此版本主要包含重要问题的修复,建议尽快更新。"
fi
# 创建release notes
cat > release_notes.md << EOF
## $VERSION_ICON Prompt Optimizer $CURRENT_TAG
**版本类型**: $VERSION_DESC$STABILITY_NOTE
### 📦 下载
- **Windows**: \`PromptOptimizer-*-win-x64.exe\` (安装程序) 或 \`PromptOptimizer-*-win-x64.zip\` (便携版)
- **macOS (Apple Silicon)**: \`PromptOptimizer-*-darwin-arm64.dmg\` (安装程序) 或 \`PromptOptimizer-*-darwin-arm64.zip\` (便携版)
- **macOS (Intel)**: \`PromptOptimizer-*-darwin-x64.dmg\` (安装程序) 或 \`PromptOptimizer-*-darwin-x64.zip\` (便携版)
- **Linux**: \`PromptOptimizer-*-linux-x86_64.AppImage\` (便携版) 或 \`PromptOptimizer-*-linux-x64.zip\` (便携版)
### 🔧 安装说明
- **Windows**:
- **安装程序**: 下载 \`.exe\` 文件,双击运行安装向导
- **便携版**: 下载 \`.zip\` 文件,解压后运行 \`PromptOptimizer.exe\`
- **macOS**:
- **Apple Silicon (M1/M2/M3)**: 下载 \`darwin-arm64\` 版本
- **Intel 芯片**: 下载 \`darwin-x64\` 版本
- **安装程序**: 下载 \`.dmg\` 文件,双击挂载后拖拽到应用程序文件夹
- **便携版**: 下载 \`.zip\` 文件,解压后运行 \`PromptOptimizer.app\`
- ⚠️ **安全提示**: 如果打开应用时提示「已损坏」或「无法验证开发者」,请在终端执行以下命令后重试:
\`\`\`bash
xattr -rd com.apple.quarantine /Applications/PromptOptimizer.app
\`\`\`
或者对于 .dmg 文件:
\`\`\`bash
xattr -rd com.apple.quarantine ~/Downloads/PromptOptimizer-*.dmg
\`\`\`
- **Linux**:
- **AppImage**: 下载 \`.AppImage\` 文件,添加执行权限后直接运行
- **便携版**: 下载 \`.zip\` 文件,解压后运行 \`PromptOptimizer\`
### 📝 更新内容
$COMMITS_LIMITED
$CONTRIBUTORS_SECTION
### 🔗 更多信息
- **项目主页**: [GitHub Repository](https://github.com/${{ github.repository }})
- **问题反馈**: [Issues](https://github.com/${{ github.repository }}/issues)
- **讨论交流**: [Discussions](https://github.com/${{ github.repository }}/discussions)
---
**提示**: 如果需要查看完整的提交历史,请访问项目的 [GitHub Commits](https://github.com/${{ github.repository }}/commits)
EOF
echo "Generated release notes:"
cat release_notes.md
node scripts/release-notes.js render-body "$CURRENT_TAG" "${{ github.repository }}" > release_body.md
echo "release_notes_path=release_body.md" >> "$GITHUB_OUTPUT"
echo "Generated release body:"
cat release_body.md
- name: 创建 Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
name: ${{ steps.version.outputs.release_name_prefix }}${{ steps.version.outputs.version }}
body_path: release_notes.md
body_path: ${{ steps.release_notes.outputs.release_notes_path }}
files: |
./artifacts/windows-build/*
./artifacts/macos-build/*
+35 -1
View File
@@ -1,5 +1,39 @@
# Changelog
Full release narratives now live in versioned files under `releases/`. This file stays as the index and summary entry point.
## [2.8.0] - 2026-04-03
- EN: Text-to-image evaluation, smoother reference-image workflows, and stronger Cloudflare-backed model support. See [Release Notes (EN)](releases/v2.8.0.en.md).
- 中文:本次发布聚焦文生图评估、更顺畅的参考图工作流,以及更稳健的 Cloudflare 模型支持。参见 [版本说明(中文)](releases/v2.8.0.zh-CN.md)。
## [2.7.0] - 2026-03-25
- EN: Structured compare evaluation, smoother compare UX, and refreshed docs and website surfaces. See [Release Notes (EN)](releases/v2.7.0.en.md).
- 中文:本次发布聚焦结构化对比评估、更顺畅的对比体验,以及焕新的文档与站点展示。参见 [版本说明(中文)](releases/v2.7.0.zh-CN.md)。
## [2.6.3] - 2026-03-22
- EN: Image prompt extraction, stronger image understanding, and a safer desktop release pipeline. See [Release Notes (EN)](releases/v2.6.3.en.md).
- 中文:本次发布带来图像提示词提取、更强的图像理解能力,以及更稳健的桌面端发布链路。参见 [版本说明(中文)](releases/v2.6.3.zh-CN.md)。
## [2.6.2] - 2026-03-15
- EN: Reworked evaluation architecture, cleaner workspace/result/compare flows, and more stable variable handling. See [Release Notes (EN)](releases/v2.6.2.en.md).
- 中文:本次发布重构了评估架构,梳理了工作区 / 结果 / 对比流程,并让变量处理更稳定。参见 [版本说明(中文)](releases/v2.6.2.zh-CN.md)。
## [2.6.1] - 2026-03-12
- EN: Better model management, richer image generation metadata, and stronger custom model configuration support. See [Release Notes (EN)](releases/v2.6.1.en.md).
- 中文:本次发布改进了模型管理、补充了更丰富的图像生成元数据,并增强了自定义模型配置能力。参见 [版本说明(中文)](releases/v2.6.1.zh-CN.md)。
## [2.6.0] - 2026-03-09
- EN: MiniMax provider support, stronger model management, and more usable temporary-variable and XML inspection workflows. See [Release Notes (EN)](releases/v2.6.0.en.md).
- 中文:本次发布带来 MiniMax provider 支持,进一步强化了模型管理,也让临时变量与 XML 检查体验更顺手。参见 [版本说明(中文)](releases/v2.6.0.zh-CN.md)。
## [2.5.5] - 2026-03-01
- EN: Media-aware favorites, Prompt Garden asset imports, and more durable preview storage. See [Release Notes (EN)](releases/v2.5.5.en.md).
- 中文:本次发布让收藏夹支持媒体内容、Prompt Garden 资源导入,并让预览资源存储更可靠。参见 [版本说明(中文)](releases/v2.5.5.zh-CN.md)。
## [2.5.4] - 2026-02-10
- EN: Feedback-driven evaluation, stronger parsing robustness, and smoother analyze interactions. See [Release Notes (EN)](releases/v2.5.4.en.md).
- 中文:本次发布强化了反馈驱动评估、结果解析鲁棒性,以及分析交互体验。参见 [版本说明(中文)](releases/v2.5.4.zh-CN.md)。
## [2.1.0] - 2025-01-19
### 🎉 Added - 收藏管理重构 (Favorite Management Refactor)
@@ -54,4 +88,4 @@
- 基础收藏管理功能
- 优化历史集成
- 标签和分类基础支持
- 导入导出功能
- 导入导出功能
+1
View File
@@ -7,6 +7,7 @@
- [产品需求文档](./prd.md) - 产品功能需求和规格说明
- [项目状态](./project-status.md) - 当前项目进度和完成情况
- [版本同步](./version-sync.md) - 版本管理和同步策略
- [版本说明与 Release 发布](./release-notes.md) - 双语版本说明与发布流程
## 📊 项目状态
+136
View File
@@ -0,0 +1,136 @@
# 版本说明与 Release 发布
## 目标
从当前流程开始,仓库内的版本说明文件是 GitHub Release 正文的唯一事实来源。
- `CHANGELOG.md`:版本索引与摘要入口
- `releases/vX.Y.Z.en.md`:某个版本的英文完整说明
- `releases/vX.Y.Z.zh-CN.md`:某个版本的中文完整说明
- `.github/workflows/release.yml`:读取两个版本文件,生成 GitHub Release 摘要正文
## 文件约定
每次发布都需要同时维护两处内容:
1. `CHANGELOG.md`
- 顶部必须是当前版本
- 必须包含一行英文摘要和一行中文摘要
- 必须分别链接到 `releases/vX.Y.Z.en.md` 和 `releases/vX.Y.Z.zh-CN.md`
2. `releases/vX.Y.Z.en.md`
- 标题固定为 `# Prompt Optimizer vX.Y.Z`
- 必须包含 `## Summary`
- 必须包含以下完整栏目:
- `## Highlights`
- `## Product Updates`
- `## Fixes`
- `## Breaking Changes / Upgrade Notes`
- `## Developer Notes`
3. `releases/vX.Y.Z.zh-CN.md`
- 标题固定为 `# Prompt Optimizer vX.Y.Z`
- 必须包含 `## 概括`
- 必须包含以下完整栏目:
- `## 亮点`
- `## 产品更新`
- `## 修复`
- `## 破坏性变更 / 升级说明`
- `## 开发者说明`
`Summary / 概括` 为正式发布必填项,GitHub Release 会优先使用它们来生成摘要正文。
## 常用命令
```bash
# 1. 生成当前版本的版本说明模板
pnpm release:notes:new
# 或者显式指定版本
pnpm release:notes:new 2.9.0
# 2. 校验当前版本说明
pnpm release:notes:check
# 或者显式指定版本
pnpm release:notes:check v2.9.0
# 3. 校验历史版本条目(用于回填旧版本 release 文档)
pnpm release:notes:check:entry v2.6.0
```
`release:notes:new` 会同时生成英文和中文模板,并附带一段仅供编辑参考的 commit 草稿区。
`release:notes:check` 用于正式发布前的硬门槛校验,要求目标版本必须位于 `CHANGELOG.md` 顶部。
`release:notes:check:entry` 用于历史版本回填,只要求 `CHANGELOG.md` 中存在对应版本条目,不要求它在顶部。
## 推荐发布流程
```bash
# 1. 更新版本号
pnpm version patch
# 2. 生成版本说明模板
pnpm release:notes:new
# 3. 手动完善 releases/vX.Y.Z.en.md 与 releases/vX.Y.Z.zh-CN.md,
# 并更新 CHANGELOG.md 顶部摘要
# 4. 校验版本说明
pnpm release:notes:check
# 5. 创建 tag(会再次自动校验)
pnpm version:tag
# 6. 推送 tag
pnpm version:publish
```
## 校验规则
以下情况会导致 `pnpm release:notes:check` 失败:
- 缺少对应版本的英文或中文 release 文件
- 标题版本号与文件名不一致
- 缺少 `## Summary` 或 `## 概括`
- 必需栏目缺失或顺序错误
- `CHANGELOG.md` 顶部不是当前版本
- `CHANGELOG.md` 顶部没有同时链接英文和中文版本说明
- 正文中仍然存在 `TODO`、`TBD`、`待补充`、`XX` 等占位内容
对于历史版本回填,可以使用 `pnpm release:notes:check:entry <version>`。它会沿用同样的正文结构校验,但把 `CHANGELOG.md` 校验范围放宽为“存在对应版本条目”。
## GitHub Release 行为
发布工作流不会再从 commit 列表自动拼接公开正文,而是优先读取:
- `releases/vX.Y.Z.en.md` 中的 `## Summary`
- `releases/vX.Y.Z.zh-CN.md` 中的 `## 概括`
然后生成:
- 英文区块
- `Summary`
- `macOS Security Note`
- 安装文档链接
- 英文全文链接
- 中文区块
- `概括`
- `macOS 安全提示`
- 安装文档链接
- 中文全文链接
其中 `macOS Security Note / macOS 安全提示` 为固定内容,用来明确说明:
- “已损坏”
- “无法验证开发者”
这类提示通常来自 macOS 的隔离属性,不一定是应用本身损坏。
如果历史版本缺少概括,渲染逻辑可以回退为全文;但正式发布流程会被校验阻塞。
`workflow_dispatch` 中手动输入的 `version` 仍然用于“当前所选 ref 上准备发布的版本”,不是用来从当前主干回补任意历史版本的重新发布。
如果版本说明校验失败:
- 构建任务不会继续执行
- GitHub Release 不会创建成功
+18 -1
View File
@@ -44,6 +44,23 @@ pnpm run version:sync
3. **同步脚本**: `scripts/sync-versions.js` 读取新的版本号并更新其他文件
4. **git commit**: pnpm 创建包含所有版本号变更的提交和标签
## 与 Release 版本说明的关系
从当前流程开始,版本号同步只是发布准备的一部分。创建正式 tag 之前,还需要补齐并校验版本说明:
```bash
pnpm release:notes:new
pnpm release:notes:check
pnpm version:tag
```
其中:
- `pnpm release:notes:new` 会同时生成 `releases/vX.Y.Z.en.md` 和 `releases/vX.Y.Z.zh-CN.md` 模板
- `pnpm release:notes:check` 会校验 `CHANGELOG.md` 以及英文、中文两个版本说明文件
- `pnpm release:notes:check:entry` 可用于历史版本回填时单独校验某个旧版本条目
- `pnpm version:tag` 会在打 tag 前再次执行一次校验,避免缺失版本说明就发布
## 添加新的同步文件
如需添加更多文件的版本同步,编辑 `scripts/sync-versions.js` 文件中的 `versionFiles` 数组:
@@ -83,4 +100,4 @@ const versionFiles = [
```bash
node scripts/sync-versions.js
```
```
+10 -4
View File
@@ -27,7 +27,9 @@
"dev:desktop:fresh": "node scripts/run-many.js kill:dev clean pnpm-install dev:desktop",
"dev:desktop:parallel": "concurrently -k -p \"[{name}]\" -n \"WEB,DESKTOP\" \"pnpm -F @prompt-optimizer/web dev\" \"pnpm -F @prompt-optimizer/desktop dev\"",
"test": "node scripts/run-many.js -s test:unit test:e2e:smart",
"test:unit": "pnpm -r test --run --passWithNoTests",
"test:unit": "node scripts/run-many.js -s test:packages test:scripts",
"test:packages": "pnpm -r test --run --passWithNoTests",
"test:scripts": "node --test scripts/*.test.mjs",
"test:e2e": "playwright test",
"test:e2e:smart": "node scripts/smart-e2e.js",
"test:e2e:record": "cross-env E2E_VCR_MODE=record playwright test",
@@ -36,19 +38,23 @@
"test:gate:core": "pnpm -F @prompt-optimizer/core test:gate",
"test:gate:ui": "pnpm -F @prompt-optimizer/core build && pnpm -F @prompt-optimizer/ui test",
"test:gate:e2e": "playwright test tests/e2e/regression.spec.ts tests/e2e/workflows/p0-route-smoke.spec.ts",
"test:gate": "node scripts/run-many.js -s test:gate:core test:gate:ui",
"test:gate": "node scripts/run-many.js -s test:scripts test:gate:core test:gate:ui",
"test:gate:full": "node scripts/run-many.js -s test:gate test:gate:e2e",
"test:fast": "pnpm -r test --run --passWithNoTests",
"test:fast": "pnpm run test:unit",
"test:e2e:ui": "playwright test --ui",
"test:e2e:debug": "playwright test --debug",
"clean": "node scripts/run-many.js clean:dist clean:vite",
"clean:dist": "rimraf packages/core/dist packages/ui/dist packages/web/dist packages/extension/dist packages/desktop/dist packages/desktop/web-dist",
"clean:vite": "rimraf packages/core/node_modules/.vite packages/ui/node_modules/.vite packages/web/node_modules/.vite packages/extension/node_modules/.vite",
"pnpm-install": "pnpm install",
"release:notes:new": "node scripts/release-notes.js new",
"release:notes:check": "node scripts/release-notes.js check",
"release:notes:check:entry": "node scripts/release-notes.js check-entry",
"release:notes:render-body": "node scripts/release-notes.js render-body",
"version:sync": "node scripts/sync-versions.js",
"version": "pnpm run version:sync && git add -A",
"version:prepare": "pnpm version --no-git-tag-version",
"version:tag": "git tag v$(node -p \"require('./package.json').version\")",
"version:tag": "pnpm release:notes:check && git tag v$(node -p \"require('./package.json').version\")",
"version:publish": "git push origin v$(node -p \"require('./package.json').version\")",
"mcp:build": "pnpm --filter @prompt-optimizer/mcp-server build",
"mcp:dev": "pnpm --filter @prompt-optimizer/mcp-server dev",
+34
View File
@@ -0,0 +1,34 @@
# Prompt Optimizer v2.5.4
## Summary
- Added feedback-driven evaluation flows so users can guide analysis with explicit evaluation notes.
- Updated evaluation templates to pay more attention to user feedback and improved result parsing robustness.
- Polished the analyze interaction flow and aligned more provider environment mappings.
## Highlights
- Evaluation became more iterative: users can now add feedback directly into the evaluation flow instead of only reading a score afterward.
- The evaluation stack now treats user feedback as a stronger signal, which improves the usefulness of follow-up analysis.
- Parsing and provider configuration became more resilient, reducing avoidable friction in evaluation-heavy sessions.
## Product Updates
### Web
- Added an evaluation feedback editor and feedback-aware analyze flow across the main workspace surfaces.
- Merged focus input more cleanly into the analyze action so evaluation guidance can be supplied at the right moment.
- Kept the evaluation popover open during apply actions, making iterative review less disruptive.
### Core/Infra
- Added support for user feedback across evaluation templates and made that feedback a higher-priority signal during evaluation.
- Improved evaluation result parsing robustness so structured output is interpreted more consistently.
- Added missing provider environment mappings and aligned provider handling across desktop logs, MCP selection, and docs.
## Fixes
- Fixed evaluation result parsing edge cases that could weaken downstream analysis.
- Fixed missing provider environment mappings that affected some env-driven model setups.
- Fixed the evaluation popover behavior so apply actions no longer interrupt iterative review as easily.
## Breaking Changes / Upgrade Notes
- None. Existing evaluation workflows continue to work, now with stronger feedback support.
## Developer Notes
- `v2.5.4` is the release where evaluation shifts from a mostly one-way score display toward a feedback-guided loop.
- If you maintain custom evaluation prompts or result consumers, this is the right baseline for the stronger feedback signal and more defensive parsing behavior.
+34
View File
@@ -0,0 +1,34 @@
# Prompt Optimizer v2.5.4
## 概括
- 评估流程开始支持“基于反馈继续分析”,用户可以直接带着明确意见去驱动评估。
- 评估模板更加重视用户反馈,同时结果解析也更稳健了。
- 分析交互流被顺了一遍,provider 环境变量映射也补得更完整。
## 亮点
- 评估不再只是看完分数就结束,用户现在可以把反馈直接带入评估流程,形成更连续的迭代闭环。
- 评估底层把用户反馈视为更强的信号,让后续分析结果更贴近真实诉求。
- 结果解析和 provider 配置都更稳了,减少了高频评估场景里的额外摩擦。
## 产品更新
### Web
- 在主要工作区里新增了评估反馈编辑器,以及基于反馈继续分析的交互流。
- 把 focus input 更自然地并入 analyze 动作,让用户能在合适的时机补充评估指导信息。
- 执行 apply 操作时,评估浮层会保持打开,连续迭代时不再那么打断思路。
### Core/Infra
- 为评估模板全面补上了用户反馈支持,并让反馈在评估时拥有更高优先级。
- 增强了评估结果解析的鲁棒性,让结构化输出的解释更稳定一致。
- 补齐了缺失的 provider 环境变量映射,并对齐了桌面日志、MCP 选择和文档中的 provider 处理逻辑。
## 修复
- 修复了若干会削弱后续分析质量的评估结果解析边界情况。
- 修复了部分依赖环境变量驱动的模型配置缺少 provider 映射的问题。
- 修复了评估浮层在 apply 操作时过早打断迭代查看的问题。
## 破坏性变更 / 升级说明
- 无。现有评估流程可以继续使用,只是现在对反馈的支持更完整了。
## 开发者说明
- `v2.5.4` 可以看作评估体系从“单向打分展示”转向“反馈驱动闭环”的起点版本。
- 如果你维护自定义评估提示词或结果消费逻辑,这一版是更强反馈信号和更防御式结果解析的合适基线。
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.5.5
## Summary
- Expanded favorites to support media uploads, richer preview cards, and Prompt Garden imports with asset references.
- Moved imported snapshot assets onto more durable storage so favorite previews survive session cleanup more reliably.
- Improved browser-side image service compatibility and polished a few import and icon details.
## Highlights
- Favorites became much more media-aware, especially when saving Prompt Garden content with images and cover previews.
- Prompt Garden imports now feel safer and more intentional thanks to asset-aware imports and confirm-mode flows.
- Snapshot preview storage became more durable, reducing the chance that imported visual references disappear with session data.
## Product Updates
### Web
- Added generic media upload and preview support when saving favorites.
- Displayed cover images on favorite cards and added richer preview panels for Prompt Garden imports.
- Added Prompt Garden import support with favorite asset references, plus a confirm-mode import flow for more deliberate saves.
- Introduced pluggable favorite-preview support so Prompt Garden previews can evolve without tightly coupling the rest of the favorites UI.
### Core/Infra
- Isolated snapshot image assets from session storage so imported favorite previews remain available more reliably.
- Improved browser-side CORS handling for OpenAI SDK fetch usage.
- Removed unnecessary CORS-restricted tagging for ModelScope and Seedream flows.
## Fixes
- Fixed provider/browser compatibility issues around image-related requests and SDK fetch behavior.
- Fixed a missing `NText` import in `SaveFavoriteDialog`.
- Standardized rounded app icon exports for a more consistent release surface.
## Breaking Changes / Upgrade Notes
- None. Existing favorites should continue to work, with stronger support for media-heavy entries.
## Developer Notes
- This release is the handoff point where favorites stop behaving like text-only records and start carrying reusable media and preview state more intentionally.
- If you maintain Prompt Garden integrations, use `v2.5.5` as the baseline for asset-aware favorite imports.
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.5.5
## 概括
- 收藏夹开始支持媒体上传、更丰富的预览卡片,以及带资源引用的 Prompt Garden 导入。
- 导入的快照资源被迁移到更持久的存储方式里,收藏预览不容易再随着会话清理而丢失。
- 浏览器侧图像服务兼容性也做了补强,同时顺手打磨了若干导入与图标细节。
## 亮点
- 收藏夹终于更像“可承载媒体内容的资产”,尤其适合保存带图片和封面的 Prompt Garden 内容。
- Prompt Garden 导入现在支持资源引用和确认式导入流程,保存体验更稳、更可控。
- 快照预览资源的持久化方式更可靠,减少了导入视觉内容后因为会话数据清理而丢失的情况。
## 产品更新
### Web
- 保存收藏时新增通用媒体上传与预览能力。
- 收藏卡片现在可以显示封面图,也为 Prompt Garden 导入补上了更丰富的预览面板。
- Prompt Garden 导入支持带收藏资源引用的保存流程,并增加了确认式导入模式,让保存动作更明确。
- 引入可插拔的收藏预览机制,让 Prompt Garden 预览能力可以持续演进,同时不把整个收藏 UI 绑死在单一集成上。
### Core/Infra
- 将快照图片资源从会话存储中隔离出来,让导入后的收藏预览更稳定可用。
- 改进了浏览器侧使用 OpenAI SDK fetch 时的 CORS 兼容性。
- 移除了 ModelScope 和 Seedream 流程中不必要的 CORS 限制标记。
## 修复
- 修复了部分图像相关请求和 SDK fetch 行为在浏览器环境下的兼容性问题。
- 修复了 `SaveFavoriteDialog` 中缺失 `NText` 导入的问题。
- 统一了圆角应用图标导出的资源表现。
## 破坏性变更 / 升级说明
- 无。现有收藏数据可以继续使用,同时会获得更好的媒体内容支持。
## 开发者说明
- 这一版是收藏夹从“偏文本记录”转向“可承载媒体与预览状态资产”的分水岭版本。
- 如果你在维护 Prompt Garden 相关集成,可以把 `v2.5.5` 视为带资源引用收藏导入的基线版本。
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.6.0
## Summary
- Added MiniMax provider support and made advanced model configuration easier to manage.
- Improved temporary-variable editing, model management, and XML result inspection across the app.
- Smoothed several cross-workspace interactions so model changes and feedback flows behave more reliably.
## Highlights
- MiniMax became a first-class provider, making it easier to test and run more model combinations in one workspace.
- The model manager and temporary-variable tools became much more usable for heavier prompt iteration workflows.
- XML outputs are now easier to inspect thanks to an auto-rendered collapsible tree view.
## Product Updates
### Web
- Refined the model manager layout and cards so text, image, and function models feel more consistent and easier to scan.
- Improved the temporary variables panel with a better editing experience, variable renaming, and a fullscreen editor that uses dialog space more effectively.
- Added automatic XML rendering with a collapsible tree so structured outputs are easier to inspect without leaving the app.
- Kept model lists in sync across workspaces after manager updates and tightened typed feedback payload handling.
### Core/Infra
- Added MiniMax provider support, including OpenAI-compatible integration and initial default-model coverage.
- Deduplicated shared `TextModelConfig` typing so model configuration behavior is easier to maintain.
- Preserved enabled-state choices and cleaned up model version label formatting.
## Fixes
- Fixed model-list synchronization after manager changes so other workspaces reflect updates more reliably.
- Fixed scroll-jump issues in the fullscreen variable editor.
- Fixed several model-manager interaction details, including enabled-choice persistence and clearer version labels.
## Breaking Changes / Upgrade Notes
- None. Existing workspaces and saved data should continue to work without migration.
## Developer Notes
- This version is a foundation release for the later model-management and evaluation work shipped in `v2.6.1` and `v2.6.2`.
- If you track when MiniMax provider support and the XML tree inspector first landed, `v2.6.0` is the right release baseline.
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.6.0
## 概括
- 新增了 MiniMax provider 支持,也让高级模型配置的管理更顺手。
- 临时变量编辑、模型管理和 XML 结果查看体验都有明显改进。
- 多个工作区之间的模型同步和反馈链路也更稳定了,日常迭代更顺畅。
## 亮点
- MiniMax 成为一等公民 provider,更方便在同一工作区里测试和运行更多模型组合。
- 模型管理器和临时变量工具更适合高频提示词迭代场景,操作成本更低。
- XML 输出现在会自动渲染为可折叠树视图,查看结构化结果更直观。
## 产品更新
### Web
- 优化了模型管理器的布局和卡片设计,让文本、图像、函数模型的呈现更统一、更容易浏览。
- 改进了临时变量面板,补齐了更顺手的编辑体验、变量重命名能力,以及更充分利用弹窗空间的全屏编辑器。
- 新增 XML 自动渲染与可折叠树视图,不用离开应用也能更轻松地检查结构化输出。
- 模型管理器更新后,模型列表可以更稳定地同步到各个工作区,同时也收紧了反馈数据的类型处理。
### Core/Infra
- 新增 MiniMax provider 支持,包括兼容 OpenAI 风格接口的接入方式和初始默认模型配置。
- 去重了共享的 `TextModelConfig` 类型来源,后续维护模型配置逻辑更清晰。
- 优化了模型启用状态的保留逻辑,并统一了版本标签格式。
## 修复
- 修复了模型管理器修改后其他工作区模型列表不同步的问题。
- 修复了全屏变量编辑器里的滚动跳动问题。
- 修复了模型管理器中的若干交互细节,包括启用状态保留和版本标签显示不清晰的问题。
## 破坏性变更 / 升级说明
- 无。现有工作区和已保存数据默认不需要迁移。
## 开发者说明
- 这一版是后续 `v2.6.1`、`v2.6.2` 模型管理和评估链路继续演进的重要基础版本。
- 如果你要追溯 MiniMax provider 支持和 XML 树形查看器最早在哪个版本落地,可以以 `v2.6.0` 为基线。
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.6.1
## Summary
- Improved model management with clone actions, restored labels, and stronger custom-model configuration support.
- Added richer image-generation details such as token usage and image metadata in the UI.
- Fixed a missing desktop IPC bridge so prompt messages can flow more reliably in desktop builds.
## Highlights
- Model management became more capable for advanced setups, especially when duplicating or tuning multiple model profiles.
- Image generation now exposes more useful runtime details directly in the interface.
- Core support for environment-driven and dynamically fetched model configuration expanded again.
## Product Updates
### Desktop
- Added the missing prompt-message IPC bridge needed by desktop flows.
### Web
- Added token-usage and image-metadata display for image generation.
- Added a Clone action for model configurations in Model Manager.
- Restored model manager action labels so key actions stay understandable.
### Core/Infra
- Added support for custom model parameters loaded from environment variables.
- Enabled dynamic model fetching for the Gemini image adapter.
## Fixes
- Fixed missing desktop IPC wiring for prompt messages.
- Restored missing model manager action labels in the UI.
## Breaking Changes / Upgrade Notes
- None. Existing model setups should continue to work, with more configuration flexibility available.
## Developer Notes
- This release continues the push toward more dynamic model configuration, especially for teams managing providers through environment-based deployment.
- If you are tracking when image generation metadata first became visible in the app UI, this is the version to reference.
+35
View File
@@ -0,0 +1,35 @@
# Prompt Optimizer v2.6.1
## 概括
- 模型管理能力进一步增强,支持克隆配置、恢复操作标签,并加强了自定义模型配置能力。
- 图像生成界面开始展示更丰富的运行时信息,包括 token 使用量和图片元数据。
- 修复了桌面端缺失的 IPC bridge,让提示消息链路更完整。
## 亮点
- 对复杂模型配置场景更友好了,尤其是在复制和微调多组模型配置时更顺手。
- 图像生成结果现在会直接展示更多有用信息,便于排查和比较。
- Core 对环境变量驱动配置和动态拉取模型的支持又向前推进了一步。
## 产品更新
### Desktop
- 补上了桌面端流程所需的 prompt message IPC bridge。
### Web
- 为图像生成增加了 token 使用量和图片元数据显示。
- 在 Model Manager 中新增了模型配置的 Clone 按钮。
- 恢复了模型管理器里缺失的操作标签,让关键动作更容易理解。
### Core/Infra
- 新增对环境变量加载自定义模型参数的支持。
- 为 Gemini 图像适配器启用了动态模型拉取能力。
## 修复
- 修复了桌面端提示消息链路缺失的 IPC 连接。
- 恢复了 UI 中丢失的模型管理器操作标签。
## 破坏性变更 / 升级说明
- 无。现有模型配置默认可继续工作,同时你会获得更灵活的配置方式。
## 开发者说明
- 这一版延续了“模型配置更动态化”的方向,尤其适合通过环境变量管理 provider 的团队。
- 如果你在追溯图像生成元数据首次进入应用 UI 的时间点,这一版就是关键版本。
+32
View File
@@ -0,0 +1,32 @@
# Prompt Optimizer v2.6.2
## Summary
- Reworked the evaluation architecture to better align workspace, result, and compare flows.
- Refactored analysis and evaluation handling so the UI feels more consistent across prompt-testing paths.
- Tightened variable handling and refreshed test coverage around the new evaluation protocol.
## Highlights
- Evaluation logic was rebuilt around a cleaner protocol and template-builder design.
- Analysis, result review, and compare evaluation flows now line up more naturally in the UI.
- Variable-heavy scenarios became safer, especially around result evaluation in pro-variable workflows.
## Product Updates
### Web
- Refactored analysis and evaluation flows around workspace, result, and compare states.
- Improved the overall evaluation journey so users can move more clearly between prompt drafting, analysis, and comparison.
### Core/Infra
- Redesigned the evaluation protocol and template builders underneath the app.
- Refreshed unit, e2e, and VCR coverage to match the new protocol shape.
- Consolidated workspace evaluation docs and acceptance evidence around the newer flow.
## Fixes
- Stripped internal predefined variables from pro-variable result evaluation output.
- Refreshed fixtures and coverage to stabilize evaluation behavior after the protocol redesign.
## Breaking Changes / Upgrade Notes
- None for end users. Internally, evaluation-related template and protocol behavior changed substantially.
## Developer Notes
- This release is an architectural checkpoint for evaluation flows. If you compare behavior before and after the newer workspace/result/compare model, `v2.6.2` is the main transition point.
- Teams maintaining evaluation prompts or result panels should expect more coordinated flow boundaries from this version onward.
+32
View File
@@ -0,0 +1,32 @@
# Prompt Optimizer v2.6.2
## 概括
- 重构了评估架构,让工作区、结果页和对比流程之间的关系更清晰。
- 分析与评估流程做了成体系整理,整体交互在不同测试路径上更一致。
- 变量处理和评估覆盖也一起补强了,特别是 Pro Variable 场景下更稳。
## 亮点
- 底层评估逻辑围绕新的协议和模板构建器重新梳理了一遍。
- 分析、结果查看和对比评估在 UI 层终于更自然地串起来了。
- 变量较多的场景更安全了,尤其是 Pro Variable 的结果评估输出更干净。
## 产品更新
### Web
- 围绕工作区、结果和对比状态,重构了分析与评估流程。
- 优化了从提示词编写、分析到对比的整体评估路径,让流程切换更清楚。
### Core/Infra
- 重新设计了应用底层的评估协议和模板构建器。
- 刷新了单元测试、E2E 和 VCR 覆盖,使其与新的协议结构一致。
- 归并了工作区评估相关文档和验收证据,减少旧流程与新流程之间的割裂。
## 修复
- 从 Pro Variable 的结果评估输出中移除了内部预定义变量。
- 在评估协议重构后刷新了测试夹具与覆盖,提升了行为稳定性。
## 破坏性变更 / 升级说明
- 对终端用户无破坏性变更;但从内部实现看,评估协议和模板行为发生了较大调整。
## 开发者说明
- 这一版可以看作评估链路的一个架构转折点。若要比较“旧评估流”和“新工作区 / 结果 / 对比模型”,`v2.6.2` 是关键界线。
- 维护评估提示词或结果面板的同学,从这一版开始需要接受更明确的流程边界。
+32
View File
@@ -0,0 +1,32 @@
# Prompt Optimizer v2.6.3
## Summary
- Added image prompt extraction so existing images can be turned back into reusable prompt material.
- Introduced stronger image understanding support in core services and extraction templates.
- Tightened the desktop release pipeline and refreshed the default MiniMax model baseline.
## Highlights
- Image prompt extraction became a first-class workflow, making it easier to reverse engineer prompts from generated or reference images.
- Core image understanding capabilities expanded to support that extraction flow more cleanly.
- Release stability improved through desktop pipeline fixes and a more up-to-date MiniMax default model.
## Product Updates
### Web
- Added an image prompt extraction workflow in the UI.
- Connected image extraction behavior to the new image understanding and prompt-template support underneath.
### Core/Infra
- Added an image understanding service and extraction-focused templates in core.
- Upgraded the default MiniMax model baseline to `M2.7`.
- Cleaned up release automation so desktop builds no longer auto-publish releases unexpectedly.
## Fixes
- Fixed the desktop release pipeline so build jobs stop auto-publishing releases.
- Reduced the chance of stale default-model behavior by aligning MiniMax defaults with the newer model generation.
## Breaking Changes / Upgrade Notes
- None. This release extends image tooling without requiring migration steps.
## Developer Notes
- This version is the real starting point for image prompt extraction in the current codebase.
- If you are tracing when image understanding moved from isolated logic into reusable release-ready workflows, this is the version to reference.
+32
View File
@@ -0,0 +1,32 @@
# Prompt Optimizer v2.6.3
## 概括
- 新增图像提示词提取能力,可以把已有图片重新转成可复用的提示词素材。
- Core 层补上了更完整的图像理解服务与提取模板支撑。
- 桌面端发布链路更稳了,同时也更新了默认 MiniMax 模型基线。
## 亮点
- 图像提示词提取正式成为一套工作流,更适合从生成图或参考图中反向整理提示词。
- 核心图像理解能力进一步补强,为提取流程提供了更完整的底层支持。
- 通过修复桌面端发布流程和更新 MiniMax 默认模型,整体发布稳定性更进一步。
## 产品更新
### Web
- 在 UI 中新增了图像提示词提取工作流。
- 让图像提取行为可以直接复用新的图像理解能力和提取模板。
### Core/Infra
- 在 Core 中新增图像理解服务以及面向提取场景的模板。
- 将默认 MiniMax 模型基线升级到 `M2.7`。
- 清理了发布自动化逻辑,避免桌面端构建任务意外自动发布 Release。
## 修复
- 修复了桌面端发布流程,避免构建任务自动发布 Release。
- 通过更新 MiniMax 默认模型,降低默认模型配置过旧带来的行为偏差。
## 破坏性变更 / 升级说明
- 无。这一版扩展的是图像能力,不需要额外迁移步骤。
## 开发者说明
- 如果你想追溯当前代码库里“图像提示词提取”正式落地的起点,这一版就是关键版本。
- 这一版也标志着图像理解能力从相对独立的逻辑,开始转向可复用、可发布的正式工作流。
+37
View File
@@ -0,0 +1,37 @@
# Prompt Optimizer v2.7.0
## Summary
- Added a structured compare evaluation workflow so prompt comparison is easier to test, calibrate, and trust.
- Smoothed the compare experience across test panels, fallback logic, and workspace reactivity.
- Refreshed docs, marketing surfaces, and extension assets so the project is easier to discover and explain.
## Highlights
- Introduced structured compare evaluation as a first-class workflow for prompt testing and analysis.
- Made compare UX feel lighter and more reliable across result panels and workspace state.
- Refreshed the public-facing surfaces with a standalone marketing site, updated demos, and localized extension store assets.
## Product Updates
### Web
- Added the structured compare workflow to test panels and simplified the compare evaluation experience.
- Kept workspace test state reactive across language switches and restore flows.
- Refined the About / resources area and removed compare hints when the current context does not support them.
### Extension
- Added localized store screenshots and refreshed derived logo assets for store-facing materials.
### Core/Infra
- Added a structured compare evaluation pipeline and calibration artifacts to support more stable comparison output.
- Refined evaluation rewrite behavior and aligned compare design docs with the new workflow.
- Improved docs build reliability while redesigning the onboarding and bilingual docs experience.
## Fixes
- Hid misleading teacher-style compare hints when no reference context is available.
- Fixed compare-version fallback to use the conversation record ID more reliably.
- Cleaned up MkDocs build issues that were blocking parts of the docs deployment flow.
## Breaking Changes / Upgrade Notes
- None. Existing data and workflows should continue to work without manual migration.
## Developer Notes
- This release is the main handoff point for the structured compare architecture. If you maintain evaluation prompts or compare-related UI, use this version as the new baseline.
- The commit range also includes marketing-site and documentation work, but the dominant product change is the structured compare stack.
+37
View File
@@ -0,0 +1,37 @@
# Prompt Optimizer v2.7.0
## 概括
- 新增了结构化对比评估工作流,让提示词对比更容易测试、校准和复用。
- 对比体验在测试面板、回退逻辑和工作区状态同步上都更顺了。
- 文档、官网展示和扩展商店素材也做了明显刷新,更方便理解和传播产品。
## 亮点
- 结构化对比评估正式成为一套完整工作流,可直接用于提示词测试与分析。
- 对比相关的交互体验更轻、更稳,结果面板和工作区状态协同也更自然。
- 项目的对外展示面焕然一新,包括独立营销站、更新后的演示内容,以及本地化扩展商店素材。
## 产品更新
### Web
- 在测试面板中加入了结构化对比工作流,并简化了整体对比评估体验。
- 让工作区测试状态在语言切换和恢复流程中保持响应式同步。
- 优化了 About / 资源区域,并在不具备参考上下文时隐藏容易误导的对比提示。
### Extension
- 补充了本地化商店截图,并刷新了面向商店展示的衍生 logo 资源。
### Core/Infra
- 新增结构化对比评估管线和校准产物,为更稳定的对比输出打下基础。
- 优化了评估重写行为,并让对比设计文档与新工作流保持一致。
- 在重做双语文档与 onboarding 体验的同时,也提升了文档构建稳定性。
## 修复
- 在缺少参考上下文时,隐藏了容易误导用户的 teacher 风格对比提示。
- 修复了对比版本回退逻辑,改为更稳定地使用会话记录 ID。
- 清理了影响文档部署流程的 MkDocs 构建问题。
## 破坏性变更 / 升级说明
- 无。现有数据与工作流默认不需要手动迁移。
## 开发者说明
- 这一版可以视为结构化对比架构的正式落点。如果你维护评估提示词或对比相关 UI,建议从这一版开始作为新基线。
- 这一段提交里也包含营销站和文档面的工作,但真正的产品主轴仍然是结构化对比能力。
+38
View File
@@ -0,0 +1,38 @@
# Prompt Optimizer v2.8.0
## Summary
- Expanded text-to-image evaluation with multimodal review, result inspection, and original-prompt analysis.
- Smoothed the reference-image workflow from dialog design and prompt migration to safer preview and download actions.
- Strengthened the platform foundation with Cloudflare-backed providers and a more reliable desktop packaging setup.
## Highlights
- Expanded the text-to-image workflow with multimodal evaluation, result review, and original-prompt analysis.
- Made reference-image workflows much smoother, from dialog design and prompt migration to safer preview and download actions.
- Strengthened the model and packaging foundations with new Cloudflare-backed providers and a more reliable desktop release setup.
## Product Updates
### Desktop
- Upgraded the desktop packaging toolchain to `electron-builder 26.8.2`.
- Removed repository-level Electron mirror defaults that could interfere with CI release builds.
### Web
- Redesigned the text-to-image reference dialog so style migration and reference replication feel more direct.
- Added the new text-to-image evaluation workflow to the UI, including analysis and evaluation entry points around image generation.
- Improved preview downloads, temporary-variable state persistence, and several smaller interaction details in headers, popovers, and navigation guards.
### Core/Infra
- Added Cloudflare-backed text and image providers, plus multimodal support for text-to-image evaluation.
- Rebuilt the reference-prompt pipeline to better support composition, migration, compare evaluation, and result evaluation flows.
- Normalized legacy template identifiers and wrapped evidence payloads in structured JSON blocks so evaluation behavior stays more consistent.
## Fixes
- Fixed the preview download hit area and normalized URL-based image results.
- Hardened safe image preview downloads and stabilized the Cloudflare-backed reference-image flow.
- Backfilled missing built-in model API keys and aligned evaluation fixtures with the updated image workflow.
## Breaking Changes / Upgrade Notes
- None. Existing saved data and current workspaces should continue to work without migration steps.
## Developer Notes
- This release reshapes a large part of the image prompt pipeline. If you maintain custom templates or evaluation prompts, expect more structured evidence blocks and more explicit stage-aware evaluation paths.
- The commit range from `v2.7.0` to `v2.8.0` also includes repo cleanup, dependency alignment, and documentation reshaping work, but the user-facing center of gravity is clearly the image evaluation stack.
+38
View File
@@ -0,0 +1,38 @@
# Prompt Optimizer v2.8.0
## 概括
- 文生图工作流进一步增强,补齐了多模态评估、结果评估和原始提示词分析能力。
- 参考图相关流程顺了很多,从对话框设计、提示词迁移到更安全的预览与下载都做了打磨。
- 模型接入和打包底座也更稳了,新增了基于 Cloudflare 的 provider,同时让桌面端发布链路更可靠。
## 亮点
- 文生图工作流进一步增强,补齐了多模态评估、结果评估和原始提示词分析能力。
- 参考图相关流程顺了很多,从对话框设计、提示词迁移到更安全的预览与下载都做了打磨。
- 模型接入和打包底座也更稳了,新增了基于 Cloudflare 的 provider,同时让桌面端发布链路更可靠。
## 产品更新
### Desktop
- 桌面端打包工具链升级到 `electron-builder 26.8.2`。
- 移除了仓库级 Electron mirror 默认配置,避免 CI 发布构建被错误环境变量干扰。
### Web
- 重做了文生图参考图对话框,让风格迁移和参考图复制这类操作更直接。
- 把新的文生图评估工作流接入到了 UI,包括围绕图片生成的分析和评估入口。
- 改进了预览下载体验、临时变量状态持久化,以及页头、浮层、路由守卫等一批交互细节。
### Core/Infra
- 新增了基于 Cloudflare 的文本与图像 Provider,并补齐多模态文生图评估能力。
- 重建了参考提示词处理链路,更好地支撑提示词组合、迁移、对比评估和结果评估。
- 规范了旧模板标识,并把证据内容包装成结构化 JSON 块,让评估行为更稳定一致。
## 修复
- 修复了预览下载按钮点击区域问题,并统一了 URL 图片结果的处理。
- 加固了安全图片预览下载流程,并稳定了基于 Cloudflare 的参考图链路。
- 回填了内置模型缺失的 API Key 配置,同时让评估夹具与新的图像工作流保持一致。
## 破坏性变更 / 升级说明
- 无。现有保存数据与当前工作区默认不需要额外迁移步骤。
## 开发者说明
- 这一版对图像提示词链路做了较大幅度重整。如果你维护自定义模板或评估提示词,需要关注更结构化的证据块,以及更明确的阶段感知评估路径。
- 从 `v2.7.0` 到 `v2.8.0` 的提交范围里也包含仓库清理、依赖对齐和文档整理,但真正面向用户的主轴还是图像评估能力的提升。
+679
View File
@@ -0,0 +1,679 @@
#!/usr/bin/env node
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const LOCALE_CONFIG = {
en: {
fileSuffix: 'en',
summaryHeading: '## Summary',
headings: [
'## Summary',
'## Highlights',
'## Product Updates',
'## Fixes',
'## Breaking Changes / Upgrade Notes',
'## Developer Notes',
],
productSectionHeading: '## Product Updates',
productSubsections: ['### Desktop', '### Web', '### Extension', '### Core/Infra'],
},
'zh-CN': {
fileSuffix: 'zh-CN',
summaryHeading: '## 概括',
headings: [
'## 概括',
'## 亮点',
'## 产品更新',
'## 修复',
'## 破坏性变更 / 升级说明',
'## 开发者说明',
],
productSectionHeading: '## 产品更新',
productSubsections: ['### Desktop', '### Web', '### Extension', '### Core/Infra'],
},
};
const PLACEHOLDER_PATTERNS = [/\bTODO\b/i, /\bTBD\b/i, /待补充/, /(^|[^A-Z])XX([^A-Z]|$)/];
function readRootPackage(cwd = process.cwd()) {
return JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
}
function normalizeVersion(value, cwd = process.cwd()) {
const candidate = String(value || readRootPackage(cwd).version).trim();
if (!/^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(candidate)) {
throw new Error(`Invalid version "${candidate}". Expected x.y.z or vx.y.z.`);
}
return candidate.startsWith('v') ? candidate.slice(1) : candidate;
}
function getTagVersion(version, cwd = process.cwd()) {
return `v${normalizeVersion(version, cwd)}`;
}
function getLocaleConfig(locale) {
const config = LOCALE_CONFIG[locale];
if (!config) {
throw new Error(`Unsupported locale "${locale}". Expected one of: ${Object.keys(LOCALE_CONFIG).join(', ')}.`);
}
return config;
}
function getReleaseNotesRelativePath(version, locale, cwd = process.cwd()) {
const normalizedVersion = normalizeVersion(version, cwd);
const config = getLocaleConfig(locale);
return `releases/v${normalizedVersion}.${config.fileSuffix}.md`;
}
function getReleaseNotesPath({ cwd = process.cwd(), version, locale }) {
return path.join(cwd, getReleaseNotesRelativePath(version, locale, cwd));
}
function getReleaseNotesPaths({ cwd = process.cwd(), version }) {
return {
en: getReleaseNotesPath({ cwd, version, locale: 'en' }),
zhCN: getReleaseNotesPath({ cwd, version, locale: 'zh-CN' }),
};
}
function stripHtmlComments(content) {
return content.replace(/<!--[\s\S]*?-->/g, '').trim();
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getHeadingIndex(block, heading) {
const match = new RegExp(`^${escapeRegExp(heading)}\\s*$`, 'm').exec(block);
return match ? match.index : -1;
}
function findSection(content, heading, nextHeading = null) {
const startIndex = getHeadingIndex(content, heading);
if (startIndex === -1) {
return null;
}
const bodyStart = content.indexOf('\n', startIndex);
if (bodyStart === -1) {
return content.slice(startIndex).trim();
}
if (!nextHeading) {
return content.slice(startIndex).trim();
}
const remaining = content.slice(bodyStart + 1);
const nextIndex = getHeadingIndex(remaining, nextHeading);
if (nextIndex === -1) {
return content.slice(startIndex).trim();
}
return content.slice(startIndex, bodyStart + 1 + nextIndex).trim();
}
function extractSectionBody(content, heading, nextHeading = null) {
const section = findSection(content, heading, nextHeading);
if (!section) {
return null;
}
const firstLineBreak = section.indexOf('\n');
if (firstLineBreak === -1) {
return '';
}
return section.slice(firstLineBreak + 1).trim();
}
function validateHeadingOrder(block, headings, label) {
const errors = [];
let lastIndex = -1;
for (const heading of headings) {
const headingIndex = getHeadingIndex(block, heading);
if (headingIndex === -1) {
errors.push(`${label} section is missing required heading "${heading}".`);
continue;
}
if (headingIndex < lastIndex) {
errors.push(`${label} section has heading "${heading}" out of order.`);
}
lastIndex = headingIndex;
}
return errors;
}
function validateProductSubsectionOrder(content, locale) {
const config = getLocaleConfig(locale);
const label = locale === 'en' ? 'English' : '中文';
const nextHeading = config.headings[config.headings.indexOf(config.productSectionHeading) + 1] ?? null;
const productSection = findSection(content, config.productSectionHeading, nextHeading);
if (!productSection) {
return [];
}
const errors = [];
let lastIndex = -1;
let previousHeading = null;
for (const heading of config.productSubsections) {
const headingIndex = getHeadingIndex(productSection, heading);
if (headingIndex === -1) {
continue;
}
if (headingIndex < lastIndex) {
errors.push(`${label} Product Updates subsection "${heading}" must appear after "${previousHeading}".`);
}
lastIndex = headingIndex;
previousHeading = heading;
}
return errors;
}
function findPlaceholderMatches(content) {
const matches = [];
for (const pattern of PLACEHOLDER_PATTERNS) {
const matched = content.match(pattern);
if (matched) {
matches.push(matched[0]);
}
}
return [...new Set(matches)];
}
function validateReleaseNotesContent(content, version, locale, options = {}) {
const config = getLocaleConfig(locale);
const requireSummary = options.requireSummary !== false;
const errors = [];
const normalizedContent = stripHtmlComments(content).replace(/\r\n/g, '\n');
const expectedTitle = `# Prompt Optimizer ${getTagVersion(version)}`;
if (!new RegExp(`^${escapeRegExp(expectedTitle)}\\s*$`, 'm').test(normalizedContent)) {
errors.push(`Release notes title must be "${expectedTitle}" in ${locale}.`);
}
errors.push(...validateHeadingOrder(normalizedContent, config.headings, locale === 'en' ? 'English' : '中文'));
errors.push(...validateProductSubsectionOrder(normalizedContent, locale));
if (requireSummary) {
const summaryBody = extractSectionBody(
normalizedContent,
config.summaryHeading,
config.headings[1] ?? null
);
if (!summaryBody) {
errors.push(`Release notes must include a non-empty "${config.summaryHeading}" section in ${locale}.`);
}
}
const placeholders = findPlaceholderMatches(normalizedContent);
if (placeholders.length > 0) {
errors.push(
`Release notes contain placeholder content in ${locale}: ${placeholders.join(', ')}. Replace placeholders before publishing.`
);
}
return errors;
}
function validateChangelogEntry(changelogContent, version, options = {}) {
const requireTopEntry = options.requireTopEntry !== false;
const errors = [];
const normalizedContent = changelogContent.replace(/\r\n/g, '\n');
const headingMatches = [...normalizedContent.matchAll(/^## \[([^\]]+)\] - .+$/gm)];
if (headingMatches.length === 0) {
return ['CHANGELOG.md must include at least one version entry.'];
}
const topEntry = headingMatches[0];
const topVersion = topEntry[1];
const normalizedVersion = normalizeVersion(version);
if (requireTopEntry && topVersion !== normalizedVersion) {
errors.push(`CHANGELOG.md top entry must be version ${normalizedVersion}.`);
}
const matchedEntry = requireTopEntry ? topEntry : headingMatches.find((match) => match[1] === normalizedVersion);
if (!matchedEntry) {
errors.push(`CHANGELOG.md must include version ${normalizedVersion}.`);
return errors;
}
const matchedEntryIndex = headingMatches.findIndex((match) => match.index === matchedEntry.index);
const entryStart = matchedEntry.index ?? 0;
const nextEntryStart = headingMatches[matchedEntryIndex + 1]?.index ?? normalizedContent.length;
const matchedEntryBlock = normalizedContent.slice(entryStart, nextEntryStart);
const englishPath = getReleaseNotesRelativePath(version, 'en');
const chinesePath = getReleaseNotesRelativePath(version, 'zh-CN');
if (!matchedEntryBlock.includes(englishPath)) {
errors.push(
requireTopEntry
? `CHANGELOG.md top entry must link to ${englishPath}.`
: `CHANGELOG.md entry for version ${normalizedVersion} must link to ${englishPath}.`
);
}
if (!matchedEntryBlock.includes(chinesePath)) {
errors.push(
requireTopEntry
? `CHANGELOG.md top entry must link to ${chinesePath}.`
: `CHANGELOG.md entry for version ${normalizedVersion} must link to ${chinesePath}.`
);
}
if (!/^- EN:/m.test(matchedEntryBlock)) {
errors.push(
requireTopEntry
? 'CHANGELOG.md top entry must include an English summary line prefixed with "- EN:".'
: `CHANGELOG.md entry for version ${normalizedVersion} must include an English summary line prefixed with "- EN:".`
);
}
if (!/^- 中文[::]/m.test(matchedEntryBlock)) {
errors.push(
requireTopEntry
? 'CHANGELOG.md top entry must include a Chinese summary line prefixed with "- 中文:".'
: `CHANGELOG.md entry for version ${normalizedVersion} must include a Chinese summary line prefixed with "- 中文:".`
);
}
return errors;
}
function validateReleaseArtifacts({ cwd = process.cwd(), version, requireSummary = true, requireTopEntry = true }) {
const normalizedVersion = normalizeVersion(version, cwd);
const filePaths = getReleaseNotesPaths({ cwd, version: normalizedVersion });
const errors = [];
for (const [locale, filePath] of [
['en', filePaths.en],
['zh-CN', filePaths.zhCN],
]) {
if (!fs.existsSync(filePath)) {
errors.push(`Release notes file is missing: ${path.relative(cwd, filePath).replace(/\\/g, '/')}.`);
continue;
}
const content = fs.readFileSync(filePath, 'utf8');
errors.push(...validateReleaseNotesContent(content, normalizedVersion, locale, { requireSummary }));
}
const changelogPath = path.join(cwd, 'CHANGELOG.md');
if (!fs.existsSync(changelogPath)) {
errors.push('CHANGELOG.md is missing.');
} else {
const changelogContent = fs.readFileSync(changelogPath, 'utf8');
errors.push(...validateChangelogEntry(changelogContent, normalizedVersion, { requireTopEntry }));
}
return {
ok: errors.length === 0,
errors,
filePaths,
version: normalizedVersion,
};
}
function safeExecFileSync(command, args, options) {
try {
return execFileSync(command, args, options);
} catch {
return null;
}
}
function buildCommitDraft(cwd, version) {
const currentTag = getTagVersion(version, cwd);
const tagsOutput = safeExecFileSync('git', ['tag', '--sort=-version:refname'], {
cwd,
encoding: 'utf8',
});
if (!tagsOutput) {
return [];
}
const tags = tagsOutput
.split(/\r?\n/)
.map((tag) => tag.trim())
.filter(Boolean);
const currentTagIndex = tags.indexOf(currentTag);
const previousTag = currentTagIndex === -1 ? tags[0] : tags[currentTagIndex + 1];
const currentTagExists = Boolean(
safeExecFileSync('git', ['rev-parse', '--verify', `refs/tags/${currentTag}`], {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
);
const rangeEnd = currentTagExists ? currentTag : 'HEAD';
const logArgs = previousTag
? ['log', '--pretty=format:- %s (%h)', `${previousTag}..${rangeEnd}`]
: ['log', '--pretty=format:- %s (%h)', '--max-count=20'];
const logOutput = safeExecFileSync('git', logArgs, { cwd, encoding: 'utf8' });
if (!logOutput) {
return [];
}
const commits = logOutput
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.slice(0, 20);
if (commits.length === 0) {
return previousTag
? [`- No commits found between ${previousTag} and ${rangeEnd}.`]
: ['- No commits found.'];
}
return previousTag ? [`- Range: ${previousTag}..${rangeEnd}`, ...commits] : commits;
}
function buildReleaseNotesTemplate({ version, locale, commitDraft = [] }) {
const normalizedVersion = normalizeVersion(version);
const config = getLocaleConfig(locale);
const draftingLines =
commitDraft.length > 0
? commitDraft.join('\n')
: '- No commit draft available. You can still write the notes manually.';
if (locale === 'en') {
return `# Prompt Optimizer ${getTagVersion(normalizedVersion)}
## Summary
- TODO: Add 2-4 concise bullets for the GitHub Release summary.
## Highlights
- TODO: Summarize the most important user-facing change in English.
## Product Updates
### Desktop
- TODO: Add desktop-specific changes or remove this subsection if none.
### Web
- TODO: Add web-specific changes or remove this subsection if none.
### Extension
- TODO: Add extension-specific changes or remove this subsection if none.
### Core/Infra
- TODO: Add shared platform or infrastructure changes or remove this subsection if none.
## Fixes
- TODO: Capture the most relevant fixes in English.
## Breaking Changes / Upgrade Notes
- TODO: Describe required upgrade actions, or write "None." when nothing changed.
## Developer Notes
- TODO: Capture implementation-facing details that are worth surfacing publicly.
<!-- Release drafting reference:
Remove every TODO placeholder before running "pnpm release:notes:check ${getTagVersion(normalizedVersion)}".
${draftingLines}
-->
`;
}
return `# Prompt Optimizer ${getTagVersion(normalizedVersion)}
## 概括
- TODO: 用 2-4 条简短要点概括本次发布,供 GitHub Release 直接引用。
## 亮点
- TODO: 用中文总结本次发布最重要的用户价值。
## 产品更新
### Desktop
- TODO: 填写桌面端相关变化;如果没有,可以删除这个小节。
### Web
- TODO: 填写 Web 端相关变化;如果没有,可以删除这个小节。
### Extension
- TODO: 填写扩展端相关变化;如果没有,可以删除这个小节。
### Core/Infra
- TODO: 填写共享基础设施或核心能力变化;如果没有,可以删除这个小节。
## 修复
- TODO: 用中文补充最重要的修复。
## 破坏性变更 / 升级说明
- TODO: 说明升级动作;如果没有,请写“无”。
## 开发者说明
- TODO: 补充对开发者或自托管用户有价值的实现说明。
<!-- Release drafting reference:
Remove every TODO placeholder before running "pnpm release:notes:check ${getTagVersion(normalizedVersion)}".
${draftingLines}
-->
`;
}
function createReleaseNotesFiles({ cwd = process.cwd(), version, commitDraft = [], force = false }) {
const normalizedVersion = normalizeVersion(version, cwd);
const filePaths = getReleaseNotesPaths({ cwd, version: normalizedVersion });
for (const filePath of [filePaths.en, filePaths.zhCN]) {
if (fs.existsSync(filePath) && !force) {
throw new Error(`Release notes already exist at ${path.relative(cwd, filePath).replace(/\\/g, '/')}.`);
}
}
fs.mkdirSync(path.dirname(filePaths.en), { recursive: true });
fs.writeFileSync(
filePaths.en,
buildReleaseNotesTemplate({ version: normalizedVersion, locale: 'en', commitDraft }),
'utf8'
);
fs.writeFileSync(
filePaths.zhCN,
buildReleaseNotesTemplate({ version: normalizedVersion, locale: 'zh-CN', commitDraft }),
'utf8'
);
return {
created: true,
filePaths,
version: normalizedVersion,
};
}
function buildTagScopedFileUrl(repository, tag, relativePath) {
return `https://github.com/${repository}/blob/${tag}/${relativePath}`;
}
function renderMacSecurityNote(locale) {
if (locale === 'en') {
return [
'### macOS Security Note',
'If macOS says the app is "damaged" or the developer cannot be verified, this is usually a system quarantine warning rather than an app defect.',
'',
'```bash',
'xattr -rd com.apple.quarantine /Applications/PromptOptimizer.app',
'```',
'',
'For a downloaded DMG:',
'',
'```bash',
'xattr -rd com.apple.quarantine ~/Downloads/PromptOptimizer-*.dmg',
'```',
].join('\n');
}
return [
'### macOS 安全提示',
'如果 macOS 提示“已损坏”或“无法验证开发者”,通常是系统隔离属性导致,并不一定是应用本身有问题。',
'',
'```bash',
'xattr -rd com.apple.quarantine /Applications/PromptOptimizer.app',
'```',
'',
'如果是刚下载的 DMG:',
'',
'```bash',
'xattr -rd com.apple.quarantine ~/Downloads/PromptOptimizer-*.dmg',
'```',
].join('\n');
}
function renderGitHubReleaseBody({ cwd = process.cwd(), version, repository }) {
const normalizedVersion = normalizeVersion(version, cwd);
const tag = getTagVersion(normalizedVersion);
const englishPath = getReleaseNotesPath({ cwd, version: normalizedVersion, locale: 'en' });
const chinesePath = getReleaseNotesPath({ cwd, version: normalizedVersion, locale: 'zh-CN' });
const englishContent = fs.readFileSync(englishPath, 'utf8').replace(/\r\n/g, '\n').trim();
const chineseContent = fs.readFileSync(chinesePath, 'utf8').replace(/\r\n/g, '\n').trim();
const englishGuideUrl = buildTagScopedFileUrl(repository, tag, 'mkdocs/docs/en/deployment/desktop.md');
const chineseGuideUrl = buildTagScopedFileUrl(repository, tag, 'mkdocs/docs/zh/deployment/desktop.md');
const englishSummary = extractSectionBody(englishContent, '## Summary', '## Highlights');
const chineseSummary = extractSectionBody(chineseContent, '## 概括', '## 亮点');
if (englishSummary && chineseSummary) {
return [
'## English',
'',
'### Summary',
englishSummary,
'',
renderMacSecurityNote('en'),
'',
`Installation guide: [English](${englishGuideUrl}) | [中文](${chineseGuideUrl})`,
`[Full release notes (EN)](${buildTagScopedFileUrl(repository, tag, getReleaseNotesRelativePath(normalizedVersion, 'en', cwd))})`,
'',
'---',
'',
'## 中文',
'',
'### 概括',
chineseSummary,
'',
renderMacSecurityNote('zh-CN'),
'',
`安装文档:[English](${englishGuideUrl}) | [中文](${chineseGuideUrl})`,
`[完整发布说明(中文)](${buildTagScopedFileUrl(repository, tag, getReleaseNotesRelativePath(normalizedVersion, 'zh-CN', cwd))})`,
'',
].join('\n');
}
return [englishContent, '', '---', '', chineseContent, ''].join('\n');
}
function printUsage() {
console.log('Usage: node scripts/release-notes.js <new|check|check-entry|render-body> [version] [repository]');
console.log('Examples:');
console.log(' pnpm release:notes:new 2.9.0');
console.log(' pnpm release:notes:check v2.9.0');
console.log(' pnpm release:notes:check:entry v2.6.0');
console.log(' node scripts/release-notes.js render-body v2.9.0 linshenkx/prompt-optimizer');
}
function main(argv = process.argv.slice(2), cwd = process.cwd()) {
const [command, versionArg, repositoryArg] = argv;
if (!command || command === '--help' || command === '-h') {
printUsage();
return;
}
const version = normalizeVersion(versionArg, cwd);
if (command === 'new') {
const result = createReleaseNotesFiles({
cwd,
version,
commitDraft: buildCommitDraft(cwd, version),
});
console.log(
`Created ${path.relative(cwd, result.filePaths.en).replace(/\\/g, '/')} and ${path
.relative(cwd, result.filePaths.zhCN)
.replace(/\\/g, '/')} for ${getTagVersion(version)}.`
);
console.log('Next steps: update both files and add the matching top entry in CHANGELOG.md.');
return;
}
if (command === 'check') {
const result = validateReleaseArtifacts({ cwd, version, requireSummary: true });
if (!result.ok) {
console.error(`Release notes validation failed for ${getTagVersion(version)}:`);
for (const error of result.errors) {
console.error(`- ${error}`);
}
process.exitCode = 1;
return;
}
console.log(`Release notes validation passed for ${getTagVersion(version)}.`);
return;
}
if (command === 'check-entry') {
const result = validateReleaseArtifacts({
cwd,
version,
requireSummary: true,
requireTopEntry: false,
});
if (!result.ok) {
console.error(`Release notes entry validation failed for ${getTagVersion(version)}:`);
for (const error of result.errors) {
console.error(`- ${error}`);
}
process.exitCode = 1;
return;
}
console.log(`Release notes entry validation passed for ${getTagVersion(version)}.`);
return;
}
if (command === 'render-body') {
if (!repositoryArg) {
throw new Error('render-body requires a repository argument like "linshenkx/prompt-optimizer".');
}
process.stdout.write(renderGitHubReleaseBody({ cwd, version, repository: repositoryArg }));
return;
}
throw new Error(`Unknown command "${command}".`);
}
if (require.main === module) {
try {
main();
} catch (error) {
console.error(error.message || error);
process.exit(1);
}
}
module.exports = {
buildCommitDraft,
buildReleaseNotesTemplate,
createReleaseNotesFiles,
extractSectionBody,
getReleaseNotesPath,
getReleaseNotesPaths,
getReleaseNotesRelativePath,
getTagVersion,
main,
normalizeVersion,
renderGitHubReleaseBody,
renderMacSecurityNote,
validateChangelogEntry,
validateReleaseArtifacts,
validateReleaseNotesContent,
};
+324
View File
@@ -0,0 +1,324 @@
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 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]+\)$/);
});