my-code-style
| Install | |
|---|---|
npm install my-code-style |
|
| Latest Version: | 1.9.7 |
| License: | MIT |
| Last Updated: | Oct 20, 2026 |
| Links: | GitHub · npm |
my-code-style
Lint/Format/Git 配置工程化 npm 包。适用于 Vue 3 + TypeScript + uni-app 项目及纯 Node/TS 基础工程。
支持 ESLint v8 (.eslintrc.cjs) 和 ESLint v9 (Flat Config) 双格式,支持 SCSS 和 Less 样式检查。
项目文档
随 npm 包发布的只有本 README 与
CHANGELOG.md;下面其余文档在仓库里(docs/与TECHNICAL_DOC.md未列入files白名单),装包后在node_modules/my-code-style/README.md能看到的就是本文件。
- HTML 项目技术全景:下载或克隆仓库后,用浏览器直接打开,查看项目结构、配置架构、接入流程与维护注意事项;支持离线阅读和打印。
- Markdown 技术文档
- 使用说明书:使用后的优点、带来的福利与解决的问题、完整使用步骤与常见问题 FAQ
- 初始化链路说明:
pnpm add -D my-code-style+npx my-code-style-init逐步做了什么、生成哪些文件、常见坑
怎么使用
前置条件:Node ≥ 18、项目有合法 package.json、已 git init(husky 需要 git 仓库)。
# 1. 安装配置包
pnpm add -D my-code-style # npm: npm i -D my-code-style
# 2. 先预览,再初始化(init 会覆盖已存在的 .prettierrc.cjs / hooks 等)
npx my-code-style-init --dry-run
npx my-code-style-init # 需保留旧文件时加 --backup
# 3. 按 CLI 输出的缺失依赖清单安装 peerDependencies(会按包管理器生成命令,别漏 typescript 和 stylelint-order——后者是 recess-order 7 的 peer,Vue/SCSS 项目缺了它 stylelint 直接崩)
pnpm add -D my-code-style eslint@^9.0.0 typescript@^6.0.0 prettier@^3.0.0 ... # 以 CLI 实际输出为准
# 4. 初始化 hooks 并首次检查
pnpm prepare # npm: npm run prepare
pnpm lint
pnpm 10+ 用户注意:依赖里带构建脚本的包(
esbuild、unrs-resolver等)默认被拦截,安装后补一条pnpm approve-builds放行即可,不是初始化失败。想写进配置:pnpm 10.26 起用pnpm-workspace.yaml的allowBuilds(包名: true映射,取代已弃用的onlyBuiltDependencies列表),pnpm 11+ 已不再读取旧键(pnpm 12 会静默忽略,仍报ERR_PNPM_IGNORED_BUILDS)。
init 脚本会自动检测技术栈类型(uni-app / Vue 3 / Node 基础库)、ESLint 版本、CSS 预处理器和包管理器,按需生成配置文件,并在结尾列出尚未安装的 peerDependencies 与对应安装命令。
初始化后:lint-staged 会在 git commit 时自动修复暂存文件,commitlint 校验提交信息,pnpm release 生成版本与 CHANGELOG。全过程细节见 docs/init-flow.md,收益与代价评估见 docs/manual.md。
使用后的变化(速览)
| 维度 | 使用前 | 使用后 |
|---|---|---|
| 代码格式 | 各自编辑器配置 | 全仓库统一(4 空格 / 无分号 / 双引号 / 行宽 100) |
| 提交 | 靠自觉,问题留到评审 | pre-commit 自动修复,修不好的挡下提交 |
| 提交信息 | 自由文本 | conventional commits 强校验 + pnpm cz 引导 |
| 版本与日志 | 手工改版本、手工写 CHANGELOG | pnpm release 自动递增并生成 CHANGELOG |
| 样式代码 | 基本无人检查 | stylelint 覆盖 SCSS/Less/Vue 内嵌样式 |
| 规则同步 | 每个项目各抄一份 | 升级一个依赖版本,全项目生效 |
用 pnpm cz 提交(交互式)
czg 由暂存区驱动——没有暂存文件会直接报 No files added to staging! 并退出,所以顺序是先 git add 再 pnpm cz:
git add -A
pnpm cz # = czg;npm 项目:npm run cz
六步交互,除类型外每一步都能直接回车跳过:
| 步骤 | 界面 | 说明 |
|---|---|---|
| 1 | 选择类型 | 15 种(见下表),支持输入关键字过滤 |
| 2 | 选择 scope | 候选来自 src/ 下的目录(自动转单数:components → component);默认值按暂存文件推断,改了 src/api/ 默认就是 api;也可选「自定义」手填或「不填」 |
| 3 | 一句话描述 | 右侧 [N more chars allowed] 是还能写多少字符,已扣除 类型(scope): 前缀——上限就是 commitlint 的 header-max-length(本配置 108) |
| 4 | 详细描述 | 可选,写多行时用管道符分隔 |
| 5 | BREAKING CHANGE | 可选 |
| 6 | 关联 ISSUE | 可选(如 #31, #34),最后预览并确认 Y/n |
| 类型 | 含义 | 类型 | 含义 |
|---|---|---|---|
feat |
新功能 | build |
影响构建系统或外部依赖的变更 |
fix |
修复缺陷 | ci |
CI 配置与脚本变更 |
docs |
仅文档变更 | chore |
其他不涉及 src 与测试的杂项 |
style |
不影响代码含义的格式调整 | revert |
回滚此前的提交 |
refactor |
既非修复缺陷也非新增功能的代码调整 | wip |
开发中(临时提交) |
perf |
性能优化 | workflow |
工作流改进 |
test |
补测试或修正既有测试 | types |
类型定义文件变更 |
release |
发布相关(版本号 / 变更日志) |
省键盘的写法:
| 写法 | 作用 |
|---|---|
pnpm cz :f |
用预置别名直接提交:f=docs: fix typos、r=docs: update README、s=style: update code format、b=build: bump dependencies、c=chore: update config |
pnpm cz -r |
重放上一条提交信息(改完文件再提交同样的说明) |
pnpm cz --help |
其他模式:emoji / break / ai / gpg / checkbox(默认都不开启) |
git commit -m "feat(api): 说明" |
不用交互也行,但冒号后必须有空格,否则报 type may not be empty + subject may not be empty |
提交时 commit-msg 钩子会用同一份 commitlint 配置复核:type 必须是上表 15 种之一、描述不能为空、标题 ≤ 108 字符;Merge branch … / Revert … 这类消息自动忽略。想换提示语言或措辞,见下方常见问题。
生成的文件
| 文件 | 说明 |
|---|---|
.eslintrc.cjs |
ESLint 配置(v8 项目,TypeScript + Vue 3 / uni-app) |
eslint.config.mjs |
ESLint Flat Config(v9 项目,默认优先推荐) |
.prettierrc.cjs |
Prettier 格式化配置 |
.prettierignore |
Prettier 忽略文件(跳过二进制文件与产物) |
.stylelintrc.cjs |
Stylelint CSS/SCSS/Less 配置(非样式项目自动跳过) |
.commitlintrc.cjs |
Git commit 信息规范(支持智能 scope 猜测) |
.versionrc.js / .versionrc.cjs |
自动版本号 + CHANGELOG(ESM 生成 .versionrc.cjs,CJS 生成 .versionrc.js) |
.editorconfig |
编辑器基础配置 |
.gitattributes |
Git 文件类型处理(统一 EOL=LF,标记二进制文件) |
.gitignore |
Git 忽略文件(自动忽略 node_modules/、dist/、*.tgz 等) |
.husky/commit-msg |
Git hook: commitlint(Husky 9 格式) |
.husky/pre-commit |
Git hook: lint-staged(Husky 9 格式) |
手动使用(不通过 init)
ESLint v8 (.eslintrc.cjs)
// .eslintrc.cjs
module.exports = require("my-code-style/eslint/uniapp")
// 或按需引入中间层
const base = require("my-code-style/eslint") // 基础 TypeScript
const vue3 = require("my-code-style/eslint/vue3") // + Vue 3 规则
ESLint v9 (Flat Config / eslint.config.mjs)
// eslint.config.mjs
import uniappConfig from "my-code-style/eslint/flat/uniapp"
// 或按需引入中间层
import baseConfig from "my-code-style/eslint/flat"
import vue3Config from "my-code-style/eslint/flat/vue3"
其他配置
// .prettierrc.cjs
module.exports = require("my-code-style/prettier")
// .stylelintrc.cjs
module.exports = require("my-code-style/stylelint")
// .commitlintrc.cjs
const base = require("my-code-style/commitlint")
const { generateScopes, guessCurrentScope } = require("my-code-style/commitlint/scopes")
const scopeComplete = guessCurrentScope()
module.exports = {
...base,
prompt: {
...base.prompt,
customScopesAlign: !scopeComplete ? "top" : "bottom",
defaultScope: scopeComplete,
scopes: [...generateScopes("src"), "mock"],
},
}
// .versionrc.js
module.exports = require("my-code-style/versionrc")
配置覆盖
// .eslintrc.cjs
const base = require("my-code-style/eslint/uniapp")
module.exports = {
...base,
rules: {
...base.rules,
"no-console": "warn",
},
}
导出入口
| 入口 | 说明 |
|---|---|
my-code-style |
默认:ESLint v8 uni-app 配置 |
my-code-style/eslint |
基础 ESLint v8(TypeScript) |
my-code-style/eslint/vue3 |
+ Vue 3 规则 |
my-code-style/eslint/uniapp |
+ uni-app globals |
my-code-style/eslint/flat |
ESLint v9 Flat Config 基础 |
my-code-style/eslint/flat/vue3 |
+ Vue 3 规则 |
my-code-style/eslint/flat/uniapp |
+ uni-app globals |
my-code-style/prettier |
Prettier 配置 |
my-code-style/stylelint |
Stylelint 配置(SCSS / 默认) |
my-code-style/stylelint/less |
Less 专用 Stylelint 配置 |
my-code-style/stylelint/less-override |
Less 专用覆写配置 |
my-code-style/commitlint |
Commitlint 基础配置 |
my-code-style/commitlint/scopes |
动态 scope 工具函数 |
my-code-style/versionrc |
commit-and-tag-version 配置 |
my-code-style/package.json |
包元数据(工具读取版本用) |
CLI 初始化参数
npx my-code-style-init [--dry-run] [--backup] [--merge] [--version|-v] [--help|-h]
--dry-run:试运行模式,仅输出将执行的变更,不修改任何文件。--backup:在覆盖已有配置文件之前,将其备份到.my-code-style-backup/目录。--merge(别名--no-overwrite):合并模式——已存在的配置与 hooks 一律保留,只补缺失文件;package.json只新增缺失的 scripts,已有lint-staged分组原样保留。适合升级场景先合并、再人工比对。- 初始化结束时自动自检 hooks(存在 / 非空 / 可执行位 /
lint-staged、commitlint是否可解析),只提示不阻断——把"hook 存在但不生效"消灭在当场。 - 具备事务回滚保护:若在写入过程中遭遇文件权限或磁盘异常,自动回滚所有修改,恢复工作区原始状态。
lint 覆盖范围须知
| 入口 | 覆盖 | 典型用途 |
|---|---|---|
pnpm lint |
ESLint(JS/TS/Vue 脚本层) | 本地与 CI 基础门禁 |
pnpm lint:style |
Stylelint 全仓(Vue/HTML 内嵌 + css/scss/less) | CI 补齐样式检查 |
pnpm lint:all |
ESLint + Stylelint 全仓 | CI 一条命令全覆盖 |
| lint-staged(pre-commit) | 仅本次改动的文件(三件套自动修复) | 提交门禁 |
lint 只跑 ESLint;Stylelint 默认只在 pre-commit 对本次改动的文件执行——CI 只跑 pnpm lint 时 CSS/SCSS/Vue 内嵌样式不会被检查。有 CSS 预处理器的项目,init 会生成 lint:style 与 lint:all。
显式扩展名 import 的兼容行为
import-x/extensions 默认要求相对导入省略 .ts/.tsx 扩展名。Vite 等 bundler resolution 项目常写 import x from "@/types/Foo.ts"(合法但与该规则冲突)。
init 会自动检测:命中显式扩展名 import 时,生成的 eslint.config.mjs 注入兼容段——该规则降级为 warn,pnpm lint 退出码不受影响:
- 带
.ts与不带.ts都可以写,两种风格共存不报错; - 兼容段同样覆盖 Vue SFC(
.vue/.nvue):<script setup>里的显式扩展名 import 不会绕过兼容段报错(BUG-038); - 不一致的写法仍会出 warning,作为渐进统一的风格信号;
- 团队统一后想恢复严格检查(error 级、只允许省略),删除
eslint.config.mjs里的兼容段即可。
项目体检:my-code-style-doctor
npx my-code-style-doctor
对既有项目随时复验(init 的依赖体检只在新装/重装时打印一次):
- peer 依赖:按项目类型裁剪核对(与 init 同口径——ESLint 8 才查 classic import、不涉样式的项目不查 stylelint 族)。stylelint 变体以项目实际的配置为准(
.stylelintrc.cjsextends 的是stylelint/less就只查 Less 线的 peer,不会给纯 Less 项目索要postcss-scss),没有配置文件时才回落到依赖推断;缺失/版本越界逐项报红,并输出可整段粘贴的补齐命令; - husky hooks:存在 / 非空 / 可执行位 / 引用命令可解析 /
core.hooksPath指向; - lockfile:是否被
.gitignore排除(排除 = 不同机器版本组合不可复现); - 配置文件:四件套是否就位。
退出码 0 健康 / 1 有问题,可直接当 CI 门禁用。
特性
- 双引号:
quotes: ["error", "double", { avoidEscape: true, allowTemplateLiterals: true }](avoidEscape必须为true,否则与 Prettier 的转义策略冲突,--fix无法消除报错) - 4 空格缩进:
tabWidth: 4(由 Prettier 全权统一格式化) - 无分号:
semi: false(.nvue文件除外) - 行宽 100:
printWidth: 100 - JSX / TSX 全面支持:Flat Config 与传统配置均支持
.jsx和.tsx语法解析、Prettier 格式化、规则修复与目录扫描 - 多工程形态适配:智能识别 uni-app / Vue 3 / 纯 Node-TS 基础工程,无样式的纯代码项目自动跳过 Stylelint
- 双 ESLint 格式:ESLint v9 (Flat Config,未配置项目默认优先) 与 v8 (.eslintrc.cjs) 全自动检测,强化支持 npm 别名、workspace 协议与复杂 semver 范围
- 现代化 Git Hooks:采用 Husky 9 原生极简 hook + lint-staged 互斥文件分组,串行运行、消除并发写入缓存冲突
- Stylelint SCSS、Less 及混合工程双支持:支持纯 SCSS、纯 Less 以及两者共存的混合工程,放行
@变量、小程序rpx/page、深度选择器::v-deep及现代 CSS 伪类,组件内嵌多预处理器样式全面检查 - 严格暂存区 Scope 猜测:
guessCurrentScope()严格只依据 Git 暂存区推断 scope,支持多目录频次投票、重命名、物理删除、中文路径与空格路径 - ESM 兼容的自动版本发布:支持 CommonJS(
.versionrc.js)与 ESM(.versionrc.cjs)工程无缝对接 commit-and-tag-version(standard-version 的社区维护分支),一键版本递增与 CHANGELOG 生成。peer 范围有意钉在^12:13.x 的 writer 存在回归且要求搭配新 preset 组合,稳定性待观察后再放宽(旧项目遗留的release: standard-version会在 init 时被自动纠偏)
常见问题(使用中遇到的问题)
| 现象 | 原因 | 处理 |
|---|---|---|
只装了 my-code-style,pnpm lint 报找不到 eslint |
peerDependencies 全部是 optional,不会自动安装 | 按 CLI 结尾输出的清单装全,别漏 typescript(@typescript-eslint/parser / typescript-eslint 的必需 peer) |
stylelint 报 Could not find plugin "stylelint-order" |
recess-order 7 把 stylelint-order 声明为 peer(非依赖),pnpm 10+ 严格模式 / npm / yarn 不自动装 | 显式安装 stylelint-order(对齐 peer 范围 ^6 || ^7 || ^8;Vue/SCSS 项目必装) |
pnpm prepare 输出 .git can't be found,hooks 不生效 |
husky 必须在 git 仓库内执行 | 先 git init、再 pnpm prepare;验证 git config core.hooksPath 输出 .husky/_ |
安装依赖报 ERR_PNPM_IGNORED_BUILDS(如 unrs-resolver) |
pnpm 10+ 默认拦截依赖的构建脚本 | 执行 pnpm approve-builds 后重新安装,不是初始化失败;要写进配置就用 pnpm-workspace.yaml 的 allowBuilds(pnpm 10.26+,取代已弃用的 onlyBuiltDependencies;pnpm 11+ 不再读旧键) |
eslint 声明 >=8.0.0 却生成了 Flat Config |
无上界范围 npm 实际会装 9,CLI 按 9 生成 | 确定留在 8 就把范围收紧到 <9(如 ~8.57.0)后重跑 init |
首次接入存量/脚手架项目,lint:style 集中报几十个属性顺序错 |
stylelint-order(recess-order)按既有排序规约检查,脚手架模板(如 Vite 的 style.css)不满足 |
一次性 stylelint "**/*.{vue,nvue,html,css,scss,less}" --fix 自动归位后再提交;此后保持绿色。属存量风格收敛,非本配置引入的错误 |
重复 init 后,改过的 .prettierrc.cjs / hooks 被覆盖 |
除 ESLint 入口与已存在的 .gitignore 外,目标文件一律覆盖 |
先用 --dry-run 看清单、用 --backup 留档(备份含 package.json,目录会自动写入 .gitignore) |
自定义 lint-staged 分组消失 |
lint-staged 为整体替换,避免残留失效分组 |
从 .my-code-style-backup/package.json 恢复后手动合并 |
| 提交时 lint 报错、提交失败 | 修不了的错误(语法错误、非法 CSS 属性)会阻止提交 | 按提示修复;紧急情况可用 git commit --no-verify,但请在 CI 用 pnpm lint 兜底 |
提交信息被拒(type may not be empty / subject may not be empty) |
提交信息需符合 conventional commits,类型(范围): 描述 的冒号后必须有空格 |
先看冒号后面有没有空格(fix:(修复)… 会被判成 type 与 subject 双空);用 pnpm cz 交互式生成;header 上限 108 字符 |
pnpm cz 报 No files added to staging! |
czg 由暂存区驱动:没有暂存文件就不启动(--all / -a 也不绕过) |
先 git add -A 再 pnpm cz;scope 候选与默认值也是按暂存区推断的 |
想让 pnpm cz 的提示换成英文/自己的措辞 |
提示语来自 prompt.messages,本包默认中文 |
在项目的 .commitlintrc.cjs 里覆盖 prompt.messages(标题)与 prompt.types(类型描述);scope 列表里的「自定义 / 不填」对应 customScopesAlias / emptyScopesAlias |
老项目已有 .eslintrc.*,init 只读退出 |
ESLint 版本与配置格式冲突时 CLI 拒绝写入 | 先确认/迁移版本,再重跑 init |
pnpm release 之后如何发布 |
release 只生成版本、CHANGELOG、提交与 tag |
另行执行 npm publish |
装完看到一片 unmet peer 警告,要不要管 |
npm 遇到不满足的 peer 会直接 ERESOLVE 拒装,pnpm 只打印 WARN 就装完 | 先看警告来自谁:unmet peer … from my-code-style 说明版本声明没跟上,先升级本包;来自别的包(如 postcss-html@1.8.1 却要 ^2)按提示对齐。重跑 init 会主动体检(范围精确到 minor,例如 vue-eslint-parser 10.2.0 不满足 ^10.3.0)并打印对齐命令 |
stylelint 该用 16 还是 17 |
两条线都支持:stylelint ^16.24.0 || ^17.0.0 |
16 线(stylelint-config-recommended@17)兼容面最广;17 线要 stylelint 17 + config-recommended@18 + recommended-scss@17 + stylelint-order@7/8,且 Node ≥ 22.12。两条线都有真实运行回归(集成套件跑 16 线,Stylelint 17 套件跑 17 线) |
完整 FAQ(含更多场景与解释)见 docs/manual.md。
测试套件
npm run test:all # 全量运行 209 项:用例明细 + 分类统计 + 最慢文件定位
npm run diagnose # 换机器后先跑它:进程 / git / 文件系统 / hooks 各占多少
npm run sweep # 给本机找最佳文件级并发(扫 integration 套件)
npm test # 只跑基础 CLI 与配置矩阵(104 项)
npm run test:integration:setup # 安装现代化隔离依赖运行环境
npm run test:integration # Flat Config、真实 Husky 及提交链路测试(67 项)
npm run test:legacy:setup # 安装 ESLint 8 隔离运行环境
npm run test:legacy # ESLint 8.57.0 兼容性回归测试(28 项)
npm run test:stylelint17:setup # 安装 Stylelint 17 隔离运行环境
npm run test:stylelint17 # Stylelint 17 生态兼容性回归(10 项)
npm run test:all 由 scripts/test-all.cjs 驱动,输出套件 → 用例 → 汇总三层:
每个大类下面列出它的每条用例(✓/✗/○ + 耗时),最后给出分类统计与总条数。
my-code-style 测试套件
▶ [1/4] 基础 CLI 与配置矩阵 (共 104 项,5 文件 · 3 文件并发(自动:2 核 / 内存 4G))
✓ 运行器 TAP 解析:统计通过与失败、SKIP 与每用例耗时 · 7ms
✓ cz 交互提示为中文,且可选类型与 type-enum 一一对应 · 1ms
✓ peer 范围覆盖我们声明支持的上游大版本(校准防回退,BUG-027) · 0ms
✓ 一键安装命令对含 || 的版本范围加引号,可整段粘贴执行 · 40ms
✓ 初始化结尾指引把 cz 用法说清楚(先 git add + 指到 README 手册) · 80ms
…
✓ 通过 104 · 4.2s · 最慢文件 init-matrix.test.cjs 4.0s
▶ [2/4] 现代工具链与提交链 (共 67 项,24 文件 · 3 文件并发(自动:2 核 / 内存 4G))
✓ 完整提交链:JS/TS/Vue/nvue/CSS/less 自动修复及二次复检 · 10.4s
✓ 发布防呆:HEAD 已有 v* tag 时拦截(此时 release 工具会静默抬版并写出空 CHANGELOG) · 609ms
…
✓ 通过 67 · 43.8s · 最慢文件 commit-chain-both.test.cjs 11.9s
▶ [3/4] ESLint 8 兼容性 (共 28 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
✓ 独立运行 ESLint 8 支持下限而非 ESLint 9 · 1ms
✓ ESLint 8 base:实际 CLI 加载生成配置并接受正常文件 · 1.3s
…
✓ 通过 28 · 20.2s
▶ [4/4] Stylelint 17 兼容性 (共 10 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
✓ 隔离环境装的是 stylelint 17(不是 16) · 2ms
✓ 生成的 .stylelintrc.cjs 在 stylelint 17 下可加载并放过正常 SCSS · 700ms
…
✓ 通过 10 · 8.8s
──────────────────────────────────────────────────────
套件 通过 失败 跳过 用时
──────────────────────────────────────────────────────
基础 CLI 与配置矩阵 104 0 - 4.2s
现代工具链与提交链 67 0 - 43.8s
ESLint 8 兼容性 28 0 - 20.2s
Stylelint 17 兼容性 10 0 - 8.8s
──────────────────────────────────────────────────────
合计 209 0 - 77.1s
──────────────────────────────────────────────────────
⏱ 最慢文件:eslint8.test.cjs 18.0s · commit-chain-less.test.cjs 11.4s · commit-chain-both.test.cjs 11.3s
✅ 全部通过:209/209 项,用时 77.1s
终端里每个套件下方还有一条实时进度条(██████░░░░ 38/67 失败 0 4.0s)。附加参数:
| 参数 | 作用 |
|---|---|
-- --quiet |
只看套件结论与汇总表,不列用例明细(长输出时用) |
-- --parallel |
强制四个套件同时跑(核数 ≥ 8 时默认已自动开启) |
-- --serial |
强制串行(CI 形态,输出顺序最稳定) |
-- --jobs=2 |
配合并行限制同时运行的套件数,其余排队(面板显示"等待中") |
-- --suite=base,legacy |
只跑指定套件(base / integration / legacy,逗号分隔) |
-- --concurrency=4 |
覆盖套件内文件级并发(默认按核数与内存自动算,封顶 8) |
-- --verbose |
展开每个套件的原始 TAP 输出(排查单条用例) |
-- --profile |
额外列出每个测试文件的耗时(定位集成套件瓶颈时用) |
-- --no-count |
跳过预统计,进度条退化为"已完成 N 项" |
-- --progress |
在非 TTY(CI 日志)中也强制刷新进度行 |
并行跑更快
测试用例之间不用同一份目录:基础套件只用临时目录,集成套件用 demo-test/.runtime,
ESLint 8 套件用 demo-test/.runtime-legacy,所以两层并行都是安全的。核数 ≥ 8 的机器
会自动开启套件级并行(想固定成 CI 那种串行输出用 --serial):
想给本机定一个更合适的并发数,跑 npm run sweep:它会把 integration 套件在 auto/1/2/4/6/8/12/16/24 各跑一次,直接给出最快的那一档(并发越高越慢就说明瓶颈在进程启动而非 CPU)。
换了机器发现某个套件特别慢?先跑 npm run diagnose:它会把「进程启动 / npx 开销 / git / 文件系统 / 一次真实提交的 hooks」逐项列出来,直接指出时间花在哪一层。
npm run test:all # 16 核机器:自动并行,墙钟 ≈ 最慢的那套
npm run test:all -- --serial # 强制串行
npm run test:all -- --parallel # 低核机器上强制并行
npm run test:all -- --profile # 看每个测试文件耗时,定位瓶颈
两层并行的实现:
- 文件级:集成套件按"一条提交链一个文件"拆成 23 个测试文件,由运行器自己按文件
调度(每个文件一个
node --test进程),不依赖 Node 的--test默认并发值—— 同一个文件内的用例仍然串行(真实 git 仓库不能并行改),文件之间才是并发的。 - 套件级:四个套件再并行,互不共享目录(基础只碰系统临时目录,集成用
demo-test/.runtime,ESLint 8 用.runtime-legacy,Stylelint 17 用.runtime-sl17)。
因为每个文件里跑的都是真实 git commit(单次 0.5–3.9s),墙钟由最慢的那个文件
决定,所以文件尽量拆到"一个文件一次提交"。每个套件跑完会直接告诉你最慢文件是谁,
汇总区再给一次全局 ⏱ 最慢文件:
| integration 子文件(23 个) | 单文件耗时(2 核、串行) | 内容 |
|---|---|---|
commit-chain-{scss,less,both} |
2.7–3.3s | 三种预处理器的完整提交链(各 1 次提交) |
empty-commit-guard |
2.7s | 空提交守卫(重复暂存不得产生漂移提交) |
git-mv-rm |
3.2s | git mv / git rm 走 lint-staged |
embedded-style-{vue,nvue,mixed-less} |
1.9–2.0s | Vue/nvue 内嵌样式错误拦截 |
git-edge-{first-commit,stash} |
2.1s / 1.7s | 首次提交、用户 stash 保留 |
partial-staging{,-unstaged,-invalid-message,-conflict} |
0.5–1.7s | 部分暂存与冲突恢复 |
error-recovery-{css,scss,less,js,ts,vue,nvue} |
0.5–1.4s | 不可修复错误的回滚 |
lint |
3.9s | 32 条规则与格式化行为(无提交链) |
release |
0.8s | commit-and-tag-version |
两层并发都要控制规模,所以默认并发不交给 Node 猜,而是运行器自己算——核数 × 1.5
(至少 核数 - 1),再乘内存上限,最后封顶 8(16 核机器实测:4/6/8/12/16/24 并发对应
20.3/18.7/19.0/19.2/18.9/19.1s,6–8 之后加并发只会让每个文件变慢;要更多用
--concurrency 覆盖):实测一条真实提交链(git + npx + eslint +
prettier)峰值 RSS 约 0.8GB,运行器留 30% 内存给系统,避免并发过多的小内存机器被
并发 git/npx 撑到 OOM(进程被 SIGKILL);文件数多于并发数时,用例少的文件(多半是同
一条重链)优先开跑,避免长任务被排进最后一波、墙钟再多乘一倍。
实测(2 核沙箱,沙箱内存 4G,所以自动并发是 3)——下表是引入 hook 优化那次的对照运行(当时 168 项):
| 运行方式 | 墙钟时间 |
|---|---|
| 串行套件 + 文件级并发 1(CI 旧形态) | 62.4s(hook 改造前同配置 70.3s) |
| 串行套件 + 自动并发 3(默认) | 47.2s(hook 改造前同配置 51.8s) |
--parallel 四套件 + 自动并发 3 |
43.0s(hook 改造前同配置 46.6s) |
node --test demo-test/integration/*.test.cjs(Node 自己调度,等同旧命令) |
43.7s(只看集成套件那一段) |
npm run test:integration(运行器调度,自动并发 3) |
30.6s(只看集成套件那一段) |
| 串行套件 + 文件级并发 23(全部一波) | ✗ 3.8G 内存沙箱被 OOM 压垮(19 项 SIGKILL) |
同机复测(200 项,同一台 2 核沙箱连续跑;绝对秒数随机器负载波动,相对关系稳定):默认自动并发 3 = 77.1s、npm run test:integration = 43.8s(本轮实跑;串行与 parallel 口径为早前基线实测,相对关系稳定)。
(2 核沙箱里并行收益被 CPU 争抢吃掉大半,--concurrency=1 时单个文件只要 0.7–4.2s;
16 核机器上集成套件那段 ≈ 最慢子文件,即"一次真实提交"的量级。)
生成的 hook 优先直连本地 bin(husky 已把 node_modules/.bin 放进 PATH),
找不到时才退回 npx --no-install——每次提交因此少起两个 npm CLI(实测每个 hook
省 130–185ms),提交链从 3.3s 降到 2.8s。集成 fixture 同样用环境变量传 git 身份
(GIT_AUTHOR_* / GIT_CONFIG_COUNT)代替 4 次 git config。
demo-test/integration/_runtime.cjs 用「指纹 + 目录锁」保证并发启动时只同步一次
隔离环境源码,不会读到写了一半的文件;prettier / eslint 以 getter 惰性加载,
只做提交链的 8 个文件不再为它们付启动开销。
未安装隔离运行环境时,对应套件会明确标记"未运行"并给出 setup 命令,退出码为 1(不会假装全绿)。
发布(维护者)
一条命令发布(升版本 → 更新 CHANGELOG → 提交 → 打 tag → 推送,推 tag 即触发发布工作流):
npm run release:push
也可以拆成两步:npm run release 只做到打 tag,之后用 git push --follow-tags origin <分支> 手动推送。
⚠️ 别把两条命令连起来跑:
release已经把 tag 打在 HEAD 上了,紧接着跑release:push,发布工具 不会报错,而是把版本号再抬一级、写出一个没有条目的空 CHANGELOG 并打出第二个 tag —— 等于多发一个空版本(npm 上不可撤销)。两条命令的第一步都是
scripts/release-guard.cjs发布前防呆,命中下面两种情况会直接中止并打印该执行的命令:HEAD 上已经有v*tag(说明这一版已生成,只差推送)、已跟踪文件还有未提交改动(会被卷进版本提交)。未跟踪文件不拦截。
.github/workflows/publish.yml 只接受 v* tag 触发(workflow_dispatch 会先校验 ref,分支上手动触发直接失败),并且在测试全绿 + npm pack --dry-run 通过后才发布;发布步骤失败即失败,不做静默降级。
发布后的 provenance 自动核验(npm 页面上的绿勾)
绿勾 = 该版本带 provenance 证明。工作流用两层核验保证它不会悄悄消失:
- 发布当场自证(不等 registry):npm 真正生成 provenance 时必定打印
Signed provenance statement with source and build information from GitHub Actions, 工作流把这行当硬断言——没有就当场失败标红。1.7.1 正是漏了--provenance,日志里没有这行、 流程却全绿,只有 npm 页面少一个勾。 - registry 侧回查:轮询
https://registry.npmjs.org/-/npm/v1/attestations/my-code-style@<version>, 前 6 次每 10 秒、之后每 30 秒,共约 5.5 分钟。这里的耐心是必需的:1.7.2 的 attestation 在发布后约 2 分钟才可查(透明日志签名时间 06:47:57Z,而 publish 结束于 06:45:49Z), 30 秒的预算会把一次成功的发布误判成失败。
等待参数可用环境变量覆盖(MAX_ATTEMPTS / FAST_ATTEMPTS / RETRY_FAST / RETRY_SLOW),便于本地复现这套轮询逻辑。
发布凭据(推荐切到 Trusted Publishing)
工作流默认按 npm Trusted Publishing(OIDC)→ NPM_TOKEN 的顺序取凭据:NPM_TOKEN secret 存在时用 token,删掉后无需改代码自动改走 OIDC。切换步骤:
- 打开
https://www.npmjs.com/package/my-code-style/access→ Trusted Publisher → GitHub Actions,填:- Organization or user:
myvn - Repository:
codeStyle - Workflow filename:
publish.yml(大小写与.yml后缀都要精确匹配) - Environment:
npm-publish(与本仓库 Environments 里的名字一致)
- Organization or user:
- 仓库 Settings → Environments →
npm-publish添加 required reviewers(发布前人工批准;不配则该环境无保护)。 - 在 GitHub 仓库 Settings → Secrets 删除
NPM_TOKEN,并到 npm 账号下吊销对应 token。
OIDC 要求发布环境是 GitHub-hosted runner + id-token: write + npm CLI ≥ 11.5.1(工作流用 Node 24 自带),因此 setup-node 刻意不写 registry-url——那会生成带 _authToken 的 .npmrc,让 npm 以为认证已就绪而跳过 OIDC。
工作流里的 action 固定在大版本号上(actions/checkout@v7 / actions/setup-node@v7 / softprops/action-gh-release@v3),它们都已迁到 Node 24 运行时——GitHub 已弃用 Actions 的 Node 20 运行时,停留在 @v4 会在每次运行时报 Node.js 20 is deprecated 告警。自托管 runner 需 ≥ v2.327.1。
License
MIT
Related Packages
Opinionated ESLint config for consistent, clean, auto-sorted code in TRES Vue3 a...
Version History
| Version | Released | License |
|---|---|---|
| 1.9.7 | MIT | |
| 1.9.6 | MIT | |
| 1.9.5 | MIT | |
| 1.9.4 | MIT | |
| 1.9.3 | MIT | |
| 1.9.2 | MIT | |
| 1.9.1 | MIT | |
| 1.9.0 | MIT | |
| 1.8.6 | MIT | |
| 1.8.5 | MIT | |
| 1.8.4 | MIT | |
| 1.8.3 | MIT | |
| 1.8.2 | MIT | |
| 1.8.1 | MIT | |
| 1.8.0 | MIT | |
| 1.7.2 | MIT | |
| 1.7.1 | MIT | |
| 1.7.0 | MIT | |
| 1.6.1 | MIT | |
| 1.6.0 | MIT | |
| 1.5.0 | MIT | |
| 1.4.0 | MIT | |
| 1.2.0 | MIT | |
| 1.0.0 | MIT |