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

·23 次浏览
  • 作者
    帖子
  • weixiaoduo@weixiaoduo
    楼主

    背景

    薇晓朵商城 (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>
    
    1. 查找映射 — repos.json 找到 org/repo,product.yaml 找到 WooCommerce product_id 、当前版本、 zip_top_dir
    2. 提取版本号 — 通过 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)
    3. 打包 zip — Gitea archive API 下载 main.zip → 解压 → 重命名顶层目录为 zip_top_dir → 重新打包排除 .git* → 输出 {slug}-{version}.zip
    4. 创建 Gitea Release — POST /repos/{org}/{repo}/releases,tag=v{version},上传 zip 为附件。已存在则跳过创建
    5. 上传到 dl1 — SCP 到 weixiaoduo-prod:/www/wwwroot/dl1.weixiaoduo.com/{YYYY}/{MM}/
    6. 更新 WooCommerce — PUT /wp-json/wc/v3/products/{id},替换 downloads 中名称含” 原版” 的条目 URL 和版本号,保留其他下载项 (中文语言包等)
    7. 更新本地 product.yaml — upstream_version 、 downloads URL/name 、 date_modified
    8. (可选) 触发客户通知 — --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. 版本提取的 「trunk」 陷阱

    部分插件的 readme.txt 里 Stable tag: trunk,这不是真实版本号。解决:维护一个无效版本黑名单 {trunk, stable, dev, master, main},匹配到就跳过继续尝试下一个候选文件。

    2. 并发扫描的连接池溢出

    --check 用 20 个线程并发调 Gitea API,但 requests.Session 默认连接池只有 10,导致大量 「Connection pool is full, discarding connection」 警告。解决:挂载自定义 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 请求

Cyberforums · 原文 · Markdown

想回复或继续讨论?请到 Cyberforums 赛博论坛参与。

参与讨论 / 回复