- publish-app: convert/add Gitea Actions deploy job to publish TickShield apps to the marketplace via aigrego-release (Go static binary) — --store auto-publish + --wait review gate; local-first CLI fetch (ensure-cli.sh) with minIO platform-matrix download + sha256 verify; workflow template and 12-item check script - rename dev-app to app-dev per app-centric naming convention (no tickshield- prefix), documented in AGENTS.md and README - app-dev boundary routes CI publish workflow work to publish-app; both skills are project-level installs only
142 lines
5.6 KiB
Bash
Executable File
142 lines
5.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# publish-app 发布工作流规范自检(零依赖:bash + grep + sed;YAML 语法解析优先 python3,缺失则 SKIP)。
|
||
# 用法:bash check-publish-workflow.sh <workflow 路径>
|
||
# 退出码:任一 die 项 FAIL → 1;仅 WARN/SKIP → 0。
|
||
set -u
|
||
|
||
WF="${1:-}"
|
||
if [ -z "$WF" ] || [ ! -f "$WF" ]; then
|
||
echo "用法: bash $0 <workflow 路径>" >&2
|
||
exit 2
|
||
fi
|
||
ROOT="$(cd "$(dirname "$WF")/../.." && pwd)"
|
||
MANIFEST="$ROOT/app.manifest.json"
|
||
FAIL=0
|
||
|
||
# 多数检查针对剥掉注释后的正文(模板里的禁令注释本身含敏感字样,不能误报)
|
||
CODE="$(mktemp)"
|
||
trap 'rm -f "$CODE"' EXIT
|
||
sed 's/#.*$//' "$WF" > "$CODE"
|
||
|
||
die() { echo "FAIL(die) $1"; FAIL=1; }
|
||
ok() { echo "PASS $1"; }
|
||
wrn() { echo "WARN $1"; }
|
||
skp() { echo "SKIP $1"; }
|
||
|
||
echo "== check-publish-workflow: $WF =="
|
||
|
||
# ① --wait 必须出现在 publish 命令里
|
||
if grep -q 'publish' "$CODE" && grep -q -- '--wait' "$CODE"; then
|
||
ok "① publish 带 --wait(202 进审核退出码仍为 0,靠它门禁)"
|
||
else
|
||
die "① publish 缺 --wait ——治理变更 202 进审核但流水线绿,商店不生效(加 --wait 600)"
|
||
fi
|
||
|
||
# ② 密钥卫生:APP_KEY/APP_SECRET 只经 ${{ secrets.* }} 引用(兼容 shell `=` 与 YAML `:` 两种写法);docker login 走 --password-stdin
|
||
if grep -nE 'APP_(KEY|SECRET)[=:]' "$CODE" | grep -vE 'secrets\.APP_(KEY|SECRET)' | grep -q .; then
|
||
die "② APP_KEY/APP_SECRET 疑似明文/硬编码 ——密钥只走 \${{ secrets.* }} env 引用"
|
||
elif grep -q 'docker login' "$CODE" && ! grep -q -- '--password-stdin' "$CODE"; then
|
||
die "② docker login 未走 --password-stdin ——密码进 argv 会留在日志与进程列表"
|
||
else
|
||
ok "② 密钥引用卫生(secrets 引用 / password-stdin)"
|
||
fi
|
||
|
||
# ③ CLI 获取:禁旧分发仓与 .mjs(平台已显式删除);须按平台矩阵走 TICKSHIELD_RAW_BASE
|
||
if grep -q 'tickshield-dist' "$CODE"; then
|
||
die "③ CLI 地址抄了已停用的 code.innev.cn tickshield-dist 旧仓 ——改用 TICKSHIELD_RAW_BASE + cli/aigrego-release-<os>-<arch>"
|
||
elif grep -q 'aigrego-release\.mjs' "$CODE"; then
|
||
die "③ 引用了 aigrego-release.mjs ——Go 重写后平台已显式删除该物料,引用必 404/拿到不更新的旧版"
|
||
elif grep -q 'TICKSHIELD_RAW_BASE' "$CODE"; then
|
||
ok "③ CLI 按平台矩阵走 TICKSHIELD_RAW_BASE"
|
||
else
|
||
wrn "③ 未见 TICKSHIELD_RAW_BASE 变量 ——若 CLI 为本地预装/vendored 二进制,确认随平台发版更新(对照 cli/sha256sums.txt)"
|
||
fi
|
||
|
||
# ④ publish 必须带 --manifest(image/dist 不可单飞)
|
||
if grep -q -- '--manifest' "$CODE"; then
|
||
ok "④ publish 带 --manifest(CLI 内 manifest 先行,避免 409 submit a manifest first)"
|
||
else
|
||
die "④ publish 缺 --manifest ——无生效版本时单独 --image 会 409"
|
||
fi
|
||
|
||
# ⑤ micro dist:CI 产物无法静态预检则提示
|
||
if grep -q -- '--dist' "$CODE"; then
|
||
if grep -q 'index.html' "$CODE"; then
|
||
ok "⑤ 含 dist 包根 index.html 防线(CI 内 test -f)"
|
||
else
|
||
wrn "⑤ --dist 已配但 workflow 无 index.html 包根自检 ——确认传的是产物目录本身(tar -C dir . 打包,套层即 400)"
|
||
fi
|
||
else
|
||
skp "⑤ 无 --dist(非 micro 或未上传产物)"
|
||
fi
|
||
|
||
# ⑥ listingKey 不另配
|
||
if grep -Eq -- '--key |LISTING_KEY=' "$CODE"; then
|
||
wrn "⑥ 出现 --key/LISTING_KEY 另配 ——listingKey 应从 app.manifest.json 取(另配易 403 mismatch)"
|
||
else
|
||
ok "⑥ listingKey 未另配(单一事实源 = manifest)"
|
||
fi
|
||
|
||
# ⑦ 旧「本机重建容器」部署段残留(改造模式)
|
||
if grep -Eq 'docker rm -f|compose up -d|docker-compose up|docker run[^|]* -d ' "$CODE"; then
|
||
wrn "⑦ 疑似残留本机重建容器部署段 ——部署权责已移交平台编排,确认是有意保留并进汇报"
|
||
else
|
||
ok "⑦ 无旧部署段残留"
|
||
fi
|
||
|
||
# ⑧ 触发:推 main 不发版
|
||
if grep -q 'branches:' "$CODE"; then
|
||
wrn "⑧ 触发含 branches ——确认发版只走 v* tag(推 main 不发版)"
|
||
else
|
||
ok "⑧ 触发不含 branches(v* tag 模型)"
|
||
fi
|
||
|
||
# ⑨ --store 必须出现(否则永不上架,本 skill 目的落空)
|
||
if grep -q -- '--store' "$CODE"; then
|
||
ok "⑨ publish 带 --store 自动上架"
|
||
else
|
||
die "⑨ publish 缺 --store ——不会上架,发布目的落空"
|
||
fi
|
||
|
||
# ⑩ manifest 侧静态预检(仓库根有 manifest 时)
|
||
if [ -f "$MANIFEST" ]; then
|
||
if grep -q '"deployDescriptor"' "$MANIFEST"; then
|
||
die "⑩ app.manifest.json 含 deployDescriptor ——平台管理员专属字段,提交必 403"
|
||
elif ! grep -Eq '"listingKey"[[:space:]]*:[[:space:]]*"[a-z0-9][a-z0-9-]{0,62}"' "$MANIFEST"; then
|
||
die "⑩ listingKey 缺失或不符 ^[a-z0-9][a-z0-9-]{0,62}$"
|
||
else
|
||
ok "⑩ manifest 静态预检(无 deployDescriptor、listingKey 合法)"
|
||
fi
|
||
else
|
||
skp "⑩ 未找到 ${MANIFEST}(非仓库根执行?)"
|
||
fi
|
||
|
||
# ⑪ YAML 语法(python3 + pyyaml 可用时)
|
||
if command -v python3 >/dev/null 2>&1; then
|
||
python3 - "$WF" <<'PY' >/dev/null 2>&1
|
||
import sys
|
||
try:
|
||
import yaml
|
||
except ImportError:
|
||
sys.exit(3)
|
||
yaml.safe_load(open(sys.argv[1]))
|
||
PY
|
||
rc=$?
|
||
if [ "$rc" -eq 0 ]; then ok "⑪ YAML 语法解析通过"
|
||
elif [ "$rc" -eq 3 ]; then skp "⑪ python3 无 pyyaml,语法未检"
|
||
else die "⑪ YAML 语法解析失败"
|
||
fi
|
||
else
|
||
skp "⑪ 无 python3,语法未检"
|
||
fi
|
||
|
||
# ⑫ 新建模式模板槽位不得残留({{SLOT}} 无 $ 前缀,与 Gitea 表达式 ${{ }} 区分)
|
||
if grep -Eq '\{\{[A-Z_]+\}\}' "$CODE"; then
|
||
die "⑫ 模板槽位 {{...}} 未渲染 ——渲染后不得残留(Gitea 表达式是 \${{ }},不受影响)"
|
||
else
|
||
ok "⑫ 无未渲染的 {{SLOT}} 残留"
|
||
fi
|
||
|
||
echo "== done =="
|
||
exit "$FAIL"
|