Repository navigation
ci: 发布前统一校验版本号,四处版本只用脚本改 - #36
Conversation
0.11.0 的 tag 漏升 JS 包版本:npm job 的检查拦住了 npm,Maven 却已发出, 产物坐标 0.11.0、内嵌 TinyUI.version 0.10.1,自相矛盾且不可撤回。 - publish.yml 加 versions 前置 job,核对三个 package.json 与 core 的 VERSION 导出 彼此一致、且等于 tag;maven 与 npm 都 needs 它,任一不符整次发布不出门 - build.yml 的 pnpm job 加 version:check,PR 期就发现四处不一致 - packages/cli/scripts/version.ts:version:set 一次改四处,version:check 校验 - 删掉 tinyui-native 的 VERSION 常量:无文档、无消费方,纯粹多一处要改 - README 补「发布」步骤与 dry-run 用法 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Reviewer's Guide本 PR 将版本号管理从人工同步和发布后检查改为脚本统一写入、构建期校验及发布前置拦截:四处版本镜像由 version:set 维护,version:check 在 PR 和 publish.yml 的 versions job 中执行,并阻止版本不一致时向 Maven Central 或 npm 发布。 Flow diagram for unified version validation and publishingflowchart TD
A["pnpm version:set <x.y.z>"] --> B["Update four version sites"]
B --> C["PR build: pnpm version:check"]
C -->|pass| D["Push matching git tag"]
D --> E["publish.yml: versions job"]
E -->|pass| F["Maven publish"]
E -->|pass| G["npm publish"]
E -->|mismatch| H["Block both publishes"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
摘要
本次改动将版本一致性检查前置到构建与发布流程,并通过脚本集中更新三个包的版本和 core 的 VERSION。发布任务现在会在 Maven 与 npm 之前执行统一校验,整体降低了两种产物版本不一致的风险。主要剩余风险是脚本对版本格式的校验不是严格的 SemVer,仍可能让无效版本通过前置检查并触发部分发布。
整个 PR 至 689cce6 · gpt-5.6-terra
| { file: "packages/cli/package.json", pattern: /("version":\s*")([^"]+)(")/ }, | ||
| { file: "packages/core/src/version.ts", pattern: /(export const VERSION = ")([^"]+)(")/ }, | ||
| ]; | ||
| const VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/; |
There was a problem hiding this comment.
[建议] VERSION 不是严格的 SemVer 校验,例如 01.2.3、1.2.3-a..b 都会通过,但它们不是合法的 npm 包版本。这样四处内容和 tag 即使一致也会通过 versions job,随后 Maven 与 npm 并行发布时,npm 可能因非法版本拒绝而 Maven 已经发布,重新引入部分发布的风险。建议使用严格的 SemVer 校验(特别是禁止主版本前导零、空 prerelease identifier 及 prerelease 数字前导零),或在不安装依赖的前提下实现等价校验。
There was a problem hiding this comment.
Hey - I've found 5 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path=".github/workflows/publish.yml" line_range="21" />
<code_context>
+ - uses: actions/setup-node@v5
+ with:
+ node-version: 22
+ - run: node packages/cli/scripts/version.ts check "${{ github.event_name == 'push' && github.ref_name || '' }}"
+
maven:
</code_context>
<issue_to_address>
**Tags execute commands in CI**
When a matching pushed tag contains shell syntax such as command substitution, the interpolated `github.ref_name` is parsed as part of the `run` script, so commands in the tag execute in the `versions` job before the checker runs. The job has checkout credentials available, and a crafted tag can still let the release jobs proceed.
Pass the tag through an environment variable and quote the variable in the shell command instead of interpolating the tag into the `run` script.
</issue_to_address>
### Comment 2
<location path="packages/native/src/index.ts" line_range="1" />
<code_context>
-export const VERSION = "0.11.1";
export * as navigation from "./navigation.ts";
export * as store from "./store.ts";
</code_context>
<issue_to_address>
**Native VERSION imports fail**
When the native test suite runs or a consumer imports `VERSION` from `tinyui-native`, the removed export makes the native version test fail during module loading, blocking `pnpm test`, builds, and npm publishing; consumers also lose the documented public API without a package-version bump.
Restore or re-export `VERSION` from the native entry point.
</issue_to_address>
### Comment 3
<location path="packages/cli/scripts/version.ts" line_range="14" />
<code_context>
+ { file: "packages/cli/package.json", pattern: /("version":\s*")([^"]+)(")/ },
+ { file: "packages/core/src/version.ts", pattern: /(export const VERSION = ")([^"]+)(")/ },
+];
+const VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
+
+const [mode, arg] = process.argv.slice(2);
</code_context>
<issue_to_address>
**Invalid versions reach release jobs**
When a release version matches this regex but violates SemVer, such as a leading-zero component or malformed prerelease, `VERSION.test` accepts the invalid value, so `set` and `check` pass it. Maven can publish a coordinate that npm rejects, and Kotlin's `SemVer.parse` rejects it in compatibility checks, leaving a partial or unusable release.
Validate versions with a strict SemVer parser before allowing `set` or `check` to succeed.
</issue_to_address>
### Comment 4
<location path="packages/cli/scripts/version.ts" line_range="18-22" />
<code_context>
+
+const [mode, arg] = process.argv.slice(2);
+if (mode === "set" && arg && VERSION.test(arg)) {
+ for (const { file, pattern } of SITES) {
+ const path = join(root, file);
+ const text = await readFile(path, "utf8");
+ if (!pattern.test(text)) throw new Error(`${file}: no version found`);
+ await writeFile(path, text.replace(pattern, `$1${arg}$3`));
+ }
+ process.stdout.write(`${arg} written to ${SITES.length} files\n`);
</code_context>
<issue_to_address>
**Failed setter leaves versions mixed**
When a later target file is missing, unreadable, does not match its pattern, or cannot be written, the loop writes earlier sites before a later read, pattern check, or write fails, then exits with an inconsistent worktree that `version:check` rejects.
Prepare and validate every update before writing, then apply the changes with rollback or another all-or-nothing update strategy.
</issue_to_address>
### Comment 5
<location path="packages/cli/scripts/version.ts" line_range="18-23" />
<code_context>
+
+const [mode, arg] = process.argv.slice(2);
+if (mode === "set" && arg && VERSION.test(arg)) {
+ for (const { file, pattern } of SITES) {
+ const path = join(root, file);
+ const text = await readFile(path, "utf8");
+ if (!pattern.test(text)) throw new Error(`${file}: no version found`);
+ await writeFile(path, text.replace(pattern, `$1${arg}$3`));
+ }
+ process.stdout.write(`${arg} written to ${SITES.length} files\n`);
+} else if (mode === "check" && (arg === undefined || arg === "")) {
</code_context>
<issue_to_address>
**Concurrent updates mix versions**
When two `version:set` commands for different versions run at the same time in one worktree, two `version:set` processes for different versions write the `SITES` files independently without a lock, so their writes can interleave and leave a mixed-version worktree that `version:check` rejects.
Serialize `version:set` invocations with a lock, or make updates derive from a single source of truth.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 5 findings to address first, and a tag name is interpolated directly into a quoted shell command, so a crafted tag could break out of the quotes and execute commands in the versions job. Reverting removes the injection point, but it cannot undo any runner data or credentials accessed before the revert.
Blocking findings: .github/workflows/publish.yml:21, packages/native/src/index.ts:1, packages/cli/scripts/version.ts:14, packages/cli/scripts/version.ts:22, packages/cli/scripts/version.ts:23
| - uses: actions/setup-node@v5 | ||
| with: | ||
| node-version: 22 | ||
| - run: node packages/cli/scripts/version.ts check "${{ github.event_name == 'push' && github.ref_name || '' }}" |
There was a problem hiding this comment.
🔴 Critical · Tags execute commands in CI
When a matching pushed tag contains shell syntax such as command substitution, the interpolated github.ref_name is parsed as part of the run script, so commands in the tag execute in the versions job before the checker runs. The job has checkout credentials available, and a crafted tag can still let the release jobs proceed.
Pass the tag through an environment variable and quote the variable in the shell command instead of interpolating the tag into the run script.
Prompt for AI agents
In `.github/workflows/publish.yml` at line 21:
**Tags execute commands in CI**
When a matching pushed tag contains shell syntax such as command substitution, the interpolated `github.ref_name` is parsed as part of the `run` script, so commands in the tag execute in the `versions` job before the checker runs. The job has checkout credentials available, and a crafted tag can still let the release jobs proceed.
Pass the tag through an environment variable and quote the variable in the shell command instead of interpolating the tag into the `run` script.| { file: "packages/cli/package.json", pattern: /("version":\s*")([^"]+)(")/ }, | ||
| { file: "packages/core/src/version.ts", pattern: /(export const VERSION = ")([^"]+)(")/ }, | ||
| ]; | ||
| const VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/; |
There was a problem hiding this comment.
🟠 High · Invalid versions reach release jobs
When a release version matches this regex but violates SemVer, such as a leading-zero component or malformed prerelease, VERSION.test accepts the invalid value, so set and check pass it. Maven can publish a coordinate that npm rejects, and Kotlin's SemVer.parse rejects it in compatibility checks, leaving a partial or unusable release.
Validate versions with a strict SemVer parser before allowing set or check to succeed.
Prompt for AI agents
In `packages/cli/scripts/version.ts` at line 14:
**Invalid versions reach release jobs**
When a release version matches this regex but violates SemVer, such as a leading-zero component or malformed prerelease, `VERSION.test` accepts the invalid value, so `set` and `check` pass it. Maven can publish a coordinate that npm rejects, and Kotlin's `SemVer.parse` rejects it in compatibility checks, leaving a partial or unusable release.
Validate versions with a strict SemVer parser before allowing `set` or `check` to succeed.| for (const { file, pattern } of SITES) { | ||
| const path = join(root, file); | ||
| const text = await readFile(path, "utf8"); | ||
| if (!pattern.test(text)) throw new Error(`${file}: no version found`); | ||
| await writeFile(path, text.replace(pattern, `$1${arg}$3`)); |
There was a problem hiding this comment.
🟡 Medium · Failed setter leaves versions mixed
When a later target file is missing, unreadable, does not match its pattern, or cannot be written, the loop writes earlier sites before a later read, pattern check, or write fails, then exits with an inconsistent worktree that version:check rejects.
Prepare and validate every update before writing, then apply the changes with rollback or another all-or-nothing update strategy.
Prompt for AI agents
In `packages/cli/scripts/version.ts` at lines 18-22:
**Failed setter leaves versions mixed**
When a later target file is missing, unreadable, does not match its pattern, or cannot be written, the loop writes earlier sites before a later read, pattern check, or write fails, then exits with an inconsistent worktree that `version:check` rejects.
Prepare and validate every update before writing, then apply the changes with rollback or another all-or-nothing update strategy.| for (const { file, pattern } of SITES) { | ||
| const path = join(root, file); | ||
| const text = await readFile(path, "utf8"); | ||
| if (!pattern.test(text)) throw new Error(`${file}: no version found`); | ||
| await writeFile(path, text.replace(pattern, `$1${arg}$3`)); | ||
| } |
There was a problem hiding this comment.
🟡 Medium · Concurrent updates mix versions
When two version:set commands for different versions run at the same time in one worktree, two version:set processes for different versions write the SITES files independently without a lock, so their writes can interleave and leave a mixed-version worktree that version:check rejects.
Serialize version:set invocations with a lock, or make updates derive from a single source of truth.
Prompt for AI agents
In `packages/cli/scripts/version.ts` at lines 18-23:
**Concurrent updates mix versions**
When two `version:set` commands for different versions run at the same time in one worktree, two `version:set` processes for different versions write the `SITES` files independently without a lock, so their writes can interleave and leave a mixed-version worktree that `version:check` rejects.
Serialize `version:set` invocations with a lock, or make updates derive from a single source of truth.- 保留 tinyui-native 的 VERSION:core / native 各有测试要求它等于 package.json,是有意的对称设计 - core 的 VERSION 会编进 core.jsb,升版本必须重生成 RuntimeBytecode.kt;0.11.1 那次漏了,main 的 runtime:check 自 d562719 起失败,本提交补上。version:set 改完五处后自动 build + runtime - publish.yml 的 versions 前置 job 加 pnpm install / build / runtime:check,字节码过期同样不发 - README 更正为五处 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
上轮意见:1 条,已处理 0 条,待处理 1 条
packages/cli/scripts/version.ts:14[建议]VERSION不是严格的 SemVer 校验,例如01.2.3、1.2.3-a..b都会通过,但它们不是合法的 npm 包版…
摘要
本次增量将发布前置校验扩展为安装依赖、构建并校验运行时字节码,同时版本脚本会同步 native 的 VERSION 并重生成字节码。生成的 RuntimeBytecode.kt 已随版本号更新。主要剩余问题是 README 保留了旧的发布步骤,和新的五处校验说明相互矛盾。
| ## 发布 | ||
|
|
||
| 版本号的真源是 `packages/core/package.json`:Kotlin 库的 `TinyUI.version` 由它生成,页面包的 `tinyui` 下限由它写入,所以源码构建与 Maven 产物报同一个数(docs/updates.md §1.1)。它另有四处镜像(native、cli 的 `package.json`,core 与 native 的 `VERSION` 导出),而 core 的 `VERSION` 会编进库内嵌的运行时字节码,所以改版本号一定伴随 `RuntimeBytecode.kt` 的重生成。只用脚本改: | ||
|
|
There was a problem hiding this comment.
[细节] 这里保留了旧的“pnpm version:set 改四处、发布时核对四处”的两步说明,紧接着第 37、38 行又给出了改五处并校验字节码的新说明。读者会看到两套相互矛盾且重复编号的发布流程。应删除旧的第 1、2 步,仅保留与当前脚本和 CI 一致的五处版本及字节码校验说明。
背景
0.11.0 的 tag 漏升 JS 包版本:npm job 的「tag 必须等于包版本」拦住了 npm,但 Maven job 并行、不做这个检查,已经发出。那个产物坐标 0.11.0、内嵌
TinyUI.version却是 0.10.1,自相矛盾且 Maven Central 不可撤回(已用 0.11.1 补齐,宿主一律指向 0.11.1)。版本号的设计本身是对的:
packages/core/package.json是真源,Kotlin 的TinyUI.version与页面包的tinyui下限都从它来,源码构建与产物一致(8c61063)。缺的是:校验只在一个 job 里、事后才跑;四处版本靠手改、PR 期不校验。改动
publish.yml:新增versions前置 job,核对三个package.json与 core 的VERSION导出彼此一致、且等于 tag;maven与npm都needs它,任一不符整次发布不出门。workflow_dispatch时也跑,只校验彼此一致build.yml:pnpm job 加pnpm version:check,PR 期就发现不一致packages/cli/scripts/version.ts:pnpm version:set <x.y.z>一次改四处,pnpm version:check [expected]校验tinyui-native的VERSION常量:无文档、无消费方,纯粹多一处要改。core 的VERSION是 runtime-api.md 的公开导出,保留并由脚本维护验证
本地:
check四处一致通过;check 0.12.0正确列出四处不符并退出 1;set 0.0.0-test后check通过,set 0.11.1还原后工作树无差异。合入后手动跑一次publish.yml(dry-run)验证versionsjob 接线。🤖 Generated with Claude Code
Summary by Sourcery
Centralize version updates and enforce consistency checks throughout pull requests and publishing workflows.
New Features:
Bug Fixes:
Enhancements:
CI:
Documentation: