我扫了 422 个仓库,近一半 CI 在裸装依赖

0 阅读4分钟

我把 GitHub 上 star 最高的 500 个 JavaScript / TypeScript 仓库的 workflow 目录拉了下来,本来只是想看看别人的缓存 key 怎么写。

看完了,key 基本没人写错。422 个有 workflow 的仓库里,380 个会装依赖,其中 107 个仓库,一条缓存都没配。

装依赖的 workflow 一共 2,032 个,有缓存配置的 1,098 个,剩下 934 个,46%,连缓存的边都没碰。

先说扫描口径

标准是动手前定的,不是扫完再挑:

500 个仓库里 78 个没有 workflow 目录,剩 422 个,共 4,845 个文件。抓取只拉 workflow 目录,业务代码一个字节没下:

git clone --depth 1 --filter=blob:none --sparse <repo>
git sparse-checkout set .github/workflows

判定靠正则:出现 npm ci、yarn、pnpm install、bun install 算装依赖;出现 cache: npm 这类 setup-* 托管缓存,或 uses: actions/cache@ 算有缓存。

正则不是 YAML 解析器,藏在复合动作、可复用 workflow 里的缓存我看不见。所以下面的数字都是下限,不是审计报告。

缓存 key,大家其实没配错

这是我出发时最想抓的现行,一个没抓到。

开缓存的 1,098 个 workflow 里,818 个用的是托管缓存:

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: npm

这一行背后是三条规则:key 由 runner.os、node 版本和 lockfile 的 hash 拼成,路径指向 npm 的全局缓存目录,命中就跳过下载。你不需要记路径,也不该改 key——改了就变回手写那批了。

手写 actions/cache 的那批,362 个依赖缓存步骤里,317 个 key 里带 hashFiles(...lock...),87.6%。

我一开始把「key 用 commit id」的全标成了错误。抽查 mastra 和 airi 之后划掉了:人家后面跟着 restore-keys,每次跑出新 key,回退时照样命中上一次的缓存。这是标准的构建缓存写法,不是错。

教程里那句「key 一定要带 lockfile 的 hash」,头部仓库早就吃透了。

真正的问题:根本没有

934 个没缓存的 workflow,我逐个找理由:

131 个是主动关的,package-manager-cache: false;46 个仓库有 turbo 或 nx 的远程缓存;38 个在 docker build 里装依赖,靠的是镜像层。

扣掉这三类,剩下 733 个找不到理由。里面有一部分是只跑 lint 的流水线,装依赖只为执行一个 prettier,这种不缓存不算冤枉,但也没必要单开一个 workflow。

仓库级更直白:107 个仓库、279 个装依赖的 workflow,缓存一次都没开过。

按我的体会,门槛不在「key 怎么写」,在「要不要碰 CI」。很多仓库的流水线是前人搭好的,跑得动就没人动,慢三分钟也没人算这笔账。

另一种浪费:配了等于没配

129 个 job 缓存了 node_modules,其中 65 个同时跑 npm ci。这 65 个里,44 个写法是对的:

- uses: actions/cache@v4
  id: cache
  with:
    path: node_modules
    key: deps-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- run: npm ci
  if: steps.cache.outputs.cache-hit != 'true'

命中就不装,没命中才装。

另外 21 个(分布在 7 个仓库)少了那行 if:缓存恢复完,紧接着无条件 npm ci。而 npm ci 的第一步就是把 node_modules 整个删掉,重装一遍。 headlessui 的 install job 就是这样,缓存 **/node_modules,下一行 run: npm ci。

缓存的下载、解压、最后回存,全白跑。没有报错,没有警告,只是慢。

三个自查动作

# 有没有开缓存
grep -rn "actions/cache\|cache: npm\|cache: pnpm\|cache: yarn" .github/workflows

# 有没有配了却用不上
grep -rn -A 6 "uses: actions/cache" .github/workflows | grep -B 6 "npm ci"

第三条只能手改:缓存了 node_modules 的地方,npm ci 前面必须有 cache-hit 判断。懒得加判断,就别缓存 node_modules,改成缓存包管理器自己的目录。

缓存什么路径key
包管理器缓存(推荐)~/.npm、~/.cache/yarn、pnpm storesetup-node 的 cache: 一行
node_modulesnode_moduleslockfile hash,且必须加 cache-hit 守卫
构建产物.next/cache、dist源码 hash,配合 restore-keys

我的立场

别再把「缓存 key 怎么写」当第一课。先看有没有打开,再谈写对。

缓存也不是免费的:仓库维度默认 10GB 额度(现在允许超,超了算钱),条目 7 天没人用就清;小项目依赖没几个,恢复缓存可能比直接装还慢。开之前先看一次 install 要多久,两秒以内就别折腾。

有人会说

「高星仓库不代表普通项目。」对,所以这组数字只能当底线读:连头部都这样,往下只会更多。

「自托管 runner 有本地缓存,不需要 actions/cache。」有道理,正则看不出来,733 这个数我只当上限用。

「一次 PR 装好几遍依赖才是大头。」我试过按触发器统计,结果对不上(380 个仓库里 307 个报零次 PR 安装),这批数据我不信,所以没写。

你的仓库是哪一类:一条缓存都没有、配了但从没命中、还是 key 对过?评论区报个数。