From bd546ebe0f62dba57b58f2b650f2347c20988cc1 Mon Sep 17 00:00:00 2001 From: linshen <32978552+linshenkx@users.noreply.github.com> Date: Sun, 5 Apr 2026 08:19:36 +0800 Subject: [PATCH] build(release): add repo-driven bilingual release notes --- .github/workflows/release.yml | 223 ++--------- CHANGELOG.md | 36 +- docs/project/README.md | 1 + docs/project/release-notes.md | 136 +++++++ docs/project/version-sync.md | 19 +- package.json | 14 +- releases/v2.5.4.en.md | 34 ++ releases/v2.5.4.zh-CN.md | 34 ++ releases/v2.5.5.en.md | 35 ++ releases/v2.5.5.zh-CN.md | 35 ++ releases/v2.6.0.en.md | 35 ++ releases/v2.6.0.zh-CN.md | 35 ++ releases/v2.6.1.en.md | 35 ++ releases/v2.6.1.zh-CN.md | 35 ++ releases/v2.6.2.en.md | 32 ++ releases/v2.6.2.zh-CN.md | 32 ++ releases/v2.6.3.en.md | 32 ++ releases/v2.6.3.zh-CN.md | 32 ++ releases/v2.7.0.en.md | 37 ++ releases/v2.7.0.zh-CN.md | 37 ++ releases/v2.8.0.en.md | 38 ++ releases/v2.8.0.zh-CN.md | 38 ++ scripts/release-notes.js | 679 +++++++++++++++++++++++++++++++++ scripts/release-notes.test.mjs | 324 ++++++++++++++++ 24 files changed, 1799 insertions(+), 189 deletions(-) create mode 100644 docs/project/release-notes.md create mode 100644 releases/v2.5.4.en.md create mode 100644 releases/v2.5.4.zh-CN.md create mode 100644 releases/v2.5.5.en.md create mode 100644 releases/v2.5.5.zh-CN.md create mode 100644 releases/v2.6.0.en.md create mode 100644 releases/v2.6.0.zh-CN.md create mode 100644 releases/v2.6.1.en.md create mode 100644 releases/v2.6.1.zh-CN.md create mode 100644 releases/v2.6.2.en.md create mode 100644 releases/v2.6.2.zh-CN.md create mode 100644 releases/v2.6.3.en.md create mode 100644 releases/v2.6.3.zh-CN.md create mode 100644 releases/v2.7.0.en.md create mode 100644 releases/v2.7.0.zh-CN.md create mode 100644 releases/v2.8.0.en.md create mode 100644 releases/v2.8.0.zh-CN.md create mode 100644 scripts/release-notes.js create mode 100644 scripts/release-notes.test.mjs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30dd83c7..4dc1deac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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/* diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e37b456..583a6675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ - 基础收藏管理功能 - 优化历史集成 - 标签和分类基础支持 -- 导入导出功能 \ No newline at end of file +- 导入导出功能 diff --git a/docs/project/README.md b/docs/project/README.md index 944eadb1..ff6ced1e 100644 --- a/docs/project/README.md +++ b/docs/project/README.md @@ -7,6 +7,7 @@ - [产品需求文档](./prd.md) - 产品功能需求和规格说明 - [项目状态](./project-status.md) - 当前项目进度和完成情况 - [版本同步](./version-sync.md) - 版本管理和同步策略 +- [版本说明与 Release 发布](./release-notes.md) - 双语版本说明与发布流程 ## 📊 项目状态 diff --git a/docs/project/release-notes.md b/docs/project/release-notes.md new file mode 100644 index 00000000..224f6e3c --- /dev/null +++ b/docs/project/release-notes.md @@ -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 `。它会沿用同样的正文结构校验,但把 `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 不会创建成功 diff --git a/docs/project/version-sync.md b/docs/project/version-sync.md index 44b4ecaa..743d57d3 100644 --- a/docs/project/version-sync.md +++ b/docs/project/version-sync.md @@ -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 -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index fadbc6c8..e68ba30d 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/releases/v2.5.4.en.md b/releases/v2.5.4.en.md new file mode 100644 index 00000000..877f505e --- /dev/null +++ b/releases/v2.5.4.en.md @@ -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. diff --git a/releases/v2.5.4.zh-CN.md b/releases/v2.5.4.zh-CN.md new file mode 100644 index 00000000..51bdacee --- /dev/null +++ b/releases/v2.5.4.zh-CN.md @@ -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` 可以看作评估体系从“单向打分展示”转向“反馈驱动闭环”的起点版本。 +- 如果你维护自定义评估提示词或结果消费逻辑,这一版是更强反馈信号和更防御式结果解析的合适基线。 diff --git a/releases/v2.5.5.en.md b/releases/v2.5.5.en.md new file mode 100644 index 00000000..b156edc2 --- /dev/null +++ b/releases/v2.5.5.en.md @@ -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. diff --git a/releases/v2.5.5.zh-CN.md b/releases/v2.5.5.zh-CN.md new file mode 100644 index 00000000..1d385488 --- /dev/null +++ b/releases/v2.5.5.zh-CN.md @@ -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` 视为带资源引用收藏导入的基线版本。 diff --git a/releases/v2.6.0.en.md b/releases/v2.6.0.en.md new file mode 100644 index 00000000..3b488c34 --- /dev/null +++ b/releases/v2.6.0.en.md @@ -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. diff --git a/releases/v2.6.0.zh-CN.md b/releases/v2.6.0.zh-CN.md new file mode 100644 index 00000000..40770159 --- /dev/null +++ b/releases/v2.6.0.zh-CN.md @@ -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` 为基线。 diff --git a/releases/v2.6.1.en.md b/releases/v2.6.1.en.md new file mode 100644 index 00000000..a6ef5bb3 --- /dev/null +++ b/releases/v2.6.1.en.md @@ -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. diff --git a/releases/v2.6.1.zh-CN.md b/releases/v2.6.1.zh-CN.md new file mode 100644 index 00000000..4e1765ce --- /dev/null +++ b/releases/v2.6.1.zh-CN.md @@ -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 的时间点,这一版就是关键版本。 diff --git a/releases/v2.6.2.en.md b/releases/v2.6.2.en.md new file mode 100644 index 00000000..9e1267eb --- /dev/null +++ b/releases/v2.6.2.en.md @@ -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. diff --git a/releases/v2.6.2.zh-CN.md b/releases/v2.6.2.zh-CN.md new file mode 100644 index 00000000..9371dac8 --- /dev/null +++ b/releases/v2.6.2.zh-CN.md @@ -0,0 +1,32 @@ +# Prompt Optimizer v2.6.2 + +## 概括 +- 重构了评估架构,让工作区、结果页和对比流程之间的关系更清晰。 +- 分析与评估流程做了成体系整理,整体交互在不同测试路径上更一致。 +- 变量处理和评估覆盖也一起补强了,特别是 Pro Variable 场景下更稳。 + +## 亮点 +- 底层评估逻辑围绕新的协议和模板构建器重新梳理了一遍。 +- 分析、结果查看和对比评估在 UI 层终于更自然地串起来了。 +- 变量较多的场景更安全了,尤其是 Pro Variable 的结果评估输出更干净。 + +## 产品更新 +### Web +- 围绕工作区、结果和对比状态,重构了分析与评估流程。 +- 优化了从提示词编写、分析到对比的整体评估路径,让流程切换更清楚。 + +### Core/Infra +- 重新设计了应用底层的评估协议和模板构建器。 +- 刷新了单元测试、E2E 和 VCR 覆盖,使其与新的协议结构一致。 +- 归并了工作区评估相关文档和验收证据,减少旧流程与新流程之间的割裂。 + +## 修复 +- 从 Pro Variable 的结果评估输出中移除了内部预定义变量。 +- 在评估协议重构后刷新了测试夹具与覆盖,提升了行为稳定性。 + +## 破坏性变更 / 升级说明 +- 对终端用户无破坏性变更;但从内部实现看,评估协议和模板行为发生了较大调整。 + +## 开发者说明 +- 这一版可以看作评估链路的一个架构转折点。若要比较“旧评估流”和“新工作区 / 结果 / 对比模型”,`v2.6.2` 是关键界线。 +- 维护评估提示词或结果面板的同学,从这一版开始需要接受更明确的流程边界。 diff --git a/releases/v2.6.3.en.md b/releases/v2.6.3.en.md new file mode 100644 index 00000000..00f6b15a --- /dev/null +++ b/releases/v2.6.3.en.md @@ -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. diff --git a/releases/v2.6.3.zh-CN.md b/releases/v2.6.3.zh-CN.md new file mode 100644 index 00000000..623c45b2 --- /dev/null +++ b/releases/v2.6.3.zh-CN.md @@ -0,0 +1,32 @@ +# Prompt Optimizer v2.6.3 + +## 概括 +- 新增图像提示词提取能力,可以把已有图片重新转成可复用的提示词素材。 +- Core 层补上了更完整的图像理解服务与提取模板支撑。 +- 桌面端发布链路更稳了,同时也更新了默认 MiniMax 模型基线。 + +## 亮点 +- 图像提示词提取正式成为一套工作流,更适合从生成图或参考图中反向整理提示词。 +- 核心图像理解能力进一步补强,为提取流程提供了更完整的底层支持。 +- 通过修复桌面端发布流程和更新 MiniMax 默认模型,整体发布稳定性更进一步。 + +## 产品更新 +### Web +- 在 UI 中新增了图像提示词提取工作流。 +- 让图像提取行为可以直接复用新的图像理解能力和提取模板。 + +### Core/Infra +- 在 Core 中新增图像理解服务以及面向提取场景的模板。 +- 将默认 MiniMax 模型基线升级到 `M2.7`。 +- 清理了发布自动化逻辑,避免桌面端构建任务意外自动发布 Release。 + +## 修复 +- 修复了桌面端发布流程,避免构建任务自动发布 Release。 +- 通过更新 MiniMax 默认模型,降低默认模型配置过旧带来的行为偏差。 + +## 破坏性变更 / 升级说明 +- 无。这一版扩展的是图像能力,不需要额外迁移步骤。 + +## 开发者说明 +- 如果你想追溯当前代码库里“图像提示词提取”正式落地的起点,这一版就是关键版本。 +- 这一版也标志着图像理解能力从相对独立的逻辑,开始转向可复用、可发布的正式工作流。 diff --git a/releases/v2.7.0.en.md b/releases/v2.7.0.en.md new file mode 100644 index 00000000..17085815 --- /dev/null +++ b/releases/v2.7.0.en.md @@ -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. diff --git a/releases/v2.7.0.zh-CN.md b/releases/v2.7.0.zh-CN.md new file mode 100644 index 00000000..96ddbc6d --- /dev/null +++ b/releases/v2.7.0.zh-CN.md @@ -0,0 +1,37 @@ +# Prompt Optimizer v2.7.0 + +## 概括 +- 新增了结构化对比评估工作流,让提示词对比更容易测试、校准和复用。 +- 对比体验在测试面板、回退逻辑和工作区状态同步上都更顺了。 +- 文档、官网展示和扩展商店素材也做了明显刷新,更方便理解和传播产品。 + +## 亮点 +- 结构化对比评估正式成为一套完整工作流,可直接用于提示词测试与分析。 +- 对比相关的交互体验更轻、更稳,结果面板和工作区状态协同也更自然。 +- 项目的对外展示面焕然一新,包括独立营销站、更新后的演示内容,以及本地化扩展商店素材。 + +## 产品更新 +### Web +- 在测试面板中加入了结构化对比工作流,并简化了整体对比评估体验。 +- 让工作区测试状态在语言切换和恢复流程中保持响应式同步。 +- 优化了 About / 资源区域,并在不具备参考上下文时隐藏容易误导的对比提示。 + +### Extension +- 补充了本地化商店截图,并刷新了面向商店展示的衍生 logo 资源。 + +### Core/Infra +- 新增结构化对比评估管线和校准产物,为更稳定的对比输出打下基础。 +- 优化了评估重写行为,并让对比设计文档与新工作流保持一致。 +- 在重做双语文档与 onboarding 体验的同时,也提升了文档构建稳定性。 + +## 修复 +- 在缺少参考上下文时,隐藏了容易误导用户的 teacher 风格对比提示。 +- 修复了对比版本回退逻辑,改为更稳定地使用会话记录 ID。 +- 清理了影响文档部署流程的 MkDocs 构建问题。 + +## 破坏性变更 / 升级说明 +- 无。现有数据与工作流默认不需要手动迁移。 + +## 开发者说明 +- 这一版可以视为结构化对比架构的正式落点。如果你维护评估提示词或对比相关 UI,建议从这一版开始作为新基线。 +- 这一段提交里也包含营销站和文档面的工作,但真正的产品主轴仍然是结构化对比能力。 diff --git a/releases/v2.8.0.en.md b/releases/v2.8.0.en.md new file mode 100644 index 00000000..04f9f1c8 --- /dev/null +++ b/releases/v2.8.0.en.md @@ -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. diff --git a/releases/v2.8.0.zh-CN.md b/releases/v2.8.0.zh-CN.md new file mode 100644 index 00000000..fc0a3c25 --- /dev/null +++ b/releases/v2.8.0.zh-CN.md @@ -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` 的提交范围里也包含仓库清理、依赖对齐和文档整理,但真正面向用户的主轴还是图像评估能力的提升。 diff --git a/scripts/release-notes.js b/scripts/release-notes.js new file mode 100644 index 00000000..e9ddf330 --- /dev/null +++ b/scripts/release-notes.js @@ -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(//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. + + +`; + } + + return `# Prompt Optimizer ${getTagVersion(normalizedVersion)} + +## 概括 +- TODO: 用 2-4 条简短要点概括本次发布,供 GitHub Release 直接引用。 + +## 亮点 +- TODO: 用中文总结本次发布最重要的用户价值。 + +## 产品更新 +### Desktop +- TODO: 填写桌面端相关变化;如果没有,可以删除这个小节。 +### Web +- TODO: 填写 Web 端相关变化;如果没有,可以删除这个小节。 +### Extension +- TODO: 填写扩展端相关变化;如果没有,可以删除这个小节。 +### Core/Infra +- TODO: 填写共享基础设施或核心能力变化;如果没有,可以删除这个小节。 + +## 修复 +- TODO: 用中文补充最重要的修复。 + +## 破坏性变更 / 升级说明 +- TODO: 说明升级动作;如果没有,请写“无”。 + +## 开发者说明 +- TODO: 补充对开发者或自托管用户有价值的实现说明。 + + +`; +} + +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 [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, +}; diff --git a/scripts/release-notes.test.mjs b/scripts/release-notes.test.mjs new file mode 100644 index 00000000..fff1f2e6 --- /dev/null +++ b/scripts/release-notes.test.mjs @@ -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]+\)$/); +});