From 27db54c44e85ea43eb35d7d014b7e9b71e6ee9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=9A=E7=AB=A0=E6=B5=A9?= Date: Mon, 12 Oct 2026 11:15:32 +0800 Subject: [PATCH] =?UTF-8?q?fix(gen):=20=E7=94=9F=E6=88=90=E5=99=A8?= =?UTF-8?q?=E8=90=BD=E9=BD=90=E9=97=A8=E7=A6=81/=E9=A2=84=E8=A7=88/?= =?UTF-8?q?=E6=A3=80=E5=87=BA/=E5=AE=89=E5=85=A8=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E4=B8=8E=E7=AD=96=E7=95=A5=E9=AA=A8=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增落盘:check_secrets.py、ci_preview.py、ci_checkout.sh、test-scope.json(合法但空的骨架)、.gitea/required-contexts.txt(故意留空并说明填法 —— 不预填猜出来的 context,否则会把没跑过必需检查的 PR 放过去)。 --- scripts/new-project.sh | 127 ++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 66 deletions(-) diff --git a/scripts/new-project.sh b/scripts/new-project.sh index fc4173c..11fc397 100755 --- a/scripts/new-project.sh +++ b/scripts/new-project.sh @@ -34,81 +34,73 @@ echo " $DIR" say "2/7 .gitignore / 门禁入口" cp "$TPL/gitignore" .gitignore -# verify.sh:本地门禁 —— 必须与 CI 的 build 作业同一条命令、串行、失败即停(正本 §3.1) +# 门禁入口:**只有一份实现**(scripts/ci_checks.py),verify.sh 只是转发。 +# 为什么不再生成一套 bash 探测逻辑:那会和模板/CI 形成第二套步骤,迟早只改一边。 cat > scripts_verify.tmp <<'SH' #!/usr/bin/env bash -# 本地门禁:一条命令跑完、串行、失败即停、末尾汇总(正本 §3.1)。 -# 与 CI 的 build 作业**同一套步骤**,只允许两处刻意差异(正本 §3.1):外部环境对拍在 CI 跳过、对拍单独一步。 +# 本地门禁入口:转发给 scripts/ci_checks.py —— **不要**在这里再写一套检查。 +# 为什么:CI 调用的是同一条命令(make verify),两边只有一份实现才不会漂移。 set -uo pipefail -LOG="$(mktemp)"; fail=0; n=0; ran="" -step() { n=$((n+1)); echo ""; echo "── [$n] $* ──"; "$@" 2>&1 | tee -a "$LOG"; return "${PIPESTATUS[0]}"; } - -export PATH="/opt/citools/go/bin:/opt/citools/bin:$PATH" - -if [ -f go.mod ]; then - ran="go" - check_gofmt() { # ⚠️ gofmt -l 列出文件时**退出码仍是 0**:判输出,不判退出码 - U="$(gofmt -l $(git ls-files '*.go' 2>/dev/null) 2>/dev/null)" - [ -z "$U" ] || { echo "未格式化的文件:"; printf '%s\n' "$U"; return 1; } - } - step check_gofmt || fail=1 # 格式:CI 会因为这一条红 - step go vet ./... || fail=1 # 静态检查:类型错了后面全是噪声 - step go build ./... || fail=1 # 构建 - step go test ./... || fail=1 # 单测(Go 会自己编译测试程序;不是"测构建产物") -fi -if [ -f pyproject.toml ] || [ -f requirements.txt ] || [ -f setup.py ]; then - ran="$ran python" - step ruff format --check . || fail=1 - step ruff check . || fail=1 - step uv run pytest -q || fail=1 -fi -if [ -f package.json ]; then - ran="$ran node" - if [ -f package-lock.json ]; then - step npm ci || fail=1 # 按锁装;缺锁**不**退化成 npm install(那会重写锁文件) - else - echo "❌ 没有 package-lock.json:不接受无约束安装(先提交锁文件)"; fail=1 - fi - if grep -q '"format:check"[[:space:]]*:' package.json 2>/dev/null; then - step npm run format:check || fail=1 - else - echo "❌ package.json 未声明 format:check —— 前端格式没有门禁(加 prettier --check)"; fail=1 - fi - step npm run build || fail=1 - step npm test || fail=1 -fi -if [ -z "$ran" ]; then echo ""; echo "⚠️ 未识别到构建入口(go.mod / pyproject.toml / package.json 都没有)—— 入口待补,不算全绿"; fail=1; fi - -echo ""; echo "════════════════════════════════════════" -if [ "$fail" -eq 0 ]; then - echo "✅ 本地门禁全过" - rm -f "$LOG" # 夹具要打扫(正本 §3.2-5):成功时不留日志,否则 $TMPDIR 会攒到几万条 -else - echo "❌ 门禁未通过 —— 先修它,再提交" - echo "(完整日志留在:$LOG)" -fi -exit "$fail" +exec python3 "$(dirname "$0")/ci_checks.py" "$@" SH mkdir -p scripts && mv scripts_verify.tmp scripts/verify.sh && chmod +x scripts/verify.sh cat > Makefile <<'MK' -# 本地门禁入口:一条命令跑完(正本 §3.1)。CI 的 build 作业跑同一套步骤。 -# 名字统一用 `verify`(如本环境另有一套叫 `check` 的历史习惯,让它成为别名,别起第二套逻辑)。 -.PHONY: verify check +# 本地门禁入口:一条命令跑完(正本 §3.1)。CI 跑的是**同一条命令**。 +# 实现只有一份:scripts/ci_checks.py(由 templates/ci-gate.py 生成)。 +.PHONY: verify check list verify: - bash scripts/verify.sh + python3 scripts/ci_checks.py check: verify +list: + python3 scripts/ci_checks.py --list MK -say "3/7 CI 工作流" +say "3/7 CI 工作流(只做 CI;**默认不生成 CD 工作流**)" mkdir -p .gitea/workflows .gitea/scripts docs -sed "s||$NAME|g" "$TPL/gitea-build.yml" > .gitea/workflows/build.yml -sed "s||$NAME|g" "$TPL/gitea-prod-deploy.yml" > .gitea/workflows/deploy-production.yml +sed "s||$NAME|g" "$TPL/gitea-build.yml" > .gitea/workflows/build.yml +echo " build.yml(只此一个:CI 不含部署)" +echo " 要 CD 时**显式**拷 templates/gitea-prod-deploy.yml 过去 —— 它的未实现步骤故意是红的" -say "4/7 闸门与上报脚本(从正本仓逐字节复制)" -cp "$TPL/ci_gate.py" .gitea/scripts/ci_gate.py -cp "$TPL/ci_report.py" .gitea/scripts/ci_report.py -chmod +x .gitea/scripts/*.py +say "4/7 门禁实现、上报、预览、检出脚本" +cp "$TPL/ci-gate.py" scripts/ci_checks.py # 权威门禁:唯一一份检查实现 +cp "$TPL/check-secrets.py" scripts/check_secrets.py # 密钥扫描(门禁的 security 项会调它) +cp "$TPL/ci_gate.py" .gitea/scripts/ci_gate.py # 提交信息闸门(按准确 Git 范围校验) +cp "$TPL/ci_report.py" .gitea/scripts/ci_report.py # 核对报告 + 上报(--verify-only 可单独核对) +cp "$TPL/ci_preview.py" .gitea/scripts/ci_preview.py # **先核对完整报告,再发预览** +cp "$TPL/ci_checkout.sh" .gitea/scripts/ci_checkout.sh # 检出:固定事件 SHA、协议不降级、URL 不放凭据 +chmod +x scripts/ci_checks.py scripts/check_secrets.py .gitea/scripts/*.py .gitea/scripts/*.sh + +# 测试范围声明:门禁的 tests 项要读它("允许跳过哪些用例""前端用什么跑测试")。 +# 给一份**合法但空**的骨架 —— 空声明不等于"随便跳",必需用例被跳过照样红。 +cat > scripts/test-scope.json <<'JS' +{ + "_doc": [ + "测试范围与前端测试框架的声明(门禁的 tests 项读它)。strict schema:", + "· optional 条目必须给 package 或 test(至少一个)—— 两个都不给会通配任意跳过,不允许;", + "· 每条必须有 reason 与 owner;package 级条目还要给 expected_skips(跳过数不许无声明地涨);", + "· review_by 是到期日(YYYY-MM-DD),过期 ⇒ 整份声明失效。", + "frontend_runner:前端用什么跑测试(vitest / jest)。不写就按 package.json 依赖认;认不出来 ⇒ 明确失败。" + ], + "frontend_runner": "vitest", + "optional": [] +} +JS +echo " scripts/test-scope.json(空声明骨架:允许跳过哪些用例要在这里写明)" + +# 合并策略文件:**必须由你填**,不填就等于没有"要求哪些检查" ⇒ 合并会被拒绝。 +# 这里不预填一个猜出来的 context —— 猜错了会把没跑过必需检查的 PR 放过去。 +cat > .gitea/required-contexts.txt <<'CTX' +# 合并前必须在 PR 头提交上 success 的 status context(一行一个,`#` 后是注释)。 +# +# ⚠️ 这份文件是"要求哪些检查"的**唯一来源**:合并脚本只认它或显式 --require-context, +# 不接受"观察到的状态"。文件为空 / 只有注释 ⇒ 视为没有策略 ⇒ 拒绝合并。 +# +# 怎么填:workflow 跑过一次之后,看那次提交的 status context(Gitea 界面或 +# GET /api/v1/repos///commits//status ),把它逐行抄下来。 +# 例如:build / build (push) +CTX +echo " ⚠️ .gitea/required-contexts.txt 还是空的:填上真实 context 之前,--merge-pr 会拒绝合并" say "5/7 AGENTS.md(从 templates/README.md 的骨架抽取并替换)" python3 - "$TPL/README.md" "$NAME" "$REPO" > AGENTS.md <<'PY' @@ -193,11 +185,14 @@ cat <