Skip to content

Commit d2b9e21

Browse files
committed
Guard Chrome Web Store active submissions
1 parent e7c2738 commit d2b9e21

9 files changed

Lines changed: 320 additions & 5 deletions

.github/workflows/chrome-web-store-publish.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ on:
3232
required: false
3333
type: boolean
3434
default: false
35+
cancel_pending_submission:
36+
description: 是否先取消 Chrome Web Store 中当前审核中或 staged 的提交,再上传本次版本
37+
required: false
38+
type: boolean
39+
default: false
3540

3641
permissions:
3742
contents: read
@@ -127,6 +132,7 @@ jobs:
127132
CHROME_PUBLISH_TYPE: ${{ inputs.publish_type }}
128133
CHROME_DEPLOY_PERCENTAGE: ${{ inputs.deploy_percentage }}
129134
CHROME_SKIP_REVIEW: ${{ inputs.skip_review }}
135+
CHROME_CANCEL_PENDING_SUBMISSION: ${{ inputs.cancel_pending_submission }}
130136
run: |
131137
args=(
132138
--zip "${CHROME_EXTENSION_ZIP}"
@@ -142,6 +148,9 @@ jobs:
142148
if [ "${CHROME_SKIP_REVIEW}" = "true" ]; then
143149
args+=(--skip-review)
144150
fi
151+
if [ "${CHROME_CANCEL_PENDING_SUBMISSION}" = "true" ]; then
152+
args+=(--cancel-pending)
153+
fi
145154
./scripts/publish-chrome-web-store.mjs "${args[@]}"
146155
147156
- name: 上传 Chrome Web Store API 结果

.github/workflows/release.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ on:
3434
required: false
3535
type: boolean
3636
default: false
37+
chrome_cancel_pending_submission:
38+
description: 是否先取消 Chrome Web Store 中当前审核中或 staged 的提交,再上传本次版本
39+
required: false
40+
type: boolean
41+
default: false
3742

3843
permissions:
3944
contents: read
@@ -181,18 +186,22 @@ jobs:
181186
CHROME_PUBLISH_TYPE_INPUT: ${{ inputs.chrome_publish_type }}
182187
CHROME_DEPLOY_PERCENTAGE_INPUT: ${{ inputs.chrome_deploy_percentage }}
183188
CHROME_SKIP_REVIEW_INPUT: ${{ inputs.chrome_skip_review }}
189+
CHROME_CANCEL_PENDING_SUBMISSION_INPUT: ${{ inputs.chrome_cancel_pending_submission }}
184190
CWS_PUBLISH_TYPE: ${{ vars.CWS_PUBLISH_TYPE }}
185191
CWS_DEPLOY_PERCENTAGE: ${{ vars.CWS_DEPLOY_PERCENTAGE }}
186192
CWS_SKIP_REVIEW: ${{ vars.CWS_SKIP_REVIEW }}
193+
CWS_CANCEL_PENDING_SUBMISSION: ${{ vars.CWS_CANCEL_PENDING_SUBMISSION }}
187194
run: |
188195
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
189196
publish_type="${CHROME_PUBLISH_TYPE_INPUT:-DEFAULT_PUBLISH}"
190197
deploy_percentage="${CHROME_DEPLOY_PERCENTAGE_INPUT:-}"
191198
skip_review="${CHROME_SKIP_REVIEW_INPUT:-false}"
199+
cancel_pending_submission="${CHROME_CANCEL_PENDING_SUBMISSION_INPUT:-false}"
192200
else
193201
publish_type="${CWS_PUBLISH_TYPE:-DEFAULT_PUBLISH}"
194202
deploy_percentage="${CWS_DEPLOY_PERCENTAGE:-}"
195203
skip_review="${CWS_SKIP_REVIEW:-false}"
204+
cancel_pending_submission="${CWS_CANCEL_PENDING_SUBMISSION:-false}"
196205
fi
197206
198207
args=(
@@ -206,6 +215,9 @@ jobs:
206215
if [ "${skip_review}" = "true" ]; then
207216
args+=(--skip-review)
208217
fi
218+
if [ "${cancel_pending_submission}" = "true" ]; then
219+
args+=(--cancel-pending)
220+
fi
209221
./scripts/publish-chrome-web-store.mjs --submit "${args[@]}"
210222
211223
- name: 上传 Chrome Web Store API 结果

docs/CHROME_WEB_STORE_RELEASE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ Chrome Web Store API v2 用于上传 extension zip,并可选提交审核。官
124124
Extension ID。
125125
- 上传新包时,`apps/chrome-extension/manifest.json``version` 必须比
126126
已发布版本更高。
127+
- 如果已有 active submission(例如 `PENDING_REVIEW``STAGED`),API 不允许继续
128+
上传或提交新版本。仓库脚本会先调用 `fetchStatus` 检查状态,默认跳过本轮商店提交
129+
并写出 JSON 结果,避免 GitHub Release 因商店审核队列而失败。
127130
- 首次提交 Dashboard 文案、权限说明和隐私字段时,先使用
128131
`docs/CHROME_WEB_STORE_LISTING.md` 里的 listing draft。
129132

@@ -186,6 +189,13 @@ CWS_REFRESH_TOKEN` 写入 GitHub repository secret。
186189
3. 调用 Chrome Web Store API v2 `upload`
187190
4. 上传成功后调用 `publish`,提交审核。
188191

192+
如果 Chrome Web Store 里已经有审核中或 staged 的提交,workflow 默认不会取消它;
193+
脚本会跳过上传并在 `chrome-web-store-result.json` 里记录
194+
`skipped.reason=ACTIVE_SUBMISSION`。这让 GitHub Release、npm、PyPI 和 Homebrew
195+
发布可以继续保持绿色。要让最新版本替换旧的审核中提交,手动触发 workflow 时把
196+
`chrome_cancel_pending_submission` 设为 `true`。这会先调用
197+
`cancelSubmission` 取消当前 active submission,再上传并提交本次 extension zip。
198+
189199
`scripts/publish-chrome-web-store.mjs` 的认证优先级是:
190200

191201
1. `CWS_ACCESS_TOKEN`:短期 access token,适合本地一次性验证。
@@ -210,13 +220,19 @@ CWS_AUTO_PUBLISH=true
210220
CWS_PUBLISH_TYPE=DEFAULT_PUBLISH
211221
CWS_DEPLOY_PERCENTAGE=
212222
CWS_SKIP_REVIEW=false
223+
CWS_CANCEL_PENDING_SUBMISSION=false
213224
```
214225

215226
tag 自动发布仍然需要上面的 `CWS_*` secrets。`CWS_SKIP_REVIEW=true` 只会请求
216227
Chrome Web Store 跳过审核;官方 API 会验证是否符合条件,不符合时会返回错误。
217228
Open Browser Use 当前包含 `<all_urls>``debugger``tabs``downloads` 等敏感
218229
能力,普通代码更新应预期继续进入审核流程。
219230

231+
`CWS_CANCEL_PENDING_SUBMISSION=true` 会让 tag 自动发布在发现 active submission
232+
时先取消旧提交再提交当前 tag 的 zip。这个动作会改变 Chrome Web Store 审核队列,
233+
建议只在你明确希望“最新 tag 覆盖旧审核版本”时开启;否则保持默认 `false`,等当前
234+
审核结束后用补发 workflow 提交最新 release。
235+
220236
## 从已有 GitHub Release 补发商店
221237

222238
如果 GitHub Release 已经创建完成,只需要把其中的插件 zip 上传到 Chrome
@@ -233,12 +249,16 @@ submit=true
233249
publish_type=DEFAULT_PUBLISH
234250
deploy_percentage=
235251
skip_review=false
252+
cancel_pending_submission=false
236253
```
237254

238255
`asset_name` 留空时会按 `release_tag` 精确下载
239256
`open-browser-use-chrome-extension-<version>.zip`。如果需要上传非默认 asset,可以
240257
显式填写完整 asset 文件名。
241258

259+
如果补发时商店里仍有旧版本在审核中,先确认是否要替换旧审核版本;确认后把
260+
`cancel_pending_submission=true`。否则 workflow 会跳过上传并保留当前审核队列。
261+
242262
## 官方参考
243263

244264
- Chrome Web Store API 使用指南:
@@ -249,3 +269,7 @@ skip_review=false
249269
<https://developer.chrome.com/docs/webstore/api/reference/rest/v2/media/upload>
250270
- Chrome Web Store API v2 publish:
251271
<https://developer.chrome.com/docs/webstore/api/reference/rest/v2/publishers.items/publish>
272+
- Chrome Web Store API v2 fetchStatus:
273+
<https://developer.chrome.com/docs/webstore/api/reference/rest/v2/publishers.items/fetchStatus>
274+
- Chrome Web Store API v2 cancelSubmission:
275+
<https://developer.chrome.com/docs/webstore/api/reference/rest/v2/publishers.items/cancelSubmission>

docs/CICD.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
和 skill 下载包,普通安装入口使用 zip/unpacked,其他 manifest、SBOM 和 repo
1313
metadata 留在 workflow artifact 里。手动触发时按输入参数可把
1414
extension 上传并提交到 Chrome Web Store;tag 推送时也可以通过 repository
15-
variable `CWS_AUTO_PUBLISH=true` 自动上传并提交商店审核。新建 GitHub Release 时使用
15+
variable `CWS_AUTO_PUBLISH=true` 自动上传并提交商店审核。商店提交前会检查
16+
active submission,默认遇到审核中或 staged 的旧提交时跳过 CWS 上传但不让
17+
release workflow 因此失败;需要替换旧审核版本时显式开启 cancel pending
18+
submission。新建 GitHub Release 时使用
1619
`gh release create --generate-notes`,交给 GitHub 自动生成 `What's Changed`
1720
`New Contributors``Full Changelog`
1821
- `npm-publish.yml`:tag `v*` 推送触发的 npm 发布流水线,使用 npm
@@ -53,6 +56,8 @@
5356
`HOMEBREW_TAP_TOKEN` 能写入 tap repo。
5457
7. 浏览器插件发布走 `docs/CHROME_WEB_STORE_RELEASE.md` 里的 Chrome Web
5558
Store API v2 流程;需要每个 tag 自动提交时,再开启 `CWS_AUTO_PUBLISH=true`
59+
如果要让最新 tag 取消并替换审核中的旧版本,再开启
60+
`CWS_CANCEL_PENDING_SUBMISSION=true`
5661
8. 技术栈和环境稳定后,再补其他部署 job 或非阻塞的依赖巡检。
5762
9. 即使交付方式变化,release 阶段的 SBOM 和 provenance 这类供应链能力也建议保留。
5863

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
## [2026-05-11 19:47] | Task: Chrome Web Store active submission guard
2+
3+
### 🤖 Execution Context
4+
5+
- **Agent ID**: `Codex`
6+
- **Base Model**: `GPT-5`
7+
- **Runtime**: `Codex CLI`
8+
9+
### 📥 User Query
10+
11+
> Chrome Web Store 已有版本在审核中时,希望发布机制能检测并处理,必要时取消旧审核并用最新版本重发,避免最新版本没发出去。
12+
13+
### 🛠 Changes Overview
14+
15+
**Scope:** Chrome Web Store publish script, release workflows, release docs.
16+
17+
**Key Actions:**
18+
19+
- **[Preflight]**: Added `fetchStatus` before CWS upload and detect active submitted revisions (`PENDING_REVIEW` and `STAGED`).
20+
- **[Default Guard]**: Default behavior now skips CWS upload/submission with `skipped.reason=ACTIVE_SUBMISSION` instead of failing the whole release workflow.
21+
- **[Explicit Replace]**: Added `--cancel-pending`, workflow inputs, and `CWS_CANCEL_PENDING_SUBMISSION=true` to cancel the active CWS submission before uploading the latest zip.
22+
- **[Docs]**: Documented skip vs cancel behavior and linked official `fetchStatus` / `cancelSubmission` references.
23+
24+
### 🧠 Design Intent (Why)
25+
26+
Chrome Web Store rejects edits while an item is already in review. Release automation should not fail GitHub Release, npm, PyPI, and Homebrew delivery because CWS is busy, but replacing an active submission is externally visible and should require an explicit opt-in.
27+
28+
### 📁 Files Modified
29+
30+
- `scripts/publish-chrome-web-store.mjs`
31+
- `.github/workflows/release.yml`
32+
- `.github/workflows/chrome-web-store-publish.yml`
33+
- `docs/CHROME_WEB_STORE_RELEASE.md`
34+
- `docs/CICD.md`
35+
- `docs/releases/feature-release-notes.md`

docs/releases/feature-release-notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
| 日期 | 功能域 | 用户价值 | 变更摘要 |
66
| --- | --- | --- | --- |
7+
| 2026-05-11 | Chrome Web Store Submission Guard | Chrome Web Store 已有版本在审核中时,tag release 不会因为商店拒绝编辑而整体失败;维护者可以选择跳过,或显式取消旧审核并用最新版本重发。 | `publish-chrome-web-store.mjs` 在上传前调用 `fetchStatus`,默认遇到 active submission 写出 `ACTIVE_SUBMISSION` skip 结果;新增 `--cancel-pending`、workflow 输入和 `CWS_CANCEL_PENDING_SUBMISSION` 变量。 |
78
| 2026-05-11 | Guided Store Setup | `open-browser-use setup` 会直接打开 Open Browser Use 的 Chrome Web Store 页面,用户可以在明确的页面里安装或启用扩展并按需重启。 | 发布 `0.1.35` patch 版本,setup 仍会注册 native host 和写入 External Extensions hint,同时新增 `--no-open` 供 CI、测试或无桌面环境只写配置。 |
89
| 2026-05-11 | Chrome Extension Popup | 高分辨率屏幕上 popup 顶部 LOGO 更清晰,不再因为用 32px 位图放大显示而出现明显锯齿。 | 发布 `0.1.34` patch 版本,popup 头部改用 128px icon 作为源图并保持 32 CSS px 显示尺寸;同步 runtime、SDK、extension 版本号和发布制品。 |
910
| 2026-05-11 | Chrome Extension Logo | Chrome Web Store 和浏览器工具栏会显示新版 Open Browser Use 标识,减少旧视觉资产和当前品牌方向不一致的问题。 | 发布 `0.1.33` patch 版本,替换 Chrome extension 的 16/32/48/128 PNG icons 和 1024px source logo,并沿用现有 tag release 与 Chrome Web Store 自动提交链路。 |

scripts/ci.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ node "${repo_root}/scripts/generate-chrome-extension-icons.mjs"
2525
cd "${repo_root}"
2626
go test ./...
2727
node --test apps/chrome-extension/*.test.mjs
28+
node --test scripts/*.test.mjs
2829
pnpm -r --if-present test
2930
)
3031
(

scripts/publish-chrome-web-store.mjs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createSign } from "node:crypto";
55
import path from "node:path";
66
import process from "node:process";
77

8-
const apiRoot = "https://chromewebstore.googleapis.com";
8+
const apiRoot = process.env.CWS_API_ROOT ?? "https://chromewebstore.googleapis.com";
99
const oauthTokenUrl = "https://oauth2.googleapis.com/token";
1010
const chromeWebStoreScope = "https://www.googleapis.com/auth/chromewebstore";
1111

@@ -199,6 +199,44 @@ async function fetchStatus(accessToken) {
199199
});
200200
}
201201

202+
async function cancelSubmission(accessToken) {
203+
return requestJson(`${apiRoot}/v2/${itemName()}:cancelSubmission`, {
204+
method: "POST",
205+
headers: {
206+
Authorization: `Bearer ${accessToken}`
207+
}
208+
});
209+
}
210+
211+
function submittedRevisionState(status) {
212+
return status?.submittedItemRevisionStatus?.state ?? "";
213+
}
214+
215+
function revisionVersions(revisionStatus) {
216+
if (!Array.isArray(revisionStatus?.distributionChannels)) {
217+
return [];
218+
}
219+
return revisionStatus.distributionChannels
220+
.map((channel) => channel?.crxVersion)
221+
.filter((version) => typeof version === "string" && version !== "");
222+
}
223+
224+
function statusSummary(status) {
225+
return {
226+
publishedState: status?.publishedItemRevisionStatus?.state ?? null,
227+
publishedVersions: revisionVersions(status?.publishedItemRevisionStatus),
228+
submittedState: submittedRevisionState(status) || null,
229+
submittedVersions: revisionVersions(status?.submittedItemRevisionStatus),
230+
lastAsyncUploadState: status?.lastAsyncUploadState ?? null,
231+
takenDown: status?.takenDown ?? false,
232+
warned: status?.warned ?? false
233+
};
234+
}
235+
236+
function hasActiveSubmittedRevision(status) {
237+
return ["PENDING_REVIEW", "STAGED"].includes(submittedRevisionState(status));
238+
}
239+
202240
async function waitForUpload(accessToken, uploadResponse) {
203241
let state = uploadResponse.uploadState;
204242
if (state === "SUCCEEDED") {
@@ -253,12 +291,34 @@ async function main() {
253291
}
254292
const absoluteZipPath = path.resolve(zipPath);
255293
const accessToken = await getAccessToken();
256-
const uploadResponse = await uploadPackage(accessToken, absoluteZipPath);
257-
const finalUploadState = await waitForUpload(accessToken, uploadResponse);
294+
const preflightStatus = await fetchStatus(accessToken);
258295
const result = {
259-
uploaded: finalUploadState,
296+
preflightStatus: statusSummary(preflightStatus),
297+
skipped: null,
298+
cancelledSubmission: null,
299+
uploaded: null,
260300
published: null
261301
};
302+
if (hasActiveSubmittedRevision(preflightStatus)) {
303+
if (hasFlag("cancel-pending") || process.env.CWS_CANCEL_PENDING_SUBMISSION === "true") {
304+
result.cancelledSubmission = await cancelSubmission(accessToken);
305+
} else {
306+
result.skipped = {
307+
reason: "ACTIVE_SUBMISSION",
308+
message:
309+
"Chrome Web Store already has an active submitted revision. Re-run with --cancel-pending or CWS_CANCEL_PENDING_SUBMISSION=true to replace it."
310+
};
311+
const outputPath = readFlag("output", process.env.CWS_RESULT_PATH ?? "");
312+
if (outputPath) {
313+
await writeFile(outputPath, `${JSON.stringify(result, null, 2)}\n`);
314+
}
315+
console.log(JSON.stringify(result, null, 2));
316+
return;
317+
}
318+
}
319+
const uploadResponse = await uploadPackage(accessToken, absoluteZipPath);
320+
const finalUploadState = await waitForUpload(accessToken, uploadResponse);
321+
result.uploaded = finalUploadState;
262322
if (hasFlag("submit") || process.env.CWS_SUBMIT_FOR_REVIEW === "true") {
263323
result.published = await publish(accessToken);
264324
}

0 commit comments

Comments
 (0)