chore(release): bump version to Ultra1.0
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
# AI 版本发布标准流程
|
||||
|
||||
## 用途
|
||||
|
||||
本文用于给 AI 一个固定、完整、可重复执行的版本发布流程。
|
||||
|
||||
当前标准版本系列固定为 `Ultra`。从本文生效开始,所有新的正式发布都必须使用 `UltraX.Y` 格式;历史 `ProX.Y` 与 `vX.Y.Z` 版本只用于兼容读取、更新检查和差异分析,不能再作为默认发布目标。
|
||||
|
||||
当用户在本仓库中引用本文,并明确说出类似下面的话时:
|
||||
|
||||
- `更新版本到 Ultra1.0`
|
||||
- `更新版本到 1.0`
|
||||
- `继续发 3.3 版本`
|
||||
- `按本文发布 Ultra1.0`
|
||||
- `引用这个文件,把版本更新到 Ultra1.0 并发版`
|
||||
|
||||
AI 必须按本文执行,不能猜流程,不能跳步骤,不能只做一半。
|
||||
|
||||
## 目标
|
||||
|
||||
AI 在收到“更新版本到 UltraX.Y”或可无歧义标准化的口语化版本号后,必须完成下面两段流程。
|
||||
|
||||
1. 发布前流程:修改 `manifest.json` 的版本字段;提交本次准备发布的代码;基于“上一个 GitHub Release 到当前 HEAD”的真实差异分析更新内容;在需要用户确认的场景下,把发布文案先返回给用户看,且必须放在代码块里。
|
||||
2. 发布流程:把最终确认过的文案作为 GitHub Release 正文发布;发布前必须把 `master` 与 `dev` 都推送到本次待发布的最新提交;发布完成后,再次从 GitHub 线上读取正文;审查正文是否乱码、是否异常、是否与最终发布稿一致。如果有问题,必须立刻修正后再汇报完成。
|
||||
|
||||
## 仓库硬性规则
|
||||
|
||||
1. 任何结论都不能猜,必须基于真实命令输出、真实 diff、真实 GitHub 返回结果。
|
||||
2. GitHub Releases 是版本发布真相源。分析“距离上一个版本更新了什么”时,默认以上一个正式 Release 为起点。
|
||||
3. 不要先检查 `gh` 是否存在、是否登录、是否在 PATH、是否可用,也不要先跑 `gh auth status`。直接使用固定路径命令执行。
|
||||
4. `gh` 固定路径使用:
|
||||
|
||||
```powershell
|
||||
$GH = 'C:\Program Files\GitHub CLI\gh.exe'
|
||||
if (-not (Test-Path $GH)) {
|
||||
throw 'gh.exe fixed path not found: C:\Program Files\GitHub CLI\gh.exe'
|
||||
}
|
||||
```
|
||||
|
||||
5. 默认 GitHub 仓库固定为:
|
||||
|
||||
```powershell
|
||||
$Repo = 'QLHazyCoder/codex-oauth-automation-extension'
|
||||
```
|
||||
|
||||
6. 默认不编译、不跑全量测试。完成后提醒用户自行测试;如果本次变更触及版本解析、更新服务或关键逻辑,应至少运行对应的聚焦测试。
|
||||
7. 如果当前工作区里存在无法确认是否属于本次发布的改动,必须先停下来问用户,不能猜哪些该一起发、哪些不该一起发。
|
||||
8. 发布完成后,必须做一次“线上正文复检”,确认不是乱码,不是空正文,不是错误版本,不是错误标签。
|
||||
9. 新标准使用 `Ultra` 作为对外版本前缀,数字部分必须使用严格的 `x.x`。
|
||||
10. `manifest.json` 中需要同时维护:
|
||||
- `version`:只保存数字部分,例如 `1.0`
|
||||
- `version_name`:保存完整显示版本,例如 `Ultra1.0`
|
||||
11. 仓库可能存在历史 `ProX.Y` 和 `vX.Y.Z` Release。历史版本只用于兼容读取和差异分析,不阻止首个 `Ultra1.0` 发布。
|
||||
12. 如果用户输入 `ProX.Y`,必须提示当前发布标准已切换到 `UltraX.Y`,不能直接创建新的 `Pro` Release;除非用户明确要求维护历史 Pro 系列,并同步修改本文流程,否则一律按 Ultra 系列处理。
|
||||
|
||||
## 输入要求
|
||||
|
||||
AI 至少要从用户输入中拿到下面这些信息:
|
||||
|
||||
- 目标完整版本号,例如 `Ultra1.0`,或可标准化为它的口语化版本号
|
||||
- 是否已经确认可以发布
|
||||
- 如果用户有特别强调的更新点,也要纳入发布文案
|
||||
|
||||
如果用户没有给版本号,就不能猜。
|
||||
|
||||
如果用户说的是口语化版本号,但可以被无歧义标准化,AI 必须直接标准化,不要再反问版本号。例如:
|
||||
|
||||
- `1` -> `Ultra1.0`
|
||||
- `1.0` -> `Ultra1.0`
|
||||
- `Ultra1` -> `Ultra1.0`
|
||||
- `3.3` -> `Ultra3.3`
|
||||
|
||||
只有在用户给出的版本号无法无歧义转换为标准完整版本号时,才允许追问。
|
||||
|
||||
如果用户明确使用了“发布 / 发版 / 继续发 / 直接发布 / 不要再问我 / 按标准直接发”等表达,视为已经确认可以发布,AI 不需要再额外等待一次口头确认;后续所有命令、提交、tag、Release 标题、Release 正文都必须统一使用完整版本号 `UltraX.Y`,不能继续混用口语化写法。
|
||||
|
||||
如果用户明确要求“文案写好之后我要确认”,则无论用户是否说了发布,都必须在创建 GitHub Release 前停止,把发布文案草稿返回给用户确认。
|
||||
|
||||
## 固定变量模板
|
||||
|
||||
每次执行本文时,先统一使用下面这组变量:
|
||||
|
||||
```powershell
|
||||
$GH = 'C:\Program Files\GitHub CLI\gh.exe'
|
||||
if (-not (Test-Path $GH)) {
|
||||
throw 'gh.exe fixed path not found: C:\Program Files\GitHub CLI\gh.exe'
|
||||
}
|
||||
|
||||
$Repo = 'QLHazyCoder/codex-oauth-automation-extension'
|
||||
$ReleaseSeries = 'Ultra'
|
||||
$TargetVersionCore = '<用户输入并标准化后的数字版本号,必须使用 x.x,例如:1.0>'
|
||||
$TargetVersionName = "$ReleaseSeries$TargetVersionCore"
|
||||
$TargetTag = $TargetVersionName
|
||||
$ManifestPath = 'manifest.json'
|
||||
```
|
||||
|
||||
## 阶段 1:发布前检查
|
||||
|
||||
### 1. 真实查看当前工作区
|
||||
|
||||
先执行:
|
||||
|
||||
```powershell
|
||||
git status --short --branch
|
||||
git remote -v
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
1. 必须看清当前分支和当前未提交改动。
|
||||
2. 如果工作区中有改动,必须结合 diff 判断这些改动是不是本次要发布的真实内容。
|
||||
3. 如果有无法确认归属的改动,必须先问用户,不准猜。
|
||||
|
||||
### 2. 读取当前版本号
|
||||
|
||||
执行:
|
||||
|
||||
```powershell
|
||||
$Manifest = Get-Content -Path $ManifestPath -Raw | ConvertFrom-Json
|
||||
$CurrentVersionCore = [string]$Manifest.version
|
||||
$CurrentVersionName = [string]$Manifest.version_name
|
||||
$CurrentVersionLabel = if ($CurrentVersionName) { $CurrentVersionName } else { "v$CurrentVersionCore" }
|
||||
$CurrentVersionCore
|
||||
$CurrentVersionLabel
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
1. 当前版本必须从 `manifest.json` 真实读取。
|
||||
2. 新标准下,`version` 只保存数字部分 `x.x`,`version_name` 保存完整版本 `UltraX.Y`。
|
||||
3. 如果当前已经是 `Ultra` 系列,则目标数字版本必须大于当前数字版本,不能相等,不能更小。
|
||||
4. 如果当前还是历史 `Pro` 或 `v` 系列,而目标是首个 `Ultra` 系列版本,则允许从 `Ultra1.0` 重新起算,但必须明确记录这是命名体系切换。
|
||||
|
||||
示例校验:
|
||||
|
||||
```powershell
|
||||
$IsCurrentUltraSeries = $CurrentVersionName -match '^Ultra\d+\.\d+$'
|
||||
if ($IsCurrentUltraSeries -and ([version]$TargetVersionCore -le [version]$CurrentVersionCore)) {
|
||||
throw "Target version $TargetVersionCore must be greater than current Ultra version $CurrentVersionCore"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 检查目标版本是否已经存在
|
||||
|
||||
先查 GitHub Releases:
|
||||
|
||||
```powershell
|
||||
$Releases = & $GH api "repos/$Repo/releases?per_page=100" | ConvertFrom-Json
|
||||
$ExistingReleaseTag = ($Releases | Where-Object { $_.tag_name -eq $TargetTag } | Select-Object -First 1 -ExpandProperty tag_name)
|
||||
```
|
||||
|
||||
再查本地 tag:
|
||||
|
||||
```powershell
|
||||
$ExistingLocalTag = ([string](git tag --list $TargetTag)).Trim()
|
||||
```
|
||||
|
||||
再查最新一个正式 `Ultra` Release:
|
||||
|
||||
```powershell
|
||||
$LatestUltraReleaseTag = ($Releases |
|
||||
Where-Object { -not $_.draft -and -not $_.prerelease -and $_.tag_name -match '^Ultra[0-9]+\.[0-9]+$' } |
|
||||
Select-Object -First 1 -ExpandProperty tag_name)
|
||||
$LatestUltraReleaseVersionCore = if ($LatestUltraReleaseTag) { $LatestUltraReleaseTag.Substring(5) } else { '' }
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
1. 只要 GitHub Release 已存在这个 tag,就必须停止,不能重复发版。
|
||||
2. 如果本地 tag 已存在,但 GitHub Release 不存在,也必须停止并告知用户先确认历史状态,不能直接覆盖。
|
||||
3. 如果已经存在最新正式 `Ultra` Release,目标数字版本还必须大于这个正式 `Ultra` Release 的数字版本,不能倒发版。
|
||||
4. 如果仓库里还没有任何 `Ultra` Release,允许从 `Ultra1.0` 开始,即使仓库里存在历史 `ProX.Y` 或 `vX.Y.Z` Release。
|
||||
|
||||
示例校验:
|
||||
|
||||
```powershell
|
||||
if ($LatestUltraReleaseVersionCore -and ([version]$TargetVersionCore -le [version]$LatestUltraReleaseVersionCore)) {
|
||||
throw "Target version $TargetVersionCore must be greater than latest Ultra released version $LatestUltraReleaseVersionCore"
|
||||
}
|
||||
```
|
||||
|
||||
## 阶段 2:更新版本号并提交
|
||||
|
||||
### 1. 修改 `manifest.json`
|
||||
|
||||
AI 必须把 `manifest.json` 中的下面两个字段同步更新为目标版本:
|
||||
|
||||
- `version = x.x`
|
||||
- `version_name = UltraX.Y`
|
||||
|
||||
### 2. 检查变更内容
|
||||
|
||||
执行:
|
||||
|
||||
```powershell
|
||||
git diff --stat
|
||||
git diff -- manifest.json
|
||||
```
|
||||
|
||||
如果工作区里还有其他变更,也要继续真实查看它们的 diff,确认是否属于本次发布内容。
|
||||
|
||||
### 3. 提交本次发布
|
||||
|
||||
规则:
|
||||
|
||||
1. 如果当前工作区改动就是本次要发布的功能改动,就应与版本号一起提交。
|
||||
2. 如果存在来源不明或明显无关的改动,必须先停下来问用户,不能偷偷一起提交,也不能擅自丢掉。
|
||||
3. 提交信息统一使用:
|
||||
|
||||
```text
|
||||
chore(release): bump version to UltraX.Y
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```powershell
|
||||
git add -A
|
||||
git commit -m "chore(release): bump version to $TargetTag"
|
||||
```
|
||||
|
||||
提交后,必须记录真实提交哈希:
|
||||
|
||||
```powershell
|
||||
git rev-parse HEAD
|
||||
```
|
||||
|
||||
## 阶段 3:分析“距离上一个版本更新了什么”
|
||||
|
||||
### 1. 获取上一个正式 Release
|
||||
|
||||
执行:
|
||||
|
||||
```powershell
|
||||
$PreviousReleaseTag = ($Releases |
|
||||
Where-Object { -not $_.draft -and -not $_.prerelease } |
|
||||
Select-Object -First 1 -ExpandProperty tag_name)
|
||||
$PreviousReleaseTag
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
1. 默认取最新一个正式 Release 作为“上一个版本”。
|
||||
2. 如果仓库还没有任何正式 Release,再退回到本地最新 tag:
|
||||
|
||||
```powershell
|
||||
if (-not $PreviousReleaseTag) {
|
||||
$PreviousReleaseTag = ([string](git tag --sort=-creatordate | Select-Object -First 1)).Trim()
|
||||
}
|
||||
```
|
||||
|
||||
3. 如果仍然拿不到上一个版本,就按“首个版本发布”处理,并明确告诉用户这是首发版本文案。
|
||||
|
||||
### 2. 拉真实变更范围
|
||||
|
||||
如果存在上一个版本,执行:
|
||||
|
||||
```powershell
|
||||
git log --oneline "$PreviousReleaseTag"..HEAD
|
||||
git diff --stat "$PreviousReleaseTag"..HEAD
|
||||
git diff --name-only "$PreviousReleaseTag"..HEAD
|
||||
```
|
||||
|
||||
然后根据变更文件继续往下读真实 diff,不能只看 commit 标题。
|
||||
|
||||
必要时继续执行:
|
||||
|
||||
```powershell
|
||||
git diff "$PreviousReleaseTag"..HEAD -- <具体文件路径>
|
||||
```
|
||||
|
||||
### 3. 文案分析要求
|
||||
|
||||
AI 必须基于真实 diff 和真实代码上下文,分析并提炼:
|
||||
|
||||
- 新增了什么功能
|
||||
- 修复了什么问题
|
||||
- 优化了什么逻辑
|
||||
- 删除了什么无用或陈旧逻辑
|
||||
- 哪些点最值得放进发布说明
|
||||
|
||||
禁止行为:
|
||||
|
||||
- 只看 commit message 就编文案
|
||||
- 把没有真实证据的内容写进发布说明
|
||||
- 把技术细节胡乱拔高成“重大更新”
|
||||
|
||||
## 阶段 4:先给用户看发布文案,不要直接发布
|
||||
|
||||
在真正发布前,AI 必须把文案返回给用户确认。
|
||||
|
||||
返回给用户时,必须同时说明:
|
||||
|
||||
1. `manifest.json` 已改到哪个 `version`
|
||||
2. `manifest.json` 已改到哪个 `version_name`
|
||||
3. 是否已经提交
|
||||
4. 本次提交哈希是什么
|
||||
5. 上一个版本标签是什么
|
||||
6. 发布文案草稿是什么
|
||||
|
||||
发布文案草稿必须放在代码块中,格式建议如下:
|
||||
|
||||
```markdown
|
||||
## UltraX.Y
|
||||
|
||||
### 更新内容
|
||||
- ...
|
||||
- ...
|
||||
|
||||
### 修复与优化
|
||||
- ...
|
||||
- ...
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
1. 这里必须先停下来等用户确认。
|
||||
2. 用户没确认前,不准创建 GitHub Release。
|
||||
3. 如果用户要求改文案,只改文案,不要乱改已经完成的代码与提交,除非用户明确要求。
|
||||
|
||||
## 阶段 5:用户确认后再发布 GitHub Release
|
||||
|
||||
### 1. 先推送当前提交
|
||||
|
||||
发布前先把当前分支 HEAD 推到远端:
|
||||
|
||||
```powershell
|
||||
git push origin HEAD
|
||||
```
|
||||
|
||||
如果本次发布是在 `master` 上完成,还必须确保 `dev` 也同步到这次待发布的最新提交,不能只推 `master` 而让 `dev` 停留在旧版本。推荐顺序:
|
||||
|
||||
```powershell
|
||||
git checkout dev
|
||||
git merge --ff-only master
|
||||
git push origin dev
|
||||
git checkout master
|
||||
```
|
||||
|
||||
如果 `dev` 无法快进到当前发布提交,必须先真实检查差异并处理,不能跳过 `dev` 的同步。
|
||||
|
||||
### 2. 把用户确认过的文案写入 UTF-8 文件
|
||||
|
||||
不要直接把长正文硬塞进命令参数里,先写入临时文件。
|
||||
|
||||
示例:
|
||||
|
||||
```powershell
|
||||
$ReleaseNotesFile = Join-Path $env:TEMP "codex-release-$TargetVersionCore.md"
|
||||
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($ReleaseNotesFile, $ReleaseNotes, $Utf8NoBom)
|
||||
```
|
||||
|
||||
### 3. 创建 Release
|
||||
|
||||
执行:
|
||||
|
||||
```powershell
|
||||
& $GH release create $TargetTag `
|
||||
--repo $Repo `
|
||||
--target HEAD `
|
||||
--title $TargetTag `
|
||||
--notes-file $ReleaseNotesFile
|
||||
```
|
||||
|
||||
要求:
|
||||
|
||||
1. 标题默认使用 `UltraX.Y`。
|
||||
2. 正文必须使用用户确认过的最终文案。
|
||||
3. 不要在用户没确认前抢先发布。
|
||||
|
||||
## 阶段 6:发布后线上正文复检
|
||||
|
||||
Release 创建完成后,必须立刻重新读取线上内容。
|
||||
|
||||
执行:
|
||||
|
||||
```powershell
|
||||
& $GH api "repos/$Repo/releases/tags/$TargetTag"
|
||||
& $GH api "repos/$Repo/releases/tags/$TargetTag" --jq '.html_url'
|
||||
& $GH api "repos/$Repo/releases/tags/$TargetTag" --jq '.tag_name'
|
||||
& $GH api "repos/$Repo/releases/tags/$TargetTag" --jq '.name'
|
||||
& $GH api "repos/$Repo/releases/tags/$TargetTag" --jq '.body'
|
||||
```
|
||||
|
||||
复检重点:
|
||||
|
||||
1. 线上 tag 是否真的是目标 tag
|
||||
2. 线上标题是否真的是目标版本
|
||||
3. 线上正文是否完整
|
||||
4. 正文是否与用户确认稿一致
|
||||
5. 是否存在乱码、异常字符、错误换行、空正文
|
||||
|
||||
尤其注意下面这些异常:
|
||||
|
||||
- 出现明显乱码字符
|
||||
- 中文被破坏
|
||||
- 正文为空
|
||||
- 版本号不对
|
||||
- 正文内容被截断
|
||||
|
||||
### 如果复检发现异常
|
||||
|
||||
必须立刻修复,不准带病汇报完成。
|
||||
|
||||
修复示例:
|
||||
|
||||
```powershell
|
||||
& $GH release edit $TargetTag `
|
||||
--repo $Repo `
|
||||
--title $TargetTag `
|
||||
--notes-file $ReleaseNotesFile
|
||||
```
|
||||
|
||||
修复后,再重复执行一次“发布后线上正文复检”,直到线上内容正常为止。
|
||||
|
||||
## 用户侧最终反馈要求
|
||||
|
||||
AI 完成后,对用户的最终反馈至少要说明:
|
||||
|
||||
1. 已把 `manifest.json` 更新到哪个 `version`
|
||||
2. 已把 `manifest.json` 更新到哪个 `version_name`
|
||||
3. 是否已经提交,提交哈希是什么
|
||||
4. 上一个版本是什么
|
||||
5. 发布文案是否经过用户确认
|
||||
6. 是否已经创建 GitHub Release
|
||||
7. 是否已经完成线上正文复检
|
||||
8. 如果没跑测试,要明确提醒用户自行测试
|
||||
|
||||
## 一句话执行要求
|
||||
|
||||
当用户引用本文并说“更新版本到 UltraX.Y”或给出可标准化的口语化版本号时,AI 必须按下面顺序执行:
|
||||
|
||||
1. 真实检查工作区与当前版本
|
||||
2. 把口语化版本号标准化成 `UltraX.Y`
|
||||
3. 真实校验目标版本是否合法且未发布
|
||||
4. 修改 `manifest.json` 的 `version` 与 `version_name`
|
||||
5. 提交本次待发布改动
|
||||
6. 基于上一个正式 Release 到当前 HEAD 的真实差异分析更新内容
|
||||
7. 如果用户没有明确授权直接发布,或者用户要求先确认文案,就把发布文案放进代码块返回给用户确认
|
||||
8. 用户确认后,推送当前发布提交,并把 `dev` 同步推送到相同的最新提交
|
||||
9. 创建 GitHub Release
|
||||
10. 发布完成后再次读取线上正文并检查是否乱码
|
||||
11. 如有异常,先修复再汇报
|
||||
|
||||
不能跳步骤,不能猜,不能偷懒。
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "codex-oauth-automation-extension",
|
||||
"version": "8.0",
|
||||
"version_name": "Pro8.0",
|
||||
"version": "1.0",
|
||||
"version_name": "Ultra1.0",
|
||||
"description": "用于自动执行多步骤 OAuth 注册流程",
|
||||
"permissions": [
|
||||
"sidePanel",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="header-version-block">
|
||||
<div class="header-version-main">
|
||||
<button id="extension-update-status" class="header-version-title header-version-link" type="button"
|
||||
aria-label="打开 GitHub Releases 页面" title="打开 GitHub Releases 页面">Pro0.0</button>
|
||||
aria-label="打开 GitHub Releases 页面" title="打开 GitHub Releases 页面">Ultra0.0</button>
|
||||
<button id="btn-release-log" class="header-link-btn" type="button" hidden>更新日志</button>
|
||||
</div>
|
||||
<span id="extension-version-meta" class="header-version-meta" hidden></span>
|
||||
|
||||
@@ -2726,7 +2726,7 @@ function renderUpdateReleaseList(releases = []) {
|
||||
|
||||
const version = document.createElement('span');
|
||||
version.className = 'update-release-version';
|
||||
version.textContent = release.displayVersion || `Pro${release.version}`;
|
||||
version.textContent = release.displayVersion || `Ultra${release.version}`;
|
||||
titleRow.appendChild(version);
|
||||
|
||||
if (release.title) {
|
||||
@@ -2820,14 +2820,14 @@ function renderReleaseSnapshot(snapshot) {
|
||||
}
|
||||
|
||||
case 'latest': {
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Pro0.0';
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||||
extensionUpdateStatus.classList.add('is-version-label');
|
||||
resetUpdateCard();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'empty': {
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Pro0.0';
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||||
extensionUpdateStatus.classList.add('is-version-label');
|
||||
resetUpdateCard();
|
||||
break;
|
||||
@@ -2835,7 +2835,7 @@ function renderReleaseSnapshot(snapshot) {
|
||||
|
||||
case 'error':
|
||||
default: {
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Pro0.0';
|
||||
extensionUpdateStatus.textContent = localVersionText || 'Ultra0.0';
|
||||
extensionUpdateStatus.classList.add('is-version-label', 'is-check-failed');
|
||||
extensionVersionMeta.textContent = snapshot?.errorMessage || 'GitHub Releases 检查失败';
|
||||
extensionVersionMeta.hidden = false;
|
||||
@@ -2858,8 +2858,8 @@ async function initializeReleaseInfo() {
|
||||
|
||||
const localVersion = sidepanelUpdateService?.getLocalVersionLabel?.(chrome.runtime.getManifest())
|
||||
|| chrome.runtime.getManifest()?.version_name
|
||||
|| (chrome.runtime.getManifest()?.version ? `v${chrome.runtime.getManifest().version}` : '');
|
||||
extensionUpdateStatus.textContent = localVersion || 'Pro0.0';
|
||||
|| (chrome.runtime.getManifest()?.version ? `Ultra${chrome.runtime.getManifest().version}` : '');
|
||||
extensionUpdateStatus.textContent = localVersion || 'Ultra0.0';
|
||||
extensionUpdateStatus.classList.remove('is-update-available', 'is-check-failed');
|
||||
extensionUpdateStatus.classList.add('is-version-label');
|
||||
extensionVersionMeta.hidden = true;
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
const FETCH_TIMEOUT_MS = 8000;
|
||||
const MAX_RELEASES = 10;
|
||||
const MAX_NOTES_PER_RELEASE = 5;
|
||||
const VERSION_FAMILY_ULTRA = 'ultra';
|
||||
const VERSION_FAMILY_PRO = 'pro';
|
||||
const VERSION_FAMILY_LEGACY = 'legacy';
|
||||
|
||||
function getVersionFamily(version, fallbackFamily = VERSION_FAMILY_LEGACY) {
|
||||
const trimmed = String(version || '').trim();
|
||||
if (/^ultra/i.test(trimmed)) {
|
||||
return VERSION_FAMILY_ULTRA;
|
||||
}
|
||||
if (/^pro/i.test(trimmed)) {
|
||||
return VERSION_FAMILY_PRO;
|
||||
}
|
||||
@@ -23,7 +27,7 @@
|
||||
}
|
||||
|
||||
function stripVersionPrefix(version) {
|
||||
return String(version || '').trim().replace(/^(?:pro|v)\s*/i, '');
|
||||
return String(version || '').trim().replace(/^(?:ultra|pro|v)\s*/i, '');
|
||||
}
|
||||
|
||||
function extractVersionCore(version) {
|
||||
@@ -38,7 +42,7 @@
|
||||
}
|
||||
|
||||
const family = getVersionFamily(version, fallbackFamily);
|
||||
return `${family === VERSION_FAMILY_PRO ? 'Pro' : 'v'}${core}`;
|
||||
return `${getVersionFamilyPrefix(family)}${core}`;
|
||||
}
|
||||
|
||||
function parseVersionMeta(version, fallbackFamily = VERSION_FAMILY_LEGACY) {
|
||||
@@ -47,7 +51,7 @@
|
||||
return {
|
||||
family,
|
||||
core,
|
||||
displayVersion: core ? `${family === VERSION_FAMILY_PRO ? 'Pro' : 'v'}${core}` : '',
|
||||
displayVersion: core ? `${getVersionFamilyPrefix(family)}${core}` : '',
|
||||
parts: core
|
||||
? core.split('.').map((part) => {
|
||||
const numeric = Number.parseInt(part, 10);
|
||||
@@ -57,8 +61,24 @@
|
||||
};
|
||||
}
|
||||
|
||||
function getVersionFamilyPrefix(family) {
|
||||
if (family === VERSION_FAMILY_ULTRA) {
|
||||
return 'Ultra';
|
||||
}
|
||||
if (family === VERSION_FAMILY_PRO) {
|
||||
return 'Pro';
|
||||
}
|
||||
return 'v';
|
||||
}
|
||||
|
||||
function getVersionFamilyRank(family) {
|
||||
return family === VERSION_FAMILY_PRO ? 2 : 1;
|
||||
if (family === VERSION_FAMILY_ULTRA) {
|
||||
return 3;
|
||||
}
|
||||
if (family === VERSION_FAMILY_PRO) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function parseVersionParts(version, fallbackFamily = VERSION_FAMILY_LEGACY) {
|
||||
@@ -185,8 +205,8 @@
|
||||
}
|
||||
|
||||
function getComparableReleaseVersion(release) {
|
||||
const fallbackFamily = release?.family === VERSION_FAMILY_PRO
|
||||
? VERSION_FAMILY_PRO
|
||||
const fallbackFamily = [VERSION_FAMILY_ULTRA, VERSION_FAMILY_PRO].includes(release?.family)
|
||||
? release.family
|
||||
: VERSION_FAMILY_LEGACY;
|
||||
const displayVersion = String(release?.displayVersion || '').trim();
|
||||
if (displayVersion) {
|
||||
@@ -319,17 +339,17 @@
|
||||
}
|
||||
|
||||
function getLocalVersionLabel(manifest = chrome.runtime.getManifest()) {
|
||||
const versionName = formatDisplayVersion(manifest?.version_name, VERSION_FAMILY_PRO);
|
||||
const versionName = formatDisplayVersion(manifest?.version_name, VERSION_FAMILY_ULTRA);
|
||||
if (versionName) {
|
||||
return versionName;
|
||||
}
|
||||
|
||||
const versionCore = extractVersionCore(manifest?.version || '');
|
||||
return versionCore ? formatDisplayVersion(`v${versionCore}`, VERSION_FAMILY_LEGACY) : '';
|
||||
return versionCore ? formatDisplayVersion(`Ultra${versionCore}`, VERSION_FAMILY_ULTRA) : '';
|
||||
}
|
||||
|
||||
async function getReleaseSnapshot(options = {}) {
|
||||
const localVersion = getLocalVersionLabel(chrome.runtime.getManifest()) || 'v0.0.0';
|
||||
const localVersion = getLocalVersionLabel(chrome.runtime.getManifest()) || 'Ultra0.0';
|
||||
|
||||
try {
|
||||
const releases = await loadReleases(options);
|
||||
|
||||
@@ -6,8 +6,8 @@ const source = fs.readFileSync('sidepanel/update-service.js', 'utf8');
|
||||
|
||||
function createUpdateService(options = {}) {
|
||||
const manifest = options.manifest || {
|
||||
version: '2.0',
|
||||
version_name: 'Pro2.0',
|
||||
version: '1.0',
|
||||
version_name: 'Ultra1.0',
|
||||
};
|
||||
const cache = new Map();
|
||||
const windowObject = {};
|
||||
@@ -74,11 +74,11 @@ function createUpdateService(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
test('getReleaseSnapshot keeps Pro releases ahead of legacy v releases', async () => {
|
||||
test('getReleaseSnapshot keeps Ultra releases ahead of historical Pro and legacy v releases', async () => {
|
||||
const { api } = createUpdateService({
|
||||
manifest: {
|
||||
version: '2.0',
|
||||
version_name: 'Pro2.0',
|
||||
version: '1.0',
|
||||
version_name: 'Ultra1.0',
|
||||
},
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
@@ -98,15 +98,15 @@ test('getReleaseSnapshot keeps Pro releases ahead of legacy v releases', async (
|
||||
name: 'Pro2.4',
|
||||
html_url: 'https://example.com/Pro2.4',
|
||||
published_at: '2026-04-18T00:00:00.000Z',
|
||||
body: '- pro release',
|
||||
body: '- historical pro release',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
},
|
||||
{
|
||||
tag_name: 'Pro2.0',
|
||||
name: 'Pro2.0',
|
||||
html_url: 'https://example.com/Pro2.0',
|
||||
published_at: '2026-04-16T00:00:00.000Z',
|
||||
tag_name: 'Ultra1.1',
|
||||
name: 'Ultra1.1',
|
||||
html_url: 'https://example.com/Ultra1.1',
|
||||
published_at: '2026-04-19T00:00:00.000Z',
|
||||
body: '- current release',
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
@@ -119,19 +119,19 @@ test('getReleaseSnapshot keeps Pro releases ahead of legacy v releases', async (
|
||||
const snapshot = await api.getReleaseSnapshot({ force: true });
|
||||
|
||||
assert.equal(snapshot.status, 'update-available');
|
||||
assert.equal(snapshot.localVersion, 'Pro2.0');
|
||||
assert.equal(snapshot.latestVersion, 'Pro2.4');
|
||||
assert.equal(snapshot.localVersion, 'Ultra1.0');
|
||||
assert.equal(snapshot.latestVersion, 'Ultra1.1');
|
||||
assert.deepEqual(
|
||||
snapshot.newerReleases.map((release) => release.displayVersion),
|
||||
['Pro2.4']
|
||||
['Ultra1.1']
|
||||
);
|
||||
});
|
||||
|
||||
test('getReleaseSnapshot reorders cached releases before choosing latest version', async () => {
|
||||
const { api, getFetchCalls } = createUpdateService({
|
||||
manifest: {
|
||||
version: '2.0',
|
||||
version_name: 'Pro2.0',
|
||||
version: '1.0',
|
||||
version_name: 'Ultra1.0',
|
||||
},
|
||||
cachedSnapshot: {
|
||||
fetchedAt: Date.now(),
|
||||
@@ -154,6 +154,15 @@ test('getReleaseSnapshot reorders cached releases before choosing latest version
|
||||
publishedAt: '2026-04-18T00:00:00.000Z',
|
||||
notes: [],
|
||||
},
|
||||
{
|
||||
version: '1.1',
|
||||
displayVersion: 'Ultra1.1',
|
||||
family: 'ultra',
|
||||
title: '',
|
||||
url: 'https://example.com/Ultra1.1',
|
||||
publishedAt: '2026-04-19T00:00:00.000Z',
|
||||
notes: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
fetchImpl: async () => {
|
||||
@@ -165,9 +174,9 @@ test('getReleaseSnapshot reorders cached releases before choosing latest version
|
||||
|
||||
assert.equal(getFetchCalls(), 0);
|
||||
assert.equal(snapshot.status, 'update-available');
|
||||
assert.equal(snapshot.latestVersion, 'Pro2.4');
|
||||
assert.equal(snapshot.latestVersion, 'Ultra1.1');
|
||||
assert.deepEqual(
|
||||
snapshot.newerReleases.map((release) => release.displayVersion),
|
||||
['Pro2.4']
|
||||
['Ultra1.1']
|
||||
);
|
||||
});
|
||||
|
||||
+8
-7
@@ -43,9 +43,9 @@
|
||||
- 在顶部“贡献/使用”按钮下方展示一个非强制的内容更新轻提示;提示来源于 `apikey.qzz.io` 的公开公告 / 教程摘要,用户关闭后仅对当前 `promptVersion` 静默,下次内容版本变化后会重新出现
|
||||
- 在 sidepanel 初始化和点击“自动”按钮前刷新一次贡献站公开内容摘要;如果刷新失败,不阻塞主自动流程
|
||||
- 在日志区通过“记录”按钮打开独立的邮箱记录覆盖层,并展示成功/失败/停止/重试统计与分页列表
|
||||
- 查询 GitHub Releases 并展示更新卡片;当前更新服务会区分 `Pro` 与 legacy `v` 两个版本族,排序时优先保持版本族语义一致,同时会在读取缓存后重新排序,避免旧缓存把 `v` 版本误显示为比 `Pro` 更新
|
||||
- 查询 GitHub Releases 并展示更新卡片;当前更新服务会区分 `Ultra`、历史 `Pro` 与 legacy `v` 三个版本族,排序时固定以 `Ultra` 为最高正式系列,同时会在读取缓存后重新排序,避免历史 `Pro` 或 `v` 版本误显示为比 `Ultra` 更新
|
||||
- 展示一个单独的“接码”开关;开启后才展示 HeroSMS 的接码国家与 API Key 设置,用于 OAuth 登录链路命中手机号验证页时直接续跑手机验证
|
||||
- 展示 `Plus 模式` 开关与 PayPal 账号/密码配置;开启后步骤列表切换为 Plus 模式 12 步定义,普通模式的 Cookie 清理与登录验证码步骤不再显示或执行
|
||||
- 展示 `Plus 模式` 开关与 PayPal 账号/密码配置;开启后步骤列表切换为 Plus 模式 13 步定义,普通模式的 Cookie 清理步骤不再显示或执行,登录验证码步骤会移动到 Plus 可见第 11 步
|
||||
- 为 Hotmail / 2925 账号池复用同一套“添加账号 / 取消添加 / 批量导入 / 收起列表”表单交互;共享的显隐控制放在 `sidepanel/account-pool-ui.js`,各自 manager 只保留 provider 相关字段校验与业务操作
|
||||
|
||||
### 2.2 Background Service Worker
|
||||
@@ -118,7 +118,7 @@
|
||||
- 步骤顺序靠 `order`
|
||||
- 步骤文件名靠语义
|
||||
- 新增步骤时不需要重命名后续文件
|
||||
- 普通模式使用 10 步定义;Plus 模式使用 12 步定义,其中 Plus 可见步骤 10/11/12 复用原 OAuth 登录、OAuth 同意页与平台回调验证执行器,但按 Plus 可见步骤编号记录状态
|
||||
- 普通模式使用 10 步定义;Plus 模式使用 13 步定义,其中 Plus 可见步骤 10/11/12/13 复用原 OAuth 登录、登录验证码、OAuth 同意页与平台回调验证执行器,但按 Plus 可见步骤编号记录状态
|
||||
|
||||
## 4. 状态与存储链路
|
||||
|
||||
@@ -497,13 +497,14 @@ Plus 模式可见步骤:
|
||||
4. 第 8 步 `PayPal 登录与授权`:在 PayPal 页面填写 `paypalEmail / paypalPassword`,登录前固定等待 1 秒,关闭可见通行密钥提示,点击“同意并继续”。
|
||||
5. 第 9 步 `订阅回跳确认`:等待 PayPal 授权后回跳到 ChatGPT / OpenAI 页面,页面加载完成后固定等待 1 秒。
|
||||
6. 第 10 步:复用原 Step 7 OAuth 登录执行器,但状态和日志按 Plus 可见第 10 步记录。
|
||||
7. 第 11 步:复用原 Step 9 OAuth 同意页点击和 localhost callback 捕获执行器,但状态和日志按 Plus 可见第 11 步记录。
|
||||
8. 第 12 步:复用原 Step 10 CPA / SUB2API / Codex2API 平台回调验证执行器,但状态和日志按 Plus 可见第 12 步记录。
|
||||
7. 第 11 步:复用原 Step 8 登录验证码执行器,但状态和日志按 Plus 可见第 11 步记录。
|
||||
8. 第 12 步:复用原 Step 9 OAuth 同意页点击和 localhost callback 捕获执行器,但状态和日志按 Plus 可见第 12 步记录。
|
||||
9. 第 13 步:复用原 Step 10 CPA / SUB2API / Codex2API 平台回调验证执行器,但状态和日志按 Plus 可见第 13 步记录。
|
||||
|
||||
隐藏与跳过规则:
|
||||
|
||||
- 原 Step 6 `清理登录 Cookies`:Plus 模式下隐藏且不执行,因为 Plus Checkout 创建依赖当前 ChatGPT 登录态。
|
||||
- 原 Step 8 `获取登录验证码`:Plus 模式下隐藏且默认跳过,Plus 后半段直接从可见第 10 步进入第 11 步。
|
||||
- 原 Step 8 `获取登录验证码`:Plus 模式下移动到可见第 11 步,继续负责登录验证码页的收码与提交。
|
||||
- 原 Step 9 不能跳过;它负责点击 OAuth 同意页并捕获 `localhostUrl`。
|
||||
- 原 Step 10 不能跳过;它依赖 `localhostUrl` 完成平台侧账号创建或回调验证。
|
||||
|
||||
@@ -765,7 +766,7 @@ Plus 模式可见步骤:
|
||||
- 如果当前 `Mail = 自定义邮箱` 且配置了 `customMailProviderPool`,会先按当前目标轮次把号池中的对应邮箱写回运行态
|
||||
- 如果当前生成方式是 `custom-pool`,会先按当前目标轮次把邮箱池中的对应邮箱写回运行态
|
||||
5. 执行 `runAutoSequenceFromStep`
|
||||
- 自动运行会按当前 `plusModeEnabled` 选择普通 10 步或 Plus 12 步可见步骤;Plus 模式下第 6~9 步走 checkout / PayPal,第 10~12 步复用 OAuth 后半段
|
||||
- 自动运行会按当前 `plusModeEnabled` 选择普通 10 步或 Plus 13 步可见步骤;Plus 模式下第 6~9 步走 checkout / PayPal,第 10~13 步复用 OAuth 后半段
|
||||
- 步骤 7 内部仍保留登录态恢复的有限重试,但 `add-phone / 手机号页` 属于立即跳出的不可重试错误
|
||||
- 步骤 8 若在验证码提交后进入 `add-phone / 手机号页`,会直接抛出 fatal 错误,不再先标记步骤成功
|
||||
- 普通模式一旦进入步骤 7~10,遇到普通报错且认证流程未进入 `add-phone`,则自动回到步骤 7 无限重开
|
||||
|
||||
+4
-4
@@ -97,7 +97,7 @@
|
||||
|
||||
- `data/names.js`:随机姓名、生日等测试数据源。
|
||||
- `data/address-sources.js`:Plus 模式本地地址 seed 表,负责按国家选择用于触发 checkout 内置 Google 地址推荐的查询词和结构化地址 fallback;第一版不实时抓取外部地址网站。
|
||||
- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染和步骤注册;当前同时提供普通 10 步定义与 Plus 模式 12 步定义。
|
||||
- `data/step-definitions.js`:共享步骤元数据,前后台共同使用,用于动态渲染和步骤注册;当前同时提供普通 10 步定义与 Plus 模式 13 步定义。
|
||||
|
||||
## `docs/`
|
||||
|
||||
@@ -148,13 +148,13 @@
|
||||
示并同时服务于 provide / receive 两种模式;当 provider 为 iCloud 时,会保存并回显目标邮箱类型与转发邮箱 provider,相关 provider 选
|
||||
项和 label 复用 `mail-provider-utils.js`;`自定义邮箱池` 模式会按邮箱池长度锁定自动轮数,并把当前输入的邮箱池配置参与自动启动前保
|
||||
存;`邮箱服务 = 自定义邮箱` 时,如果配置了 `customMailProviderPool`,也会按号池长度锁定自动轮数并在 Auto 中按轮次分配注册邮箱,同时
|
||||
在普通失败时继续复用当前邮箱,只有成功或出现手机号验证时才切换下一个邮箱;Plus 模式开启后,步骤列表会切换为 12 步,并显示 PayPal 凭
|
||||
在普通失败时继续复用当前邮箱,只有成功或出现手机号验证时才切换下一个邮箱;Plus 模式开启后,步骤列表会切换为 13 步,并显示 PayPal 凭
|
||||
据配置;Step 8 的自定义邮箱确认弹窗当前额外提供“出现手机号验证”按钮,用于直接走与真实 add-phone 一致的 fatal 分支;HeroSMS 国家列表
|
||||
会在 sidepanel 初始化时拉取并恢复到本地保存的国家/API 设置,接码开关关闭时会直接隐藏相关配置;账号记录快照同步改为默认自动模式,只
|
||||
要本地 helper 可用就会自动落盘;新来源 Codex2API 在这里仅补充来源配置接线、表单显隐和 Step 10 按钮文案,不承接协议业务逻辑;
|
||||
Hotmail / 2925 的新增表单显隐统一接到 `sidepanel/account-pool-ui.js` 这一层共享 helper;侧边栏初始化与点击“自动”前会刷新一次贡献站
|
||||
公开内容摘要,并按本地关闭版本决定是否展示轻提示,同时在首次初始化后按现有规则决定是否弹出新手引导提示。
|
||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Pro` / `v` 双版本族排序、缓存读取与版本展示。
|
||||
- `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询、`Ultra` / 历史 `Pro` / legacy `v` 版本族排序、缓存读取与版本展示。
|
||||
|
||||
## `tests/`
|
||||
|
||||
@@ -239,6 +239,6 @@
|
||||
- `tests/step9-cpa-mode.test.js`:测试本地 CPA 平台回调跳过策略判断,当前作用于步骤 10。
|
||||
- `tests/step9-localhost-cleanup-scope.test.js`:测试 localhost callback 与路径前缀残留页的精确清理范围,当前对应步骤 10 收尾。
|
||||
- `tests/step9-status-diagnostics.test.js`:测试平台回调成功判定、红色错误态过滤,以及成功徽标与失败提示并存时的优先级,当前对应步骤 10。
|
||||
- `tests/update-service.test.js`:测试侧边栏更新检查服务在 `Pro` / `v` 混合版本和旧缓存场景下都能按版本族正确排序,不会把 legacy `v` 版本误显示成最新可升级版本。
|
||||
- `tests/update-service.test.js`:测试侧边栏更新检查服务在 `Ultra` / 历史 `Pro` / legacy `v` 混合版本和旧缓存场景下都能按版本族正确排序,不会把历史版本误显示成最新可升级版本。
|
||||
- `tests/verification-stop-propagation.test.js`:测试验证码流程在 Stop 场景下的错误传播与不中途降级。
|
||||
- `tests/verification-flow-polling.test.js`:测试 2925 长轮询参数、验证码提交流程中的 `beforeSubmit` 钩子执行顺序,以及步骤 8 提交验证码后进入手机号页时会把“继续手机号验证”状态交给步骤 9 处理。
|
||||
|
||||
Reference in New Issue
Block a user