批量重命名 npm scope:锚定斜杠的 sed 和 grep 会系统性漏掉分隔符变体
给一个组件库做 npm scope 迁移,从 @old 改到 @newscope。一行 sed,全仓库替换,typecheck 绿、测试全过、包发布成功。grep 验收也干净——看着大功告成。
结果两周后,消费方照文档配 .npmrc 装包直接 404;CI 里一个覆盖率脚本悄悄返回 0,没有任何报错,只是数字不对。两个问题追溯到同一个根因:替换和验收都锚定了同一个分隔符,而 token 在其他地方以不同分隔符出现。
现象
替换命令长这样:
grep -rl '@old/' --include='*.ts' --include='*.tsx' --include='*.json' . \
| xargs sed -i '' 's#@old/#@newscope/#g'验收命令:
grep -rn '@old/' --include='*.ts' --include='*.tsx' --include='*.json' .
# → 0 结果,以为搞定了 ✅两个地方出了问题:
-
.npmrc里的 scope→registry 映射用的是冒号:@old:registry=https://npm.pkg.github.com。sed 搜的是@old/,完全不匹配,原封不动留下来。消费方按文档复制这段配置,pnpm add @newscope/...去错误的 registry 找包,404。 -
脚本里有一个正则字面量,斜杠被转义:
/from\s+["']@old\/ui["']/。sed 搜的是@old/,实际文本是@old\/ui(中间多了一个反斜杠),一样不匹配。rename 后这个正则再也匹配不到任何 import,覆盖率脚本静默返回 0,没有异常,只是数字从 199/231 变成了 0/231。
根因
sed s#@old/#...# 和 grep '@old/' 共享同一个正则模式 @old/(token + 尾部斜杠)。这个模式对以下形式的 token 全部不可见:
@old:registry=(冒号形式,.npmrc)@old\/ui(转义斜杠形式,正则字面量)@old(裸 token,文档/注释/yml)--include过滤掉的文件类型(.yml、.npmrc、dotfiles)
转义斜杠那个最危险:工具不报错,只是静默返回错误的数字,你不盯那个数字根本不会发现。
正解
替换用裸 token,验收也用裸 token,覆盖所有文件类型。
验收命令改成:
# 匹配 @old 后跟任何非字母数字字符(含行尾),排除新名字和 vendor 目录
grep -rnE '@old([:/]|\\\\|[^a-zA-Z0-9]|$)' . 2>/dev/null \
| grep -v node_modules | grep -v '\.git/' | grep -v '@newscope'替换阶段,除原有 *.ts/*.tsx/*.json 之外,显式追加这几类:
# .npmrc —— 冒号形式
sed -i '' 's#@old:registry=#@newscope:registry=#g' .npmrc
# 正则字面量 —— 转义斜杠形式
grep -rl '@old\\/' . | grep -v node_modules \
| xargs sed -i '' 's#@old\\/#@newscope\\/#g'
# CI / workflow yml
grep -rl '@old' --include='*.yml' --include='*.yaml' . \
| xargs sed -i '' 's#@old#@newscope#g'验收阶段只允许两类残留:(1)mock 数据里的 email/handle,(2)故意保留的历史注释。正则里的 @old、.npmrc 里的 @old:、yml 里的 @old 都必须被替换掉。
验收通过后,跑一次 pnpm install --frozen-lockfile 和依赖 import 扫描脚本,确认计数非零且符合预期。
一句话外卖
批量 rename 时,替换和验收用的 pattern 一样,就等于你看不见的部分也永远不会被发现——用裸 token + 所有文件类型做最终验收,才算真的干净。