Skip to content

Update CI workflow, docs, and implement an auto-release process#4

Open
WSXYT wants to merge 14 commits intobetafrom
dev
Open

Update CI workflow, docs, and implement an auto-release process#4
WSXYT wants to merge 14 commits intobetafrom
dev

Conversation

@WSXYT
Copy link
Owner

@WSXYT WSXYT commented Mar 2, 2026

Summary of Changes

  • CI Workflows:

    • Updated workflows to support triggering on multiple branches (dev, beta, and master).
    • Improved document synchronization:
      • Trigger now activates on beta and master branch pushes.
      • Adjusted fetch-depth to 0 and introduced base_branch parameter for branch diff analysis.
      • Enhanced output clarity for sync tasks, including better handling of push changes.
    • Migrated to full platform matrix builds and implemented concurrent job cancellation.
    • Addressed CI/CD pipeline failures and specified win-x64 architecture to fix build issues.
  • Documentation:

    • Added a development and contribution guide, referenced in the README.
  • Automation:

    • Established an auto-release process for dev, beta, and master branches.

Please verify:

  • CI workflows are running as expected for all supported branches.
  • New documentation adheres to project standards and is accessible.
  • Auto-release process is functioning correctly across designated branches.

WSXYT and others added 6 commits March 2, 2026 16:44
- 扩展分支限制,添加对 dev 和 beta 分支的支持
- 文档同步任务触发条件从 PR 合并调整为针对 beta 和 master 分支的 push
- 调整 fetch-depth 为 0,并新增 `base_branch` 参数以支持分支差异分析
- 修订文档同步任务的提示与输出格式,优化 push 变更处理
Copilot AI review requested due to automatic review settings March 2, 2026 09:43
@github-actions
Copy link

github-actions bot commented Mar 2, 2026

Junie is failed!

Details: Check job logs for more details

View job run

- 在更新服务中引入 `IncludePrerelease` 常量,以控制是否包含预发布版本
- 更新 UI 文本,提示默认仅检查稳定版
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the repo’s CI/CD to support a devbetamaster branching model, adds contributor documentation, and introduces automated beta/stable release pipelines driven by VERSION.md.

Changes:

  • Added new CI workflow and expanded existing automation triggers to run across dev, beta, and master.
  • Implemented automated beta releases (beta-release.yml) and stable releases (release.yml) with generated release notes from VERSION.md.
  • Added development/contribution documentation (CONTRIBUTING.md) and linked it from the README.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
VERSION.md Introduces a structured, release-driven version/changelog source.
README.md Links to the new contribution/development guide.
PrismCore.csproj Sets a default Platform to x64 when unspecified (CI/build stabilization).
CONTRIBUTING.md Documents branching, versioning (VERSION.md format), and release rules.
.github/workflows/release.yml New master-only stable release pipeline + dev changelog cleanup job.
.github/workflows/beta-release.yml New beta-only prerelease pipeline.
.github/workflows/ci.yml New matrix CI build for dev/beta/master pushes and PRs.
.github/workflows/junie.yml Adjusts triggers and switches docs-sync to run on push to beta/master.
.github/scripts/versionmd.sh Adds parsing/extraction utilities for VERSION.md and note generation.
.github/scripts/prepare-master.sh Generates stable release metadata/notes and release gating logic.
.github/scripts/prepare-beta.sh Generates beta tag/version and delta release notes vs prior beta tag.
.github/scripts/cleanup-dev.sh Automates removing released entries from dev’s VERSION.md after stable release.
.github/release-body-footer.md Provides a shared footer appended to release notes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


- name: 下载安装包
uses: actions/download-artifact@v4
with:
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前这一步会下载本次工作流的“所有” artifacts(包括 prepare 阶段上传的 release-notes / released-entries)。由于后面发布步骤使用 files: artifacts/*,这些说明文件也会被当作 Release 资产上传,导致 Release 里出现多余的 .md/.txt 资产。建议仅下载打包产物(例如使用 download-artifact 的 pattern: release-* / 指定 name,或把说明 artifacts 下载到单独目录并在 files 中排除)。

Suggested change
with:
with:
pattern: release-*

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +126
- name: 下载安装包
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里会下载本次工作流的“所有” artifacts(包含 prepare 阶段上传的 beta-release-notes)。后续 files: artifacts/* 会把这些说明 .md 也作为 Release 资产上传,产生多余资产。建议只下载打包产物(例如 pattern: beta-* / 指定 name),把发布说明单独下载到 notes 目录即可。

Copilot uses AI. Check for mistakes.
- 将模型配置从硬编码改为使用环境变量 `JUNIE_MODEL`
- 简化和统一模型参数管理,提升工作流可维护性
return 1
fi

echo "$version_line" | sed -E 's/^Version:[[:space:]]*//'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
echo "$version_line" | sed -E 's/^Version:[[:space:]]*//'
echo "$version_line" | sed -E 's/^Version:[[:space:]]*//; s/[[:space:]]*$//'

The grep allows trailing whitespace ([[:space:]]*$) but sed only strips the prefix, leaving potential trailing spaces in the version string used for tag names.

Comment on lines +30 to +36
if [[ "$line" =~ ^##[[:space:]]+待发布 ]]; then
in_unreleased="true"
current_section=""
continue
fi

if [[ "$line" =~ ^##[[:space:]]+ ]] && [[ "$line" != "## 待发布" ]]; then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [[ "$line" =~ ^##[[:space:]]+待发布 ]]; then
in_unreleased="true"
current_section=""
continue
fi
if [[ "$line" =~ ^##[[:space:]]+ ]] && [[ "$line" != "## 待发布" ]]; then
if [[ "$line" =~ ^##[[:space:]]+待发布[[:space:]]*$ ]]; then
in_unreleased="true"
current_section=""
continue
fi
if [[ "$line" =~ ^##[[:space:]]+ ]] && [[ "$line" != "## 待发布" ]]; then

The first regex is a prefix match (^##[[:space:]]+待发布) — a heading like ## 待发布xxx would incorrectly enter the unreleased block. Anchor it to end-of-string for safety.

- 为 release 和 beta 工作流新增 `pattern` 参数,分别支持匹配 `release-win-*` 和 `beta-win-*` 文件
- 修正 `versionmd.sh` 中版本号和未发布条
Comment on lines +46 to +58
case "$line" in
"### 新增")
current_section="新增"
;;
"### 变更")
current_section="变更"
;;
"### 修复")
current_section="修复"
;;
"### 其他")
current_section="其他"
;;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate logic: section names are hardcoded here but already defined in VERSIONMD_SECTIONS. If a section is added/renamed in the array, this case will silently miss it. Consider iterating VERSIONMD_SECTIONS dynamically instead, e.g.:

local matched=false
for s in "${VERSIONMD_SECTIONS[@]}"; do
  if [[ "$line" == "### $s" ]]; then
    current_section="$s"
    matched=true
    break
  fi
done
if [[ "$matched" == "true" ]]; then continue; fi

Comment on lines +35 to +39
dev_entries_file="$(mktemp)"
extract_entries VERSION.md "$dev_entries_file"

remaining_entries_file="$(mktemp)"
multiset_subtract "$dev_entries_file" "$released_entries_file" "$remaining_entries_file"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temp files from mktemp are never cleaned up. Consider adding a trap at the top of the script for cleanup on exit, consistent with good shell hygiene:

trap 'rm -f "$dev_entries_file" "$remaining_entries_file"' EXIT

Comment on lines +44 to +47
current_entries_file="$(mktemp)"
extract_entries "$VERSION_FILE" "$current_entries_file"

previous_entries_file="$(mktemp)"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as in cleanup-dev.sh: temp files (current_entries_file, previous_entries_file, previous_version_file) are never cleaned up. Add a trap ... EXIT for cleanup.

- 为 `cleanup-dev.sh` 和 `prepare-beta.sh` 脚本新增 `trap` 机制,在退出时清理临时文件
- 优化 `versionmd.sh` 的 section 解析逻辑,提升代码可读性和灵活性

local section
for section in "${VERSIONMD_SECTIONS[@]}"; do
mapfile -t section_items < <(awk -F '\t' -v target="$section" '$1 == target { print $2 }' "$entries_file")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mapfile -t section_items < <(awk -F '\t' -v target="$section" '$1 == target { print $2 }' "$entries_file")
mapfile -t section_items < <(awk -F '\t' -v target="$section" '$1 == target { sub(/^[^\t]*\t/, ""); print }' "$entries_file")

print $2 only prints the second tab-delimited field. If an entry contains a tab, it gets truncated. Use { sub(/^[^\t]*\t/, ""); print } instead. Same issue in write_versionmd at line 140.

local section
for section in "${VERSIONMD_SECTIONS[@]}"; do
echo "### $section"
awk -F '\t' -v target="$section" '$1 == target { print "- " $2 }' "$entries_file"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
awk -F '\t' -v target="$section" '$1 == target { print "- " $2 }' "$entries_file"
awk -F '\t' -v target="$section" '$1 == target { sub(/^[^\t]*\t/, ""); print "- " $0 }' "$entries_file"

Same print $2 truncation issue as in write_notes. Use sub to strip the section prefix instead.

Comment on lines +10 to +11
<TextBlock Text="检查并安装最新版本(默认仅稳定版,不接收预发布)"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<TextBlock Text="检查并安装最新版本(默认仅稳定版,不接收预发布)"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>
<TextBlock Text="检查并安装最新版本"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"/>

This implementation detail ("不接收预发布") is user-facing UI text. Users don't need to know about prerelease channels; keep it concise like the original.

- 修正 `versionmd.sh` 中 section 解析逻辑,移除多余前缀以提升灵活性
- 更新 `UpdatePage.xaml` 文本描述,简化更新信息显示内容
Comment on lines 21 to +22
push:
branches: [master]
branches: [dev, beta, master]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The push trigger now matches dev, beta, and master. Combined with docs-sync (filters beta/master) and fix-ci (triggers on workflow_run), pushes to dev will start this workflow but no job condition matches push to dev, resulting in a no-op workflow run. Consider restricting the push trigger to branches: [beta, master] to avoid unnecessary workflow runs.

{
private const string RepoUrl = "https://github.com/WSXYT/PrismCore";
private const string ProxyBaseUrl = "https://gemini.435535.xyz";
private const bool IncludePrerelease = false; // 默认仅稳定版更新,不接收 beta/预发布

This comment was marked as outdated.

- 新增更新通道设置(稳定版/预发布版)并调整默认值逻辑
- 重构 `UpdateService` 支持动态通道切换并优化实现
- 更新 `AppSettings` 增加通道相关配置项
- 更新 UI 与视图模型以支持通道选择功能
- 修正部分逻辑默认值与描述
App.xaml.cs Outdated
Comment on lines +142 to +148
var settings = AppSettings.Instance;
var recommendedChannel = UpdateService.GetRecommendedChannel();
if (settings.LastInstalledChannel != recommendedChannel)
{
settings.UpdateChannel = recommendedChannel;
settings.LastInstalledChannel = recommendedChannel;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate logic: This channel-recommendation block is identical to the one in UpdateViewModel constructor. Extract it into a shared method (e.g., on UpdateService or AppSettings) to avoid divergence.

Comment on lines +56 to +64
private static bool IsCurrentBuildPrerelease()
{
try
{
var mgr = new UpdateManager(new GithubSource(RepoUrl, null, false));
if (mgr.IsInstalled && mgr.CurrentVersion is { } v)
return IsPrereleaseVersion(v.ToString());
}
catch { /* 非 Velopack 安装环境 */ }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new UpdateManager(new GithubSource(...)) is repeated in GetCurrentVersion, IsVelopackInstalled, and now here. Each call likely makes a network/disk probe. Consider extracting a shared helper or caching the result to reduce duplication and overhead.

Comment on lines +41 to +43
public UpdateService(bool includePrerelease)
{
_includePrerelease = includePrerelease;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _includePrerelease field is stored but only used inside the constructor to build _sources. It can be removed; just use the parameter directly in the collection initializer.

- 重构 `UpdateService`,简化安装版本检测与通道推断逻辑
- 新增 `ResolveAndPersistRecommendedChannel` 方法统一通道设置流程
- 调整 `App.xaml.cs` 与视图模型以适配通道设置优化
- 更新工作流注释内容,提升可读性
Comment on lines +39 to +42
partial void OnSelectedUpdateChannelChanged(int value)
{
_settings.UpdateChannel = value;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
partial void OnSelectedUpdateChannelChanged(int value)
{
_settings.UpdateChannel = value;
}
partial void OnSelectedUpdateChannelChanged(int value)
{
_settings.UpdateChannel = value;
_cachedUpdate = null;
_activeUpdateService = null;
LatestVersion = "未检查";
IsUpToDate = false;
StatusMessage = "";
OnPropertyChanged(nameof(HasUpdate));
}

When the user switches channel after a check, _cachedUpdate and _activeUpdateService still reference the old channel. The "Update" button could apply a wrong-channel update. Invalidate cached state here.

Comment on lines +53 to +63
private static bool IsInstalledByVelopack(out string version)
{
version = string.Empty;
try
{
var mgr = CreateProbeManager();
if (!mgr.IsInstalled) return false;
if (mgr.CurrentVersion is { } v) version = v.ToString();
return true;
}
catch { /* 非 Velopack 安装环境 */ }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returns true with version = "" when mgr.IsInstalled is true but CurrentVersion is null. Callers like IsCurrentBuildPrerelease then pass an empty string to IsPrereleaseVersion, silently returning false. Consider returning false when version is unavailable, or documenting this contract clearly.

Comment on lines +91 to +98
if (IsUpdating || _cachedUpdate == null || _activeUpdateService == null) return;
IsUpdating = true;
UpdateProgress = 0;
StatusMessage = "正在下载更新...";

try
{
await _updateService.DownloadAndApplyAsync(_cachedUpdate, progress =>
await _activeUpdateService.DownloadAndApplyAsync(_cachedUpdate, progress =>

This comment was marked as outdated.

// 更新模式:0=不检查, 1=仅检查, 2=自动安装
public int UpdateMode { get => Get("update_mode", 0); set => Set("update_mode", value); }
// 更新模式:0=不检查, 1=仅检查, 2=自动安装(默认)
public int UpdateMode { get => Get("update_mode", 2); set => Set("update_mode", value); }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The default UpdateMode is now 2 (auto-install), causing the application to update and restart on launch without user consent for new or unconfigured existing users.
Severity: HIGH

Suggested Fix

Revert the default value for UpdateMode in Models/AppSettings.cs back to 0 to preserve the previous opt-in behavior. Alternatively, implement migration logic to handle the setting for existing users and consider prompting users before an automatic restart.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: Models/AppSettings.cs#L117

Potential issue: The default value for the `UpdateMode` setting has been changed from
`0` (do not check for updates) to `2` (check and auto-install). For new users or
existing users who have never configured this setting, the application will now
automatically download and apply updates on startup. This process includes an
application restart, which occurs without any user prompt or confirmation, potentially
interrupting the user's workflow.

- 重构 `UpdateService`,分离版本检测与安装检查逻辑
- 更新 `UpdateViewModel` 以支持动态通道切换时的状态清理
- 优化更新检查流程,避免过期检查结果影响
Comment on lines 130 to 133
public static bool IsVelopackInstalled
{
get
{
try
{
var mgr = new UpdateManager(new GithubSource(RepoUrl, null, true));
return mgr.IsInstalled;
}
catch { return false; }
}
get => IsInstalledByVelopack();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property creates a new UpdateManager on every access. Consider converting to a method (IsVelopackInstalled()) to signal the cost to callers, consistent with how GetCurrentVersion() is a method.

Comment on lines +142 to +144
var settings = AppSettings.Instance;
var activeChannel = UpdateService.ResolveAndPersistRecommendedChannel(settings);
var svc = new UpdateService(activeChannel == 1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolveAndPersistRecommendedChannel is also called in UpdateViewModel constructor. If the ViewModel is created before this runs, channel resolution happens twice, creating redundant UpdateManager instances. Consider calling it once at a single entry point.

Comment on lines +61 to +63
var service = new UpdateService(SelectedUpdateChannel == 1);
_activeUpdateService = service;
var update = await service.CheckForUpdateAsync();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new UpdateService is created on every check. Since the channel doesn't change mid-check, consider reusing _activeUpdateService when the channel hasn't changed, avoiding repeated allocations of source arrays and managers.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 55 to +63
if (IsChecking) return;
IsChecking = true;
StatusMessage = "正在检查更新...";

try
{
_cachedUpdate = await _updateService.CheckForUpdateAsync();
if (_cachedUpdate != null)
var service = new UpdateService(SelectedUpdateChannel == 1);
_activeUpdateService = service;
var update = await service.CheckForUpdateAsync();
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckForUpdateAsync 在开始检查/发生异常时没有清空 _cachedUpdate(以及与之配套的 _activeUpdateService)。如果上一次检查已缓存了更新信息,本次检查失败后 HasUpdate 仍可能保持为 true,进而允许用户用“过期的 UpdateInfo + 新的/异常的 UpdateService”执行更新。建议在发起检查前先清空缓存(或在各个 catch 分支中显式清空并触发 HasUpdate 变更),确保失败场景不会保留旧更新结果。

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +50
- name: Restore
run: >
dotnet restore PrismCore.csproj
-r ${{ matrix.rid }}
-p:Platform=${{ matrix.platform }}

- name: Build
run: >
dotnet build PrismCore.csproj
-c Release
--no-restore
-r ${{ matrix.rid }}
-p:Platform=${{ matrix.platform }}
-p:WindowsPackageType=None
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dotnet restoredotnet build --no-restore 使用了不同的 MSBuild 属性集:build 额外传了 -p:WindowsPackageType=None,但 restore 没有。NuGet 还原结果(project.assets.json)是属性敏感的,这种不一致可能导致后续 --no-restore 构建出现“assets file doesn't have a target…”之类的问题。建议把 -p:WindowsPackageType=None 也加到 Restore 步骤(或移除 --no-restore 让 build 重新还原)。

Copilot uses AI. Check for mistakes.
Copilot AI added a commit that referenced this pull request Mar 2, 2026
- UpdateMode 默认值从 2 改为 0(避免未经用户同意自动更新)
- IsVelopackInstalled 从属性改为方法,避免每次访问创建 UpdateManager
- 移除 UpdateViewModel 中重复的 ResolveAndPersistRecommendedChannel 调用
- 检查更新前和失败时清除缓存,防止使用过期更新信息
- 复用 UpdateService 实例(通道未变时不重复创建)
- CI Restore 步骤添加 -p:WindowsPackageType=None 与 Build 保持一致

Co-authored-by: WSXYT <102407247+WSXYT@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants