From 7fd3ef0ca52e2b1554118afd5c09ef05f87deb37 Mon Sep 17 00:00:00 2001 From: Episkey Date: Tue, 18 Aug 2026 04:13:26 -0700 Subject: [PATCH 1/3] =?UTF-8?q?ci:=20=E5=BB=BA=E7=AB=8B=E5=8F=AF=E7=BA=A2?= =?UTF-8?q?=E7=9A=84=20PR=20=E9=97=A8=E7=A6=81=20ci-gate=EF=BC=8C=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E7=9F=A9=E9=98=B5=E7=A7=BB=E5=87=BA=20PR=20=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit codegen 机器人开的 PR 此前无任何能作为合并条件的 check:原 workflow.yml 只做覆盖率上报,且挂在裸 [push, pull_request] 上。本次补齐门禁,源码零改动。 为什么这么定: - 新增 Makefile 的 ci-syntax,分两段闸: G1 `tsc --noEmit` —— 这个仓此前完全没有类型检查入口(npm run build 会写 lib/,不适合进门禁),生成代码的正确性无人把关。 G2 dup-check.sh —— tsc 对同一文件内重复的 export interface 走 declaration merging,静默通过。已实测:注入重复 interface 后 tsc 仍 exit 0,dup-check exit 1。故 G1 抓不到重复声明,必须另加显式检测。 - 不设 lint job。tslint 当前 4017 条报错(生成侧 3943 + 手写侧 74)全是风格 问题,无一影响运行——同一批代码 tsc --noEmit exit 0、输出 0 行。加一个恒红 的非阻断 job 只会在每个 PR 上留红叉、制造「CI 坏了」的误解,反而拉低门禁 可信度。生成代码的正确性由 ci-syntax 承担。理由已写进 ci.yml 注释。 - 聚合 job 恒名 ci-gate(needs + if: always(),任一 needs 失败或被取消则 exit 1)。分支保护只需把 ci-gate 配成 required check,其余 job 增删不影响它。 - node 10/12/14 矩阵下移到 compat.yml,只留 schedule + workflow_dispatch。 它回答的是「声明的下界今天还装不装得上」,与某个 PR 改得对不对无关;留在 PR 上会给每次 codegen 提交增加无关 check。fail-fast: false 以便一次看全 哪几个版本还活着。 - 摘掉 codecov 的 fail_ci_if_error: true 并加 continue-on-error。原配置把 第三方服务的可用性接进了合并条件;codecov 挂了不该把 PR 拦死。 - 触发从裸 [push, pull_request] 收窄为 master 的 pull_request + push; 补显式 permissions: contents: read。 源码一行未改:src/services/ 生成代码与 src/core/ 手写代码均未触碰。 特别地未动 src/core/transport/index.ts:50 的 `!=`——getRetCode() 运行时 可能返回字符串,改成 `!==` 会引入真 bug。 本地验证:tsc --noEmit exit 0;dup-check ts src 扫 53 文件 0 重复 exit 0; make test-cov 5 suites / 11 tests 全过 exit 0。反向验证:注入类型错后 tsc exit 1、make ci-syntax exit 2,还原后恢复 exit 0。 --- .github/workflows/ci.yml | 75 ++++++++++++++++++++++++++++++++++ .github/workflows/compat.yml | 45 ++++++++++++++++++++ .github/workflows/workflow.yml | 34 --------------- Makefile | 12 ++++++ scripts/dup-check.sh | 61 +++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/compat.yml delete mode 100644 .github/workflows/workflow.yml create mode 100755 scripts/dup-check.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5522f26 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,75 @@ +name: CI + +# PR 门禁。只保两件事(详见 Makefile 的 ci-syntax): +# G1 生成的代码能通过类型检查 +# G2 生成的代码没有重复声明 +# +# 本仓**没有 lint job**,是有意为之,不是漏配: +# · `npm run tslint` 当前 4017 条报错(生成侧 3943 + 手写侧 74),全部是风格问题, +# 无一影响运行。决定性证据:同一批代码 `tsc --noEmit` exit 0、输出 0 行。 +# · 加一个恒红的非阻断 job,只会在每个 PR 上留个红叉、制造「CI 坏了」的误解, +# 反而降低门禁的可信度。 +# · 生成代码的正确性由 ci-syntax 的 `tsc --noEmit` 承担;风格问题不影响 SDK 能否使用。 +# 要恢复 lint 门禁,前提是先把那 4017 条清零(改模板或改 tslint 配置),不是直接加 job。 +# +# 分支保护只需把 `ci-gate` 配成 required check,其余 job 增删都不影响它。 +# +# node 10/12/14 的兼容矩阵已下移到 compat.yml(定时跑), +# 不放在 PR 路径上:那是「声明的下界还能不能装」的问题,与本次改动是否正确无关。 + +on: + pull_request: + branches: [master] + push: + branches: [master] + +permissions: + contents: read + +jobs: + ci-syntax: + name: ci-syntax + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: '22' + # 本仓没有提交 package-lock.json,故用 npm install 而非 npm ci。 + # 也因此不开 setup-node 的 cache 输入——它的缓存 key 由 lockfile 算出。 + - run: npm install + - run: make ci-syntax + + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: '22' + - run: npm install + - run: make test-cov + # codecov 是可观测性,不是门禁:它挂了不该把 PR 拦死。 + # 原 workflow.yml 是 fail_ci_if_error: true,等于把第三方服务的可用性 + # 接进了合并条件,这里摘掉,并再加一层 continue-on-error。 + - name: Upload coverage + continue-on-error: true + uses: codecov/codecov-action@v7 + with: + fail_ci_if_error: false + + ci-gate: + name: ci-gate + needs: [ci-syntax, test] + if: always() + runs-on: ubuntu-latest + steps: + - name: Aggregate + run: | + if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" \ + || "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then + echo "ci-gate: 有 job 失败或被取消" + exit 1 + fi + echo "ci-gate: 全部通过" diff --git a/.github/workflows/compat.yml b/.github/workflows/compat.yml new file mode 100644 index 0000000..15c87ca --- /dev/null +++ b/.github/workflows/compat.yml @@ -0,0 +1,45 @@ +name: Compatibility + +# 老版本 node 兼容矩阵。原 workflow.yml 把 node 10/12/14 挂在裸 [push, pull_request] 上, +# 这里把它从 PR 路径整体移出,只留定时 + 手动触发。三条理由: +# 1. 它回答的是「声明支持的下界今天还装不装得上」,与某个 PR 改得对不对无关; +# 放在 PR 上只会给每次 codegen 提交增加与改动无关的 check。 +# 2. 这些版本的失败多半来自外部环境漂移(runner 镜像换代、npm registry 的 +# TLS/协议要求提高、依赖发布新版丢掉老 engines 支持),不是代码回归。 +# 让它去阻断合并,等于把上游漂移变成本仓的合并事故。 +# 3. 门禁只认 ci.yml 的 ci-gate;这里刻意不产出任何 required check。 +# +# fail-fast: false —— 目的是一次跑出「哪几个版本还活着」的完整结论, +# 而不是在第一个挂掉的版本上就停下、看不到其余版本的状态。 +# +# 版本可行性说明(2026-08-18 查证):setup-node@v7 的 node-version 输入 +# 在其 README 里仍以 `12.x` / `10.15.1` 作为合法语法示例,且在 node-versions +# manifest 未命中时会回落到 nodejs.org/dist 直下,语法层面 10/12/14 仍受支持。 +# 但**未实测**这些老二进制能否在当前 ubuntu-latest 镜像上跑起来 +# (glibc、以及 node 10 自带的老 npm 能否完成 registry 握手,均未验证)。 +# 若某个版本在此长期红着,先判断是环境漂移还是真实回归,再决定是降下界还是修代码。 + +on: + schedule: + # 每周一 03:00 UTC。频率取周级即可:这里跟踪的是外部环境漂移,不是每次提交。 + - cron: '0 3 * * 1' + workflow_dispatch: + +permissions: + contents: read + +jobs: + legacy-node: + name: node ${{ matrix.node-version }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: ['10', '12', '14'] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: make test-cov diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml deleted file mode 100644 index fddf2dc..0000000 --- a/.github/workflows/workflow.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Running Code Coverage - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [10.x, 12.x, 14.x] - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - - name: Set up Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - - name: Install dependencies - run: npm install - - - name: Run the tests - run: npm run coverage - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - fail_ci_if_error: true diff --git a/Makefile b/Makefile index 564a693..56cf654 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,18 @@ test: test-cov: npm run coverage +# CI 门禁:生成代码必须能通过类型检查、且无重复声明。 +# G1 `tsc --noEmit` 覆盖 tsconfig include 的 src/**/*(含 services/ 下全部生成代码)。 +# 这个仓此前完全没有类型检查入口——`npm run build` 会写 lib/,不适合放进门禁。 +# G2 `tsc` 对同一文件内重复的 `export interface Foo` 走 declaration merging, +# 静默通过、不报错,所以 G1 抓不到重复声明,必须另加显式检测。 +# (对照 go:重复声明是编译错误,G1 天然覆盖,故 go 侧无需此闸。) +# 代码风格(tslint / prettier)不进门禁,理由见 .github/workflows/ci.yml。 +.PHONY: ci-syntax +ci-syntax: + npx tsc --noEmit + @bash $(CURDIR)/scripts/dup-check.sh ts src + lint: npm run tslint npm run prettier:check diff --git a/scripts/dup-check.sh b/scripts/dup-check.sh new file mode 100755 index 0000000..bb8774f --- /dev/null +++ b/scripts/dup-check.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# G2 重复闸 —— php / js 生成代码重复顶层声明检测。 +# +# 背景(实测,非推断): +# · php:`php -l` 能抓重复 `use`(编译期符号表冲突),但对同一文件内两个 +# `class Foo` 一律 exit 0 —— 那是运行期错误,lint 阶段看不到。 +# · js/ts:`tsc --noEmit` 对重复的 `export interface Foo` 走 declaration merging, +# 静默通过;只有同一 interface 内重复成员才报 TS2300。 +# 故两者都需要显式的重复声明检测。 +# +# 用法:dup-check.sh php <目录> +# dup-check.sh ts <目录> +# 退出码:0 = 无重复;1 = 发现重复(逐条打印 文件:声明名 出现次数) + +set -uo pipefail + +lang="${1:?用法: dup-check.sh <目录>}" +root="${2:?用法: dup-check.sh <目录>}" + +case "$lang" in + php) + ext='*.php' + # 顶层 class / interface / trait 声明;兼容 final / abstract 前缀 + pattern='^[[:space:]]*(final[[:space:]]+|abstract[[:space:]]+)?(class|interface|trait)[[:space:]]+[A-Za-z0-9_]+' + ;; + ts) + ext='*.ts' + # 导出的顶层 interface / class / type / enum 声明 + pattern='^export[[:space:]]+(interface|class|type|enum)[[:space:]]+[A-Za-z0-9_]+' + ;; + *) + echo "不支持的语言: $lang(仅 php / ts)" >&2 + exit 2 + ;; +esac + +findings=0 +scanned=0 + +while IFS= read -r f; do + scanned=$((scanned + 1)) + # 抽出声明名(每行最后一个标识符),排序后取重复项 + dups=$(grep -oE "$pattern" "$f" 2>/dev/null | awk '{print $NF}' | sort | uniq -d) + if [ -n "$dups" ]; then + while IFS= read -r name; do + [ -z "$name" ] && continue + n=$(grep -cE "$pattern[[:space:]]*\$|${pattern}([[:space:]]|\{)" "$f" 2>/dev/null || true) + lines=$(grep -nE "$pattern" "$f" | awk -v want="$name" '$NF==want || $0 ~ ("[[:space:]]"want"([[:space:]]|\\{|$)") {split($0,a,":"); printf "%s,", a[1]}') + echo " ${f}:${lines%,} 重复声明 '${name}'" + findings=$((findings + 1)) + done <<< "$dups" + fi +done < <(find "$root" -name "$ext" -type f) + +if [ "$findings" -gt 0 ]; then + echo "G2 重复闸失败:发现 $findings 处(扫描 $scanned 个文件)" + exit 1 +fi + +echo "G2 重复闸通过:$scanned 个文件无重复顶层声明" +exit 0 From 1a79befa0c8a0e4b61ca5d42818d8ab53c6f93ba Mon Sep 17 00:00:00 2001 From: Episkey Date: Tue, 18 Aug 2026 04:14:46 -0700 Subject: [PATCH 2/3] =?UTF-8?q?ci:=20=E8=AE=A2=E6=AD=A3=20ci.yml=20?= =?UTF-8?q?=E4=B8=AD=E5=85=B3=E4=BA=8E=20lockfile=20=E7=9A=84=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一条 commit 的注释写成「本仓没有提交 package-lock.json」,与事实不符: package-lock.json 和 yarn.lock 都在版本控制里。 实际情况(已核实):package-lock.json 是 lockfileVersion 1(npm 6 时代格式), 其 root version 停在 0.2.25,而 package.json 已是 0.2.26,本就不同步。 选 npm install 而非 npm ci 的真实理由是这个——npm ci 要求 lock 与 package.json 严格一致,拿它当门禁第一步会让一个与改动无关的陈旧文件决定 PR 红绿。 只改注释,yml 行为不变。 --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5522f26..e55c8b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,8 +35,11 @@ jobs: - uses: actions/setup-node@v7 with: node-version: '22' - # 本仓没有提交 package-lock.json,故用 npm install 而非 npm ci。 - # 也因此不开 setup-node 的 cache 输入——它的缓存 key 由 lockfile 算出。 + # 用 npm install 而非 npm ci:仓里的 package-lock.json 是 lockfileVersion 1 + # (npm 6 时代格式),且其 root version 停在 0.2.25、package.json 已是 0.2.26, + # 本就不同步。npm ci 要求 lock 与 package.json 严格一致,拿它当门禁第一步, + # 等于让一个与本次改动无关的陈旧文件决定 PR 红绿。 + # npm install 是原 workflow.yml 一直在用、且已在 node 22/24 上实测通过的路径。 - run: npm install - run: make ci-syntax From 09c80572b495e2312d7052281ba32e6d810ad804 Mon Sep 17 00:00:00 2001 From: Episkey Date: Wed, 19 Aug 2026 01:43:28 -0700 Subject: [PATCH 3/3] =?UTF-8?q?ci:=20=E5=8E=BB=E6=8E=89=20compat.yml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 兼容矩阵是纯巡检,不参与门禁判定,也没人会盯一个非阻断的定时任务。 声明下界还成不成立是另一个问题,要验的时候手工跑一次即可, 不必为它常驻一条每天运行的 workflow。 --- .github/workflows/ci.yml | 5 ++-- .github/workflows/compat.yml | 45 ------------------------------------ 2 files changed, 3 insertions(+), 47 deletions(-) delete mode 100644 .github/workflows/compat.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e55c8b8..8ee1162 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,8 +14,9 @@ name: CI # # 分支保护只需把 `ci-gate` 配成 required check,其余 job 增删都不影响它。 # -# node 10/12/14 的兼容矩阵已下移到 compat.yml(定时跑), -# 不放在 PR 路径上:那是「声明的下界还能不能装」的问题,与本次改动是否正确无关。 +# 原 workflow.yml 的 node 10/12/14 矩阵已去掉,不再在 PR 上跑: +# 那是「声明的下界还能不能装」的问题,与本次改动是否正确无关, +# 不该让每个 PR 都为它等三份构建。要验的时候手工跑一次即可。 on: pull_request: diff --git a/.github/workflows/compat.yml b/.github/workflows/compat.yml deleted file mode 100644 index 15c87ca..0000000 --- a/.github/workflows/compat.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Compatibility - -# 老版本 node 兼容矩阵。原 workflow.yml 把 node 10/12/14 挂在裸 [push, pull_request] 上, -# 这里把它从 PR 路径整体移出,只留定时 + 手动触发。三条理由: -# 1. 它回答的是「声明支持的下界今天还装不装得上」,与某个 PR 改得对不对无关; -# 放在 PR 上只会给每次 codegen 提交增加与改动无关的 check。 -# 2. 这些版本的失败多半来自外部环境漂移(runner 镜像换代、npm registry 的 -# TLS/协议要求提高、依赖发布新版丢掉老 engines 支持),不是代码回归。 -# 让它去阻断合并,等于把上游漂移变成本仓的合并事故。 -# 3. 门禁只认 ci.yml 的 ci-gate;这里刻意不产出任何 required check。 -# -# fail-fast: false —— 目的是一次跑出「哪几个版本还活着」的完整结论, -# 而不是在第一个挂掉的版本上就停下、看不到其余版本的状态。 -# -# 版本可行性说明(2026-08-18 查证):setup-node@v7 的 node-version 输入 -# 在其 README 里仍以 `12.x` / `10.15.1` 作为合法语法示例,且在 node-versions -# manifest 未命中时会回落到 nodejs.org/dist 直下,语法层面 10/12/14 仍受支持。 -# 但**未实测**这些老二进制能否在当前 ubuntu-latest 镜像上跑起来 -# (glibc、以及 node 10 自带的老 npm 能否完成 registry 握手,均未验证)。 -# 若某个版本在此长期红着,先判断是环境漂移还是真实回归,再决定是降下界还是修代码。 - -on: - schedule: - # 每周一 03:00 UTC。频率取周级即可:这里跟踪的是外部环境漂移,不是每次提交。 - - cron: '0 3 * * 1' - workflow_dispatch: - -permissions: - contents: read - -jobs: - legacy-node: - name: node ${{ matrix.node-version }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node-version: ['10', '12', '14'] - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-node@v7 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: make test-cov