---
canonical: https://meta.cyberforums.com/t/fei-publish/1829
webpage: https://wpcy.com/community/t/1829-fei-publish
language: zh-Hans
---

# Fei publish：从仓库到商城的自动化发布管道

> 文派叶子支持论坛的只读副本，本页不能回复。
> 讨论原文：https://meta.cyberforums.com/t/fei-publish/1829
> 网页版：https://wpcy.com/community/t/1829-fei-publish

## 1 · weixiaoduo

- 发布时间：2026年3月1日 03:45

背景
薇晓朵商城（mall.weixiaoduo.com）的产品更新链路长期断裂——dl1 上 83% 的文件是 2017-2022 年上传的，feicode 仓库有 457 个有代码但 0 个 release。手动更新一个产品需要：下载源码 → 打包 zip → 上传 dl1 → 创建 Release → 更新 WooCommerce 下载链接 → 更新本地 YAML，6 个步骤，容易出错且耗时。
Elementor Pro 试点手动跑通全流程后，我们把它固化成了 fei publish 命令。
架构
采用混合委托模式：

~/bin/fei（Bash）加了一个 cmd_publish() 薄壳，exec python3 委托给 Python 脚本
~/Projects/mall-products/feicode/fei-publish.py（Python）承载主逻辑
复用现有数据：repos.json（530 个仓库映射）、product.yaml（371 个产品定义）、.env.secrets（凭证）

这样 fei CLI 保持统一入口，Python 处理复杂逻辑（API 调用、zip 操作、YAML 读写）。
单个产品发布流程（8 步）
fei publish <slug>


查找映射 — repos.json 找到 org/repo，product.yaml 找到 WooCommerce product_id、当前版本、zip_top_dir
提取版本号 — 通过 Gitea raw API 远程读取（不需要 clone），按优先级尝试：

metadata.main_file 指定的文件 → Version: header
{slug}.php → Version: header
style.css → Version: header（主题）
readme.txt → Stable tag:
过滤无效值（trunk/stable/dev/master/main）


打包 zip — Gitea archive API 下载 main.zip → 解压 → 重命名顶层目录为 zip_top_dir → 重新打包排除 .git* → 输出 {slug}-{version}.zip
创建 Gitea Release — POST /repos/{org}/{repo}/releases，tag=v{version}，上传 zip 为附件。已存在则跳过创建
上传到 dl1 — SCP 到 weixiaoduo-prod:/www/wwwroot/dl1.weixiaoduo.com/{YYYY}/{MM}/
更新 WooCommerce — PUT /wp-json/wc/v3/products/{id}，替换 downloads 中名称含"原版"的条目 URL 和版本号，保留其他下载项（中文语言包等）
更新本地 product.yaml — upstream_version、downloads URL/name、date_modified
（可选）触发客户通知 — --notify 参数，通过 prod-query 调用 wdpum 排队通知

批量操作
fei publish --check              # 并发扫描所有产品版本差异（20 线程，297 个产品 3 秒完成）
fei publish --auto --limit 10    # 自动发布前 10 个有差异的产品
fei publish <slug> --dry-run     # 模拟运行，不实际执行
fei publish <slug> --force       # 强制发布（即使版本相同）

踩过的坑
1. 版本提取的 &#12300;trunk&#12301; 陷阱
部分插件的 readme.txt 里 Stable tag: trunk，这不是真实版本号。解决：维护一个无效版本黑名单 {trunk, stable, dev, master, main}，匹配到就跳过继续尝试下一个候选文件。
2. 并发扫描的连接池溢出
--check 用 20 个线程并发调 Gitea API，但 requests.Session 默认连接池只有 10，导致大量 &#12300;Connection pool is full, discarding connection&#12301; 警告。解决：挂载自定义 HTTPAdapter，pool_connections=20, pool_maxsize=20。
3. zip 顶层目录名不一致
Gitea archive API 下载的 zip 顶层目录是 {repo}/，但 WordPress 插件/主题要求顶层目录名与 slug 一致（有些产品还有特殊的 zip_top_dir，比如 astra 主题的子主题目录是 astra-wei）。解决：解压后根据 product.yaml 的 metadata.zip_top_dir 重命名。
4. WooCommerce 下载项的精确替换
一个产品可能有多个下载项（原版插件包、中文语言包、子主题等），更新时只替换名称含"原版"的条目，保留其他不变。如果没有"原版"条目则插入一个新的。
数据依赖关系
repos.json (530 仓库)
    ↓ slug → org/repo
product.yaml (371 产品)
    ↓ id, downloads, metadata.upstream_version, metadata.zip_top_dir
.env.secrets
    ↓ FEICODE_TOKEN, WC_CONSUMER_KEY/SECRET, WC_STORE_URL

日志和回滚

每次发布写 JSON 日志到 publish-log/{date}-{slug}.json
WooCommerce 更新前备份当前下载列表到日志
dl1 上传是追加式的（新文件新路径），WC 更新失败不会丢失已上传的文件

下一步

当仓库有新代码推送时，考虑 Gitea webhook 触发自动发布
--check 可以做成定时任务，每天扫描一次，有更新就通知
批量发布加上速率限制，避免短时间内大量 API 请求
