my-code-style 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +56 -22
  3. package/bin/init +149 -16
  4. package/package.json +11 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  ## 变更日志
2
2
 
3
+ ### [1.7.1](https://github.com/myvn/codeStyle/compare/v1.7.0...v1.7.1) (2026-09-20)
4
+
5
+ ## [1.7.0](https://github.com/myvn/codeStyle/compare/v1.6.1...v1.7.0) (2026-09-20)
6
+
7
+ ### 🐛 Bug Fixes | Bug 修复
8
+
9
+ - **deps:** stylelint 生态 peer 放宽到 16/17 两条线,并补 Stylelint 17 运行时回归 ([a230edc](https://github.com/myvn/codeStyle/commit/a230edcae73d2b935977567b8f082aaaa74dc938))
10
+
11
+ ### ✨ Features | 新功能
12
+
13
+ - **init:** 依赖版本体检 —— 已装但版本不匹配、上游配置 peer 冲突、parser 错位 ([2820834](https://github.com/myvn/codeStyle/commit/2820834bc3062900724132f7b29c2581eace13e8))
14
+ - **init:** 版本体检精确到 minor —— 只比主版本会漏掉 10.2.0 vs ^10.3.0 ([d13629c](https://github.com/myvn/codeStyle/commit/d13629c947575109ed3319854ab902e5caada145))
15
+
3
16
  ### [1.6.1](https://github.com/myvn/codeStyle/compare/v1.6.0...v1.6.1) (2026-09-18)
4
17
 
5
18
  ## [1.6.0](https://github.com/myvn/codeStyle/compare/v1.4.0...v1.6.0) (2026-09-18)
package/README.md CHANGED
@@ -185,13 +185,15 @@ npx my-code-style-init [--dry-run] [--backup] [--version|-v] [--help|-h]
185
185
  | 提交信息被拒(`type may not be empty` / `subject may not be empty`) | 提交信息需符合 conventional commits | 用 `pnpm cz` 交互式生成;header 上限 108 字符 |
186
186
  | 老项目已有 `.eslintrc.*`,init 只读退出 | ESLint 版本与配置格式冲突时 CLI 拒绝写入 | 先确认/迁移版本,再重跑 init |
187
187
  | `pnpm release` 之后如何发布 | `release` 只生成版本、CHANGELOG、提交与 tag | 另行执行 `npm publish` |
188
+ | 装完看到一片 `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`)并打印对齐命令 |
189
+ | `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 线) |
188
190
 
189
191
  完整 FAQ(含更多场景与解释)见 [docs/manual.md](docs/manual.md#五使用中遇到的问题faq)。
190
192
 
191
193
  ## 测试套件
192
194
 
193
195
  ```bash
194
- npm run test:all # 全量运行 152 项:用例明细 + 分类统计 + 最慢文件定位
196
+ npm run test:all # 全量运行 168 项:用例明细 + 分类统计 + 最慢文件定位
195
197
  npm run diagnose # 换机器后先跑它:进程 / git / 文件系统 / hooks 各占多少
196
198
  npm run sweep # 给本机找最佳文件级并发(扫 integration 套件)
197
199
  npm test # 只跑基础 CLI 与配置矩阵(70 项)
@@ -199,6 +201,8 @@ npm run test:integration:setup # 安装现代化隔离依赖运行环境
199
201
  npm run test:integration # Flat Config、真实 Husky 及提交链路测试(55 项)
200
202
  npm run test:legacy:setup # 安装 ESLint 8 隔离运行环境
201
203
  npm run test:legacy # ESLint 8.57.0 兼容性回归测试(27 项)
204
+ npm run test:stylelint17:setup # 安装 Stylelint 17 隔离运行环境
205
+ npm run test:stylelint17 # Stylelint 17 生态兼容性回归(8 项)
202
206
  ```
203
207
 
204
208
  `npm run test:all` 由 `scripts/test-all.cjs` 驱动,输出**套件 → 用例 → 汇总**三层:
@@ -207,38 +211,44 @@ npm run test:legacy # ESLint 8.57.0 兼容性回归测试(27 项)
207
211
  ```text
208
212
  my-code-style 测试套件
209
213
 
210
- ▶ [1/3] 基础 CLI 与配置矩阵 (共 70 项,5 文件 · 3 文件并发(自动:2 核 / 内存 4G))
214
+ ▶ [1/4] 基础 CLI 与配置矩阵 (共 78 项,5 文件 · 3 文件并发(自动:2 核 / 内存 4G))
211
215
  ✓ 运行器 TAP 解析:统计通过与失败、SKIP 与每用例耗时 · 2ms
212
- ✓ 所有公共导出目标存在且可通过包名解析 · 11ms
213
- ✓ Prettier 默认值和 JSON/YAML/nvue 文件例外 · 1ms
216
+ ✓ 依赖版本体检:两位数主版本(stylelint 17)不再被静默跳过 · 41ms
217
+ ✓ 所有公共导出目标存在且可通过包名解析 · 3ms
214
218
  …
215
- ✓ 通过 70 · 2.1s · 最慢文件 init-matrix.test.cjs 1.9s
219
+ ✓ 通过 78 · 3.2s · 最慢文件 init-matrix.test.cjs 3.0s
216
220
 
217
- ▶ [2/3] 现代工具链与提交链 (共 55 项,23 文件 · 3 文件并发(自动:2 核 / 内存 4G))
218
- ✓ 完整提交链:JS/TS/Vue/nvue/CSS/scss 自动修复及二次复检 · 8.0s
219
- ✓ 完整提交链拦截 nvue 内嵌样式错误(不是仅检查脚本) · 3.6s
221
+ ▶ [2/4] 现代工具链与提交链 (共 55 项,23 文件 · 3 文件并发(自动:2 核 / 内存 4G))
222
+ ✓ 完整提交链:JS/TS/Vue/nvue/CSS/scss 自动修复及二次复检 · 10.7s
220
223
  ✓ Flat Config 启用 eslint:recommended 核心规则:no-debugger · 45ms
221
224
  …
222
- ✓ 通过 55 · 30.8s · 最慢文件 commit-chain-both.test.cjs 8.4s
225
+ ✓ 通过 55 · 42.7s · 最慢文件 commit-chain-scss.test.cjs 11.4s
223
226
 
224
- ▶ [3/3] ESLint 8 兼容性 (共 27 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
225
- ✓ ESLint 8 base:生成的 lint 脚本遍历并拦截 broken.ts · 1.1s
227
+ ▶ [3/4] ESLint 8 兼容性 (共 27 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
228
+ ✓ ESLint 8 base:生成的 lint 脚本遍历并拦截 broken.ts · 1.3s
226
229
  …
227
- ✓ 通过 27 · 14.3s
230
+ ✓ 通过 27 · 19.0s
231
+
232
+ ▶ [4/4] Stylelint 17 兼容性 (共 8 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
233
+ ✓ stylelint 17 下 --fix 生效且幂等(SCSS) · 1.4s
234
+ ✓ stylelint-order(recess-order 7 的 peer)在 stylelint 17 下可用 · 1.0s
235
+ …
236
+ ✓ 通过 8 · 7.4s
228
237
 
229
238
  ──────────────────────────────────────────────────────
230
239
  套件 通过 失败 跳过 用时
231
240
  ──────────────────────────────────────────────────────
232
- 基础 CLI 与配置矩阵 70 0 - 2.1s
233
- 现代工具链与提交链 55 0 - 30.8s
234
- ESLint 8 兼容性 27 0 - 14.3s
241
+ 基础 CLI 与配置矩阵 78 0 - 3.2s
242
+ 现代工具链与提交链 55 0 - 42.7s
243
+ ESLint 8 兼容性 27 0 - 19.0s
244
+ Stylelint 17 兼容性 8 0 - 7.4s
235
245
  ──────────────────────────────────────────────────────
236
- 合计 152 0 - 47.2s
246
+ 合计 168 0 - 72.3s
237
247
  ──────────────────────────────────────────────────────
238
248
 
239
- ⏱ 最慢文件:eslint8.test.cjs 14.3s · commit-chain-both.test.cjs 8.4s · commit-chain-scss.test.cjs 8.3s
249
+ ⏱ 最慢文件:eslint8.test.cjs 19.0s · commit-chain-scss.test.cjs 11.4s · commit-chain-less.test.cjs 11.1s
240
250
 
241
- ✅ 全部通过:152/152 项,用时 47.2s
251
+ ✅ 全部通过:168/168 项,用时 72.3s
242
252
  ```
243
253
 
244
254
 
@@ -248,7 +258,7 @@ npm run test:legacy # ESLint 8.57.0 兼容性回归测试(27 项)
248
258
  | 参数 | 作用 |
249
259
  | -------------------- | ------------------------------------------------------------- |
250
260
  | `-- --quiet` | 只看套件结论与汇总表,不列用例明细(长输出时用) |
251
- | `-- --parallel` | 强制三个套件同时跑(核数 ≥ 8 时默认已自动开启) |
261
+ | `-- --parallel` | 强制四个套件同时跑(核数 ≥ 8 时默认已自动开启) |
252
262
  | `-- --serial` | 强制串行(CI 形态,输出顺序最稳定) |
253
263
  | `-- --jobs=2` | 配合并行限制同时运行的套件数,其余排队(面板显示"等待中") |
254
264
  | `-- --suite=base,legacy` | 只跑指定套件(base / integration / legacy,逗号分隔) |
@@ -280,7 +290,8 @@ npm run test:all -- --profile # 看每个测试文件耗时,定位瓶颈
280
290
  - **文件级**:集成套件按"一条提交链一个文件"拆成 23 个测试文件,**由运行器自己按文件
281
291
  调度**(每个文件一个 `node --test` 进程),不依赖 Node 的 `--test` 默认并发值——
282
292
  同一个文件内的用例仍然串行(真实 git 仓库不能并行改),文件之间才是并发的。
283
- - **套件级**:三个套件再并行,互不共享目录。
293
+ - **套件级**:四个套件再并行,互不共享目录(基础只碰系统临时目录,集成用
294
+ `demo-test/.runtime`,ESLint 8 用 `.runtime-legacy`,Stylelint 17 用 `.runtime-sl17`)。
284
295
 
285
296
  因为每个文件里跑的都是真实 `git commit`(单次 0.5–3.9s),墙钟由**最慢的那个文件**
286
297
  决定,所以文件尽量拆到"一个文件一次提交"。每个套件跑完会直接告诉你最慢文件是谁,
@@ -306,13 +317,13 @@ npm run test:all -- --profile # 看每个测试文件耗时,定位瓶颈
306
317
  并发 git/npx 撑到 OOM(进程被 SIGKILL);文件数多于并发数时,**用例少的文件(多半是同
307
318
  一条重链)优先开跑**,避免长任务被排进最后一波、墙钟再多乘一倍。
308
319
 
309
- 实测(2 核沙箱,152 项;沙箱内存 4G,所以自动并发是 3):
320
+ 实测(2 核沙箱,168 项;沙箱内存 4G,所以自动并发是 3):
310
321
 
311
322
  | 运行方式 | 墙钟时间 |
312
323
  | ----------------------------------- | ----------------------------------------------- |
313
324
  | 串行套件 + 文件级并发 1(CI 旧形态) | 62.4s(hook 改造前同配置 70.3s) |
314
325
  | 串行套件 + 自动并发 3(默认) | 47.2s(hook 改造前同配置 51.8s) |
315
- | `--parallel` 三套件 + 自动并发 3 | 43.0s(hook 改造前同配置 46.6s) |
326
+ | `--parallel` 四套件 + 自动并发 3 | 43.0s(hook 改造前同配置 46.6s) |
316
327
  | `node --test demo-test/integration/*.test.cjs`(Node 自己调度,等同旧命令) | 43.7s(只看集成套件那一段) |
317
328
  | `npm run test:integration`(运行器调度,自动并发 3) | 30.6s(只看集成套件那一段) |
318
329
  | 串行套件 + 文件级并发 23(全部一波) | ✗ 3.8G 内存沙箱被 OOM 压垮(19 项 SIGKILL) |
@@ -330,6 +341,29 @@ npm run test:all -- --profile # 看每个测试文件耗时,定位瓶颈
330
341
 
331
342
  未安装隔离运行环境时,对应套件会明确标记"未运行"并给出 setup 命令,退出码为 1(不会假装全绿)。
332
343
 
344
+ ## 发布(维护者)
345
+
346
+ ```bash
347
+ npm run release # standard-version:按 conventional commits 决定版本号,更新 CHANGELOG,提交并打 tag
348
+ npm run release:push # release + git push --follow-tags(推 tag 即触发发布工作流)
349
+ ```
350
+
351
+ `.github/workflows/publish.yml` 只接受 **`v*` tag** 触发(`workflow_dispatch` 会先校验 ref,分支上手动触发直接失败),并且在测试全绿 + `npm pack --dry-run` 通过后才发布;发布步骤失败即失败,不做静默降级。
352
+
353
+ ### 发布凭据(推荐切到 Trusted Publishing)
354
+
355
+ 工作流默认按 **npm Trusted Publishing(OIDC)→ `NPM_TOKEN`** 的顺序取凭据:`NPM_TOKEN` secret 存在时用 token,删掉后**无需改代码**自动改走 OIDC。切换步骤:
356
+
357
+ 1. 打开 `https://www.npmjs.com/package/my-code-style/access` → Trusted Publisher → GitHub Actions,填:
358
+ - Organization or user:`myvn`
359
+ - Repository:`codeStyle`
360
+ - Workflow filename:`publish.yml`(大小写与 `.yml` 后缀都要精确匹配)
361
+ - Environment:`npm-publish`(与本仓库 Environments 里的名字一致)
362
+ 2. 仓库 Settings → Environments → `npm-publish` 添加 **required reviewers**(发布前人工批准;不配则该环境无保护)。
363
+ 3. 在 GitHub 仓库 Settings → Secrets 删除 `NPM_TOKEN`,并到 npm 账号下吊销对应 token。
364
+
365
+ OIDC 要求发布环境是 GitHub-hosted runner + `id-token: write` + npm CLI ≥ 11.5.1(工作流用 Node 24 自带),因此 `setup-node` **刻意不写 `registry-url`**——那会生成带 `_authToken` 的 `.npmrc`,让 npm 以为认证已就绪而跳过 OIDC。
366
+
333
367
  ## License
334
368
 
335
369
  MIT
package/bin/init CHANGED
@@ -211,6 +211,25 @@ function rangeIntervals(range) {
211
211
  return intervals
212
212
  }
213
213
 
214
+ /**
215
+ * 判断某个具体版本(含 minor / patch)是否落在声明的范围内。
216
+ *
217
+ * 为什么不用主版本判断:`vue-eslint-parser ^10.3.0` 与装到的 10.2.0 主版本相同,
218
+ * 但 10.3 才是 Vue 插件 10 的兼容下限,只比主版本会漏掉这类 minor 级错位。
219
+ * 这里直接做区间相交判断,既不受主版本位数限制(stylelint 16/17),也不丢 minor。
220
+ * 返回 null 表示范围无法解析(不判断)。
221
+ */
222
+ function satisfiesRange(range, version) {
223
+ const parts = parseVersionFields(version)
224
+ if (!parts || parts.some((part) => part === null)) return null
225
+ const target = lowerBound(parts)
226
+ const intervals = rangeIntervals(range)
227
+ if (!intervals.length) return null
228
+ return intervals.some(
229
+ ([lower, upper]) => compareVersion(lower, target) <= 0 && compareVersion(target, upper) < 0,
230
+ )
231
+ }
232
+
214
233
  /**
215
234
  * List every major version a declared range can install (0-12), or null when
216
235
  * the range cannot be parsed.
@@ -233,18 +252,13 @@ function majorsForRange(range) {
233
252
  }
234
253
 
235
254
  /**
236
- * Read the actually installed ESLint major version. In a monorepo the binary is
237
- * usually hoisted to the workspace root, so walk up to 8 levels; a version is
238
- * only accepted from the project root itself or from an ancestor that looks
239
- * like a workspace root (package.json / pnpm-workspace.yaml / lerna.json).
255
+ * 读取任意包在本项目(或向上寻找的“工作区根”)里实际安装的版本。
256
+ * monorepo 里依赖常被提升到工作区根,所以要向上走;只采纳项目根或带
257
+ * manifest 的祖先目录,避免把无关上级目录的 node_modules 当成本项目依赖。
240
258
  */
241
- function findInstalledEslintMajor(rootDir) {
259
+ function findInstalledPackage(pkgName, rootDir) {
242
260
  let dir = rootDir
243
261
  for (let depth = 0; depth < 8; depth += 1) {
244
- // 只采纳项目根或“像工作区根”的祖先里的安装(package.json / pnpm-workspace.yaml /
245
- // lerna.json)。中间层(如 monorepo 的 packages/)通常没有任何 manifest,
246
- // 必须继续向上找;而完全无关的上级目录(例如 /tmp 或 $HOME 的 node_modules)
247
- // 即使存在 eslint 也不能当作项目依赖。
248
262
  const isProjectRoot = dir === rootDir
249
263
  const isWorkspaceRoot =
250
264
  fs.existsSync(path.resolve(dir, "package.json")) ||
@@ -253,16 +267,20 @@ function findInstalledEslintMajor(rootDir) {
253
267
 
254
268
  if (isProjectRoot || isWorkspaceRoot) {
255
269
  try {
256
- const installedPkgPath = path.resolve(dir, "node_modules", "eslint", "package.json")
257
- if (fs.existsSync(installedPkgPath)) {
258
- const installedPkg = JSON.parse(fs.readFileSync(installedPkgPath, "utf8"))
259
- const installedMajor = parseInt(String(installedPkg.version).split(".")[0], 10)
260
- if (!Number.isNaN(installedMajor)) {
261
- return installedMajor
270
+ const manifestPath = path.resolve(
271
+ dir,
272
+ "node_modules",
273
+ ...pkgName.split("/"),
274
+ "package.json",
275
+ )
276
+ if (fs.existsSync(manifestPath)) {
277
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"))
278
+ if (manifest.version) {
279
+ return { version: String(manifest.version), dir, manifest }
262
280
  }
263
281
  }
264
282
  } catch {
265
- // ignore unreadable/partial installs and keep walking up
283
+ // 忽略读不动的安装(半装状态),继续向上找
266
284
  }
267
285
  }
268
286
 
@@ -273,6 +291,16 @@ function findInstalledEslintMajor(rootDir) {
273
291
  return null
274
292
  }
275
293
 
294
+ /**
295
+ * 读取实际安装的 ESLint 主版本号(见 findInstalledPackage 的查找规则)。
296
+ */
297
+ function findInstalledEslintMajor(rootDir) {
298
+ const installed = findInstalledPackage("eslint", rootDir)
299
+ if (!installed) return null
300
+ const major = parseInt(String(installed.version).split(".")[0], 10)
301
+ return Number.isNaN(major) ? null : major
302
+ }
303
+
276
304
  /**
277
305
  * Detect ESLint major version from the installed package or the declared range.
278
306
  * Returns 8, 9, 10, … or null (not installed and no usable range).
@@ -1194,6 +1222,8 @@ function checkPeerDeps({
1194
1222
  ...(pkg.devDependencies || {}),
1195
1223
  }
1196
1224
 
1225
+ checkInstalledVersions({ peerDeps, allPeerDeps })
1226
+
1197
1227
  const missing = allPeerDeps.filter((dep) => !allDeps[dep])
1198
1228
  if (missing.length > 0) {
1199
1229
  console.log("")
@@ -1211,6 +1241,109 @@ function checkPeerDeps({
1211
1241
  }
1212
1242
  }
1213
1243
 
1244
+ // 体检时会顺着这些包的 peerDependencies 反查底层依赖的实际版本
1245
+ const PEER_SOURCE_PACKAGES = [
1246
+ "stylelint-config-recommended",
1247
+ "stylelint-config-recommended-scss",
1248
+ "stylelint-config-recommended-vue",
1249
+ "stylelint-config-html",
1250
+ "stylelint-config-recess-order",
1251
+ "stylelint-prettier",
1252
+ ]
1253
+ // 只反查这些“装错就真的会坏”的底层包,避免把上游配置之间的宽松约束也报出来
1254
+ const PEER_SOURCE_TARGETS = [
1255
+ "stylelint",
1256
+ "stylelint-order",
1257
+ "postcss-html",
1258
+ "stylelint-config-recommended",
1259
+ "stylelint-config-html",
1260
+ "stylelint-config-recommended-scss",
1261
+ ]
1262
+
1263
+ /**
1264
+ * 已装依赖的版本体检。
1265
+ *
1266
+ * 为什么需要它:npm 遇到不满足的 peer 会直接 ERESOLVE 拒装,pnpm 只打印一行
1267
+ * WARN 就装完,于是“装是装上了,跑起来才坏”的组合会长期潜伏。这里把两类问题
1268
+ * 主动查出来并给出对齐命令(只做 major 级别判断,保持零依赖):
1269
+ * 1. 我们声明的 peer 范围 vs 项目里实际安装的版本(含 minor / patch);
1270
+ * 2. 上游配置包自己的 peer(例如 stylelint-config-recommended@18 要求
1271
+ * stylelint ^17),以及 typescript-eslint 与其 parser/plugin 的版本错位。
1272
+ */
1273
+ function checkInstalledVersions({ peerDeps, allPeerDeps, rootDir = PROJECT_ROOT }) {
1274
+ const problems = [];
1275
+ const seen = new Set();
1276
+ const add = (name, message, target) => {
1277
+ const key = `${name}\u0000${message}`;
1278
+ if (seen.has(key)) return;
1279
+ seen.add(key);
1280
+ problems.push({ name, message, target });
1281
+ };
1282
+ const minorOf = (version) => String(version).split(".").slice(0, 2).join(".");
1283
+
1284
+ // 1) 本工具的 peer 范围 vs 实际安装
1285
+ for (const dep of allPeerDeps) {
1286
+ const range = dep === "eslint" ? peerDeps.eslint : peerDeps[dep];
1287
+ if (!range || range === "*" || range.startsWith("npm:")) continue;
1288
+ const installed = findInstalledPackage(dep, rootDir);
1289
+ if (!installed) continue;
1290
+ const allowed = satisfiesRange(range, installed.version);
1291
+ if (allowed === null || allowed) continue;
1292
+ add(dep, `${dep} 装了 ${installed.version},本工具要求 ${range}`, `${dep}@${range}`);
1293
+ }
1294
+
1295
+ // 2) 上游配置包的 peer vs 实际安装(配置文件本身也会挑底层版本)
1296
+ for (const dep of PEER_SOURCE_PACKAGES) {
1297
+ const source = findInstalledPackage(dep, rootDir);
1298
+ if (!source) continue;
1299
+ for (const [target, range] of Object.entries(source.manifest.peerDependencies || {})) {
1300
+ if (!PEER_SOURCE_TARGETS.includes(target)) continue;
1301
+ if (range === "*" || String(range).startsWith("npm:")) continue;
1302
+ const installed = findInstalledPackage(target, rootDir);
1303
+ if (!installed) {
1304
+ if (!rangeIntervals(range).length) continue;
1305
+ add(
1306
+ target,
1307
+ `${dep}@${source.version} 需要 ${target}@${range},项目里没有安装`,
1308
+ `${target}@${range}`,
1309
+ );
1310
+ continue;
1311
+ }
1312
+ const allowed = satisfiesRange(range, installed.version);
1313
+ if (allowed === null || allowed) continue;
1314
+ add(
1315
+ target,
1316
+ `${dep}@${source.version} 要求 ${target}@${range},实际装了 ${installed.version}`,
1317
+ `${target}@${range}`,
1318
+ );
1319
+ }
1320
+ }
1321
+
1322
+ // 3) typescript-eslint 与 @typescript-eslint/parser、eslint-plugin 必须同版本
1323
+ const meta = findInstalledPackage("typescript-eslint", rootDir);
1324
+ if (meta) {
1325
+ for (const sibling of ["@typescript-eslint/parser", "@typescript-eslint/eslint-plugin"]) {
1326
+ const installed = findInstalledPackage(sibling, rootDir);
1327
+ if (!installed) continue;
1328
+ if (minorOf(installed.version) === minorOf(meta.version)) continue;
1329
+ add(
1330
+ sibling,
1331
+ `${sibling}@${installed.version} 与 typescript-eslint@${meta.version} 不是同一版本`,
1332
+ `${sibling}@${meta.version}`,
1333
+ );
1334
+ }
1335
+ }
1336
+
1337
+ if (!problems.length) return;
1338
+ console.log("");
1339
+ warn("以下依赖已安装,但版本与当前配置不匹配:");
1340
+ for (const problem of problems) console.log(` · ${problem.message}`);
1341
+ console.log("");
1342
+ warn("对齐命令(也可以把上游配置包降到与你现有版本匹配的大版本):");
1343
+ const targets = [...new Set(problems.map((problem) => problem.target))];
1344
+ console.log(` ${addDevCommand(detectPackageManager())} ${targets.join(" ")}`);
1345
+ }
1346
+
1214
1347
  // Only run when executed directly; requiring this file must be side-effect free.
1215
1348
  if (require.main === module) {
1216
1349
  main()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-code-style",
3
- "version": "1.6.1",
3
+ "version": "1.7.1",
4
4
  "description": "Lint/Format/Git 配置工程化 npm 包(ESLint + Prettier + Stylelint + Commitlint + Husky + standard-version)",
5
5
  "type": "commonjs",
6
6
  "main": "src/eslint/uniapp.cjs",
@@ -47,7 +47,9 @@
47
47
  "test:integration:setup": "node demo-test/setup-integration.cjs",
48
48
  "test:integration": "node scripts/test-all.cjs --suite=integration",
49
49
  "test:legacy:setup": "node demo-test/setup-legacy.cjs",
50
+ "test:stylelint17:setup": "node demo-test/setup-stylelint17.cjs",
50
51
  "test:legacy": "node scripts/test-all.cjs --suite=legacy",
52
+ "test:stylelint17": "node scripts/test-all.cjs --suite=stylelint17",
51
53
  "test:all": "node scripts/test-all.cjs",
52
54
  "diagnose": "node scripts/diagnose-speed.cjs",
53
55
  "sweep": "node scripts/sweep-concurrency.cjs",
@@ -88,12 +90,13 @@
88
90
  "postcss-scss": "^4.0.0",
89
91
  "prettier": "^3.0.0",
90
92
  "standard-version": "^9.0.0",
91
- "stylelint": "^16.0.0",
93
+ "stylelint": "^16.24.0 || ^17.0.0",
92
94
  "stylelint-config-html": "^2.0.0",
93
- "stylelint-config-recess-order": "^5.0.0",
94
- "stylelint-config-recommended": "^17.0.0",
95
- "stylelint-config-recommended-scss": "^16.0.0",
95
+ "stylelint-config-recess-order": "^5.0.0 || ^6.0.0 || ^7.0.0",
96
+ "stylelint-config-recommended": "^17.0.0 || ^18.0.0",
97
+ "stylelint-config-recommended-scss": "^16.0.0 || ^17.0.0",
96
98
  "stylelint-config-recommended-vue": "^2.0.0",
99
+ "stylelint-order": "^6.0.0 || ^7.0.0 || ^8.0.0",
97
100
  "stylelint-prettier": "^5.0.0",
98
101
  "typescript": "^5.0.0",
99
102
  "typescript-eslint": "^8.0.0",
@@ -181,6 +184,9 @@
181
184
  "stylelint-config-recommended-vue": {
182
185
  "optional": true
183
186
  },
187
+ "stylelint-order": {
188
+ "optional": true
189
+ },
184
190
  "stylelint-prettier": {
185
191
  "optional": true
186
192
  },