my-code-style 1.9.4 → 1.9.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## 变更日志
2
2
 
3
+ ## [1.9.5](https://github.com/myvn/codeStyle/compare/v1.9.4...v1.9.5) (2026-10-20)
4
+
5
+
6
+ ### 🐛 Bug Fixes | Bug 修复
7
+
8
+ * **config:** Less 线误报、ESLint root、doctor 变体判定 ([7c9cc87](https://github.com/myvn/codeStyle/commit/7c9cc8771bbf5a94c61117d7afdf111a21ea162d))
9
+
3
10
  ## [1.9.4](https://github.com/myvn/codeStyle/compare/v1.9.3...v1.9.4) (2026-09-28)
4
11
 
5
12
  ## [1.9.3](https://github.com/myvn/codeStyle/compare/v1.9.2...v1.9.3) (2026-09-23)
package/README.md CHANGED
@@ -33,6 +33,8 @@ pnpm prepare # npm: npm run prepare
33
33
  pnpm lint
34
34
  ```
35
35
 
36
+ > **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`)。
37
+
36
38
  init 脚本会自动检测技术栈类型(uni-app / Vue 3 / Node 基础库)、ESLint 版本、CSS 预处理器和包管理器,按需生成配置文件,并在结尾列出尚未安装的 peerDependencies 与对应安装命令。
37
39
 
38
40
  初始化后:`lint-staged` 会在 `git commit` 时自动修复暂存文件,`commitlint` 校验提交信息,`pnpm release` 生成版本与 CHANGELOG。全过程细节见 [docs/init-flow.md](docs/init-flow.md),收益与代价评估见 [docs/manual.md](docs/manual.md)。
@@ -234,7 +236,7 @@ npx my-code-style-doctor
234
236
 
235
237
  对既有项目随时复验(init 的依赖体检只在新装/重装时打印一次):
236
238
 
237
- - **peer 依赖**:按项目类型裁剪核对(与 init 同口径——ESLint 8 才查 classic import、无预处理器不查 stylelint 族),缺失/版本越界逐项报红,并输出可整段粘贴的补齐命令;
239
+ - **peer 依赖**:按项目类型裁剪核对(与 init 同口径——ESLint 8 才查 classic import、不涉样式的项目不查 stylelint 族)。stylelint 变体**以项目实际的配置为准**(`.stylelintrc.cjs` extends 的是 `stylelint/less` 就只查 Less 线的 peer,不会给纯 Less 项目索要 `postcss-scss`),没有配置文件时才回落到依赖推断;缺失/版本越界逐项报红,并输出可整段粘贴的补齐命令;
238
240
  - **husky hooks**:存在 / 非空 / 可执行位 / 引用命令可解析 / `core.hooksPath` 指向;
239
241
  - **lockfile**:是否被 `.gitignore` 排除(排除 = 不同机器版本组合不可复现);
240
242
  - **配置文件**:四件套是否就位。
@@ -262,7 +264,7 @@ npx my-code-style-doctor
262
264
  | 只装了 `my-code-style`,`pnpm lint` 报找不到 eslint | peerDependencies 全部是 optional,不会自动安装 | 按 CLI 结尾输出的清单装全,**别漏 `typescript`**(`@typescript-eslint/parser` / `typescript-eslint` 的必需 peer) |
263
265
  | 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 项目必装) |
264
266
  | `pnpm prepare` 输出 `.git can't be found`,hooks 不生效 | husky 必须在 git 仓库内执行 | 先 `git init`、再 `pnpm prepare`;验证 `git config core.hooksPath` 输出 `.husky/_` |
265
- | 安装依赖报 `ERR_PNPM_IGNORED_BUILDS`(如 `unrs-resolver`) | pnpm 10+ 默认拦截依赖的构建脚本 | 执行 `pnpm approve-builds` 后重新安装,不是初始化失败 |
267
+ | 安装依赖报 `ERR_PNPM_IGNORED_BUILDS`(如 `unrs-resolver`) | pnpm 10+ 默认拦截依赖的构建脚本 | 执行 `pnpm approve-builds` 后重新安装,不是初始化失败;要写进配置就用 `pnpm-workspace.yaml` 的 `allowBuilds`(pnpm 10.26+,取代已弃用的 `onlyBuiltDependencies`;pnpm 11+ 不再读旧键) |
266
268
  | `eslint` 声明 `>=8.0.0` 却生成了 Flat Config | 无上界范围 npm 实际会装 9,CLI 按 9 生成 | 确定留在 8 就把范围收紧到 `<9`(如 `~8.57.0`)后重跑 init |
267
269
  | 首次接入存量/脚手架项目,`lint:style` 集中报几十个属性顺序错 | stylelint-order(recess-order)按既有排序规约检查,脚手架模板(如 Vite 的 `style.css`)不满足 | 一次性 `stylelint "**/*.{vue,nvue,html,css,scss,less}" --fix` 自动归位后再提交;此后保持绿色。属存量风格收敛,非本配置引入的错误 |
268
270
  | 重复 init 后,改过的 `.prettierrc.cjs` / hooks 被覆盖 | 除 ESLint 入口与已存在的 `.gitignore` 外,目标文件一律覆盖 | 先用 `--dry-run` 看清单、用 `--backup` 留档(备份含 `package.json`,目录会自动写入 `.gitignore`) |
@@ -281,16 +283,16 @@ npx my-code-style-doctor
281
283
  ## 测试套件
282
284
 
283
285
  ```bash
284
- npm run test:all # 全量运行 200 项:用例明细 + 分类统计 + 最慢文件定位
286
+ npm run test:all # 全量运行 209 项:用例明细 + 分类统计 + 最慢文件定位
285
287
  npm run diagnose # 换机器后先跑它:进程 / git / 文件系统 / hooks 各占多少
286
288
  npm run sweep # 给本机找最佳文件级并发(扫 integration 套件)
287
- npm test # 只跑基础 CLI 与配置矩阵(98 项)
289
+ npm test # 只跑基础 CLI 与配置矩阵(104 项)
288
290
  npm run test:integration:setup # 安装现代化隔离依赖运行环境
289
- npm run test:integration # Flat Config、真实 Husky 及提交链路测试(66 项)
291
+ npm run test:integration # Flat Config、真实 Husky 及提交链路测试(67 项)
290
292
  npm run test:legacy:setup # 安装 ESLint 8 隔离运行环境
291
- npm run test:legacy # ESLint 8.57.0 兼容性回归测试(27 项)
293
+ npm run test:legacy # ESLint 8.57.0 兼容性回归测试(28 项)
292
294
  npm run test:stylelint17:setup # 安装 Stylelint 17 隔离运行环境
293
- npm run test:stylelint17 # Stylelint 17 生态兼容性回归(8 项)
295
+ npm run test:stylelint17 # Stylelint 17 生态兼容性回归(10 项)
294
296
  ```
295
297
 
296
298
  `npm run test:all` 由 `scripts/test-all.cjs` 驱动,输出**套件 → 用例 → 汇总**三层:
@@ -299,50 +301,50 @@ npm run test:stylelint17 # Stylelint 17 生态兼容性回归(8 项)
299
301
  ```text
300
302
  my-code-style 测试套件
301
303
 
302
- ▶ [1/4] 基础 CLI 与配置矩阵 (共 98 项,5 文件 · 3 文件并发(自动:2 核 / 内存 4G))
304
+ ▶ [1/4] 基础 CLI 与配置矩阵 (共 104 项,5 文件 · 3 文件并发(自动:2 核 / 内存 4G))
303
305
  ✓ 运行器 TAP 解析:统计通过与失败、SKIP 与每用例耗时 · 7ms
304
306
  ✓ cz 交互提示为中文,且可选类型与 type-enum 一一对应 · 1ms
305
307
  ✓ peer 范围覆盖我们声明支持的上游大版本(校准防回退,BUG-027) · 0ms
306
308
  ✓ 一键安装命令对含 || 的版本范围加引号,可整段粘贴执行 · 40ms
307
309
  ✓ 初始化结尾指引把 cz 用法说清楚(先 git add + 指到 README 手册) · 80ms
308
310
  …
309
- ✓ 通过 98 · 4.2s · 最慢文件 init-matrix.test.cjs 4.0s
311
+ ✓ 通过 104 · 4.2s · 最慢文件 init-matrix.test.cjs 4.0s
310
312
 
311
- ▶ [2/4] 现代工具链与提交链 (共 66 项,24 文件 · 3 文件并发(自动:2 核 / 内存 4G))
313
+ ▶ [2/4] 现代工具链与提交链 (共 67 项,24 文件 · 3 文件并发(自动:2 核 / 内存 4G))
312
314
  ✓ 完整提交链:JS/TS/Vue/nvue/CSS/less 自动修复及二次复检 · 10.4s
313
315
  ✓ 发布防呆:HEAD 已有 v* tag 时拦截(此时 release 工具会静默抬版并写出空 CHANGELOG) · 609ms
314
316
  …
315
- ✓ 通过 66 · 43.8s · 最慢文件 commit-chain-both.test.cjs 11.9s
317
+ ✓ 通过 67 · 43.8s · 最慢文件 commit-chain-both.test.cjs 11.9s
316
318
 
317
- ▶ [3/4] ESLint 8 兼容性 (共 27 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
319
+ ▶ [3/4] ESLint 8 兼容性 (共 28 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
318
320
  ✓ 独立运行 ESLint 8 支持下限而非 ESLint 9 · 1ms
319
321
  ✓ ESLint 8 base:实际 CLI 加载生成配置并接受正常文件 · 1.3s
320
322
  …
321
- ✓ 通过 27 · 20.2s
323
+ ✓ 通过 28 · 20.2s
322
324
 
323
- ▶ [4/4] Stylelint 17 兼容性 (共 9 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
325
+ ▶ [4/4] Stylelint 17 兼容性 (共 10 项,1 文件 · 3 文件并发(自动:2 核 / 内存 4G))
324
326
  ✓ 隔离环境装的是 stylelint 17(不是 16) · 2ms
325
327
  ✓ 生成的 .stylelintrc.cjs 在 stylelint 17 下可加载并放过正常 SCSS · 700ms
326
328
  …
327
- ✓ 通过 9 · 8.8s
329
+ ✓ 通过 10 · 8.8s
328
330
 
329
331
  ──────────────────────────────────────────────────────
330
332
  套件 通过 失败 跳过 用时
331
333
  ──────────────────────────────────────────────────────
332
- 基础 CLI 与配置矩阵 98 0 - 4.2s
333
- 现代工具链与提交链 66 0 - 43.8s
334
- ESLint 8 兼容性 27 0 - 20.2s
335
- Stylelint 17 兼容性 9 0 - 8.8s
334
+ 基础 CLI 与配置矩阵 104 0 - 4.2s
335
+ 现代工具链与提交链 67 0 - 43.8s
336
+ ESLint 8 兼容性 28 0 - 20.2s
337
+ Stylelint 17 兼容性 10 0 - 8.8s
336
338
  ──────────────────────────────────────────────────────
337
- 合计 200 0 - 77.1s
339
+ 合计 209 0 - 77.1s
338
340
  ──────────────────────────────────────────────────────
339
341
 
340
342
  ⏱ 最慢文件:eslint8.test.cjs 18.0s · commit-chain-less.test.cjs 11.4s · commit-chain-both.test.cjs 11.3s
341
343
 
342
- ✅ 全部通过:200/200 项,用时 77.1s
344
+ ✅ 全部通过:209/209 项,用时 77.1s
343
345
  ```
344
346
 
345
- 终端里每个套件下方还有一条实时进度条(`██████░░░░ 38/66 失败 0 4.0s`)。附加参数:
347
+ 终端里每个套件下方还有一条实时进度条(`██████░░░░ 38/67 失败 0 4.0s`)。附加参数:
346
348
 
347
349
  | 参数 | 作用 |
348
350
  | ------------------------ | ---------------------------------------------------------- |
package/bin/doctor CHANGED
@@ -9,6 +9,8 @@
9
9
  const fs = require("fs")
10
10
  const path = require("path")
11
11
  const { spawnSync } = require("child_process")
12
+ // 样式变体判定与 bin/init 共用原语,避免两侧口径漂移(BUG-043)
13
+ const styleVariant = require("../src/stylelint/variant.cjs")
12
14
 
13
15
  const PROJECT_ROOT = process.cwd()
14
16
  let problems = 0
@@ -136,8 +138,8 @@ ok(`my-code-style ${selfPkg.version}`)
136
138
 
137
139
  // ---- ② peer 依赖:装了吗?版本对吗? ----
138
140
  // 与 init 的依赖体检(checkPeerDeps)同口径裁剪:classic import 仅 ESLint 8 legacy
139
- // 线需要;无预处理器且非 Vue 项目不查 stylelint 族;Vue 项目才查 vue 全家。
140
- // 改动需与 bin/init 两侧同步(init-matrix 回归守底)。
141
+ // 线需要;不涉样式的项目不查 stylelint 族(变体判定见 src/stylelint/variant.cjs);
142
+ // Vue 项目才查 vue 全家。改动需与 bin/init 两侧同步(init-matrix 回归守底)。
141
143
  function requiredPeers(manifest) {
142
144
  const deps = {
143
145
  ...manifest.dependencies,
@@ -157,14 +159,11 @@ function requiredPeers(manifest) {
157
159
  ].some((f) => fs.existsSync(path.join(PROJECT_ROOT, f)))
158
160
  const useFlat = hasFlatEntry || (!hasLegacyEntry && eslintMajor >= 9)
159
161
  const isVue = !!(deps.vue || deps["@dcloudio/uni-app"] || deps["uni-app"])
160
- const cssPre =
161
- deps.less && (deps.sass || isVue)
162
- ? "both"
163
- : deps.less
164
- ? "less"
165
- : deps.sass || isVue
166
- ? "scss"
167
- : null
162
+ // 样式变体以项目实际的 stylelint 配置为准(配置顶层 extends 缺 peer 会直接加载
163
+ // 失败),没有配置时才回落到依赖推断。此前这里只看依赖,`less && (sass || isVue)`
164
+ // 把「Vue + Less」判成 both,给纯 Less 项目索要 postcss-scss /
165
+ // stylelint-config-recommended-scss(BUG-043,下游 Vue 3 + Less 项目实测)。
166
+ const cssPre = styleVariant.detectVariant(PROJECT_ROOT, deps, isVue)
168
167
 
169
168
  const list = [
170
169
  "eslint",
package/bin/init CHANGED
@@ -12,6 +12,8 @@
12
12
 
13
13
  const fs = require("fs")
14
14
  const path = require("path")
15
+ // 样式变体判定与 bin/doctor 共用原语,避免两侧口径漂移(BUG-043)
16
+ const styleVariant = require("../src/stylelint/variant.cjs")
15
17
 
16
18
  const PROJECT_ROOT = process.cwd()
17
19
  const PACKAGE_DIR = path.resolve(__dirname, "..")
@@ -489,37 +491,15 @@ function detectCssPreprocessor() {
489
491
  }
490
492
  : {}
491
493
 
492
- const hasSass = allDeps["sass"] || allDeps["node-sass"] || allDeps["sass-loader"]
493
- const hasLess = allDeps["less"] || allDeps["less-loader"]
494
+ // 依赖优先(项目意图的更强信号);认不出再看已有的 stylelint 配置。
495
+ // 判定原语与 bin/doctor 共用(src/stylelint/variant.cjs),两侧不会漂移;
496
+ // doctor 的优先级相反(配置优先),因为它要判的是「哪条线会被真正加载」。
497
+ const fromDeps = styleVariant.variantFromDeps(allDeps)
498
+ if (fromDeps) return fromDeps
494
499
 
495
- if (hasSass && hasLess) return "both"
496
- if (hasLess && !hasSass) return "less"
497
- if (hasSass) return "scss"
498
-
499
- // Fallback: check for existing stylelint config
500
- if (fileExists(".stylelintrc.cjs") || fileExists(".stylelintrc.js")) {
501
- const stylelintFile = fileExists(".stylelintrc.cjs")
502
- ? ".stylelintrc.cjs"
503
- : ".stylelintrc.js"
504
- let content = ""
505
- try {
506
- content = fs.readFileSync(path.resolve(PROJECT_ROOT, stylelintFile), "utf8")
507
- } catch {}
508
- // "my-code-style/stylelint" is a prefix of "my-code-style/stylelint/less",
509
- // so the Less entry must be matched before the SCSS one.
510
- const usesLess =
511
- /my-code-style\/stylelint\/less(?:-override)?/.test(content) ||
512
- content.includes("postcss-less") ||
513
- content.includes("stylelint-config-recommended-less")
514
- const usesScss =
515
- /my-code-style\/stylelint(?![-\w/])/.test(content) ||
516
- content.includes("postcss-scss") ||
517
- content.includes("stylelint-config-recommended-scss")
518
- if (usesLess && usesScss) return "both"
519
- if (usesLess) return "less"
520
- if (usesScss) return "scss"
521
- return "scss"
522
- }
500
+ const fromConfig = styleVariant.variantFromConfig(PROJECT_ROOT)
501
+ if (fromConfig === "unknown") return "scss" // 有配置但认不出变体:按统一入口处理
502
+ if (fromConfig) return fromConfig
523
503
 
524
504
  // 无 package.json 时无法判断项目类型,不猜测样式方案(避免凭空安装一堆 stylelint 依赖)
525
505
  if (!pkg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-code-style",
3
- "version": "1.9.4",
3
+ "version": "1.9.5",
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",
@@ -5,6 +5,12 @@ const prettierConfig = require("../prettier/index.cjs")
5
5
  const { overrides: _prettierOverrides, ...prettierOptions } = prettierConfig
6
6
 
7
7
  module.exports = {
8
+ // 阻止 eslintrc 的级联合并:没有它,祖先目录(monorepo 根 / 上层目录)的
9
+ // .eslintrc 会被静默合并进来 —— 实测两个后果:① 根配置里本包没声明的规则
10
+ // (eqeqeq 之类)漏进子项目,报出与本包口径无关的错;② 根配置引用的插件
11
+ // 子项目没装时整条 lint 直接崩(ESLint couldn't find the plugin)。
12
+ // 本包的 stylelint 配置一直带 root: true,ESLint 侧对齐(BUG-042)。
13
+ root: true,
8
14
  env: {
9
15
  browser: true,
10
16
  es2021: true,
@@ -88,10 +94,6 @@ module.exports = {
88
94
  "no-param-reassign": "off",
89
95
  "no-redeclare": "off",
90
96
  "prefer-promise-reject-errors": "off",
91
-
92
- // Standard JS 风格规则已通过手动配置实现,不再依赖 eslint-config-standard
93
- "style/quotes": "off",
94
- "style/semi": "off",
95
97
  },
96
98
  settings: {
97
99
  "import/parsers": { "@typescript-eslint/parser": [".ts", ".tsx"] },
@@ -66,10 +66,6 @@ export const commonRules = {
66
66
  "no-param-reassign": "off",
67
67
  "no-redeclare": "off",
68
68
  "prefer-promise-reject-errors": "off",
69
-
70
- // Standard JS uses @stylistic style/quotes — disable to avoid conflict
71
- "style/quotes": "off",
72
- "style/semi": "off",
73
69
  }
74
70
 
75
71
  /**
@@ -13,6 +13,12 @@ module.exports = {
13
13
  "at-rule-no-unknown": null,
14
14
  // Less built-in functions (darken, lighten, fade...) unknown to Stylelint
15
15
  "function-no-unknown": null,
16
+ // Less 变量出现在媒体查询里(`@media (max-width: @screen-md)`)、Less 的
17
+ // `@import (reference) "m.less"` 选项,在 CSS 语法里都非法,会被 recommended
18
+ // 开启的这两条规则判死且无法 --fix(与 less.cjs 同源:BUG-040 / BUG-041)。
19
+ // 走 less-override 合并的项目同样需要这层放行。
20
+ "media-query-no-invalid": null,
21
+ "at-rule-prelude-no-invalid": null,
16
22
  // Allow ::v-deep / /deep/ / ::ng-deep pseudo-elements
17
23
  "selector-pseudo-element-no-unknown": [
18
24
  true,
@@ -55,6 +55,16 @@ module.exports = {
55
55
  // BUG-029 深测实测);SCSS 线上游(recommended-scss)本就将其置 null,
56
56
  // 这里对齐,保证 uni-app Less 项目 rpx 写法不误报。
57
57
  "declaration-property-value-no-unknown": null,
58
+ // 同 BUG-029 的机理:Less 用 @ 写变量,媒体查询里的 `@screen-md` 在 CSS
59
+ // 语法里不是合法值,stylelint-config-recommended 开启的 media-query-no-invalid
60
+ // 会报 "Invalid media query"(BUG-040 实测:下游 18 处命中,且无 fixer,
61
+ // --fix 后原地不动 → 门禁永久红)。SCSS 线由 recommended-scss 置 null,
62
+ // Less 线没有那层覆盖,这里对齐。
63
+ "media-query-no-invalid": null,
64
+ // Less 的 `@import (reference) "m.less"` / `@import (less) "t.css"` 选项在
65
+ // CSS 语法里同样非法,at-rule-prelude-no-invalid 会把整个 prelude 判死
66
+ // (BUG-041 实测:下游 3 处命中,无 fixer)。同样对齐 SCSS 线上游的 null。
67
+ "at-rule-prelude-no-invalid": null,
58
68
  // 允许小程序 page 标签
59
69
  "selector-type-no-unknown": [true, { ignoreTypes: ["page"] }],
60
70
  "comment-empty-line-before": "never",
@@ -0,0 +1,142 @@
1
+ // 样式方案(变体)判定原语:bin/init 与 bin/doctor 共用,避免两侧口径漂移。
2
+ //
3
+ // 背景(BUG-043):doctor 曾只按 package.json 的依赖猜变体,其中
4
+ // `deps.less && (deps.sass || isVue)` 把「Vue + Less(无 sass)」判成 both,于是给
5
+ // 纯 Less 项目索要 postcss-scss / stylelint-config-recommended-scss —— 项目实际
6
+ // extends 的是 my-code-style/stylelint/less,这两个包装了也用不上,doctor 的
7
+ // 「一键补齐」还会诱导用户装无用依赖(下游 Vue 3 + Less 项目实测 exit 1)。
8
+ //
9
+ // 真正决定加载哪条线的是 stylelint 配置本身(统一入口顶层 extends 了
10
+ // recommended-scss,缺 peer 会直接加载失败),所以「体检 peer」必须以配置为准。
11
+ // init 的口径刻意不同:它要决定「生成哪份配置」,依赖是项目意图的更强信号,
12
+ // 因此保持 1.9.4 的顺序(先依赖、后配置),只是换成同一套原语。
13
+
14
+ const fs = require("fs")
15
+ const path = require("path")
16
+
17
+ // stylelint 官方配置查找顺序里的常见文件名;package.json 的 stylelint 字段单列处理
18
+ const CONFIG_FILES = [
19
+ ".stylelintrc",
20
+ ".stylelintrc.json",
21
+ ".stylelintrc.yaml",
22
+ ".stylelintrc.yml",
23
+ ".stylelintrc.js",
24
+ ".stylelintrc.cjs",
25
+ ".stylelintrc.mjs",
26
+ "stylelint.config.js",
27
+ "stylelint.config.mjs",
28
+ "stylelint.config.cjs",
29
+ ]
30
+
31
+ /**
32
+ * 读取项目已有的 stylelint 配置内容(含 package.json 的 stylelint 字段)。
33
+ * 查找顺序对齐 stylelint 自己的 searchPlaces:package.json 优先,其后按 cosmiconfig
34
+ * 的文件名顺序,命中即停。
35
+ * @returns {string|null} 没有配置文件时返回 null
36
+ */
37
+ function readStylelintConfig(projectRoot) {
38
+ const pkgPath = path.join(projectRoot, "package.json")
39
+ if (fs.existsSync(pkgPath)) {
40
+ try {
41
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
42
+ if (pkg.stylelint) {
43
+ return JSON.stringify(pkg.stylelint)
44
+ }
45
+ } catch {
46
+ // 非法 JSON 当作没有 package.json 处理(配置文件继续找)
47
+ }
48
+ }
49
+ for (const file of CONFIG_FILES) {
50
+ const configPath = path.join(projectRoot, file)
51
+ if (!fs.existsSync(configPath)) {
52
+ continue
53
+ }
54
+ try {
55
+ return fs.readFileSync(configPath, "utf8")
56
+ } catch {
57
+ // 读不出来(无权限、同名目录等)就继续找下一个候选文件
58
+ continue
59
+ }
60
+ }
61
+ return null
62
+ }
63
+
64
+ /**
65
+ * 从已有配置内容判定变体。
66
+ * @returns {"less"|"scss"|"both"|"unknown"|null}
67
+ * less/scss/both — 命中明确标记;unknown — 有配置文件但认不出变体(纯手写规则);
68
+ * null — 项目没有 stylelint 配置
69
+ */
70
+ function variantFromConfig(projectRoot) {
71
+ const content = readStylelintConfig(projectRoot)
72
+ if (content === null) {
73
+ return null
74
+ }
75
+ // "my-code-style/stylelint" 是 "my-code-style/stylelint/less" 的前缀,
76
+ // 所以 Less 入口必须先匹配(BUG-003 的教训)。
77
+ const usesLess =
78
+ /my-code-style\/stylelint\/less(?:-override)?/.test(content) ||
79
+ content.includes("postcss-less") ||
80
+ content.includes("stylelint-config-recommended-less")
81
+ const usesScss =
82
+ /my-code-style\/stylelint(?![-\w/])/.test(content) ||
83
+ content.includes("postcss-scss") ||
84
+ content.includes("stylelint-config-recommended-scss")
85
+ if (usesLess && usesScss) {
86
+ return "both"
87
+ }
88
+ if (usesLess) {
89
+ return "less"
90
+ }
91
+ if (usesScss) {
92
+ return "scss"
93
+ }
94
+ return "unknown"
95
+ }
96
+
97
+ /**
98
+ * 从依赖判定变体(sass / node-sass / sass-loader 与 less / less-loader 的组合)。
99
+ * @returns {"less"|"scss"|"both"|null} 认不出时返回 null
100
+ */
101
+ function variantFromDeps(deps = {}) {
102
+ const hasSass = Boolean(deps.sass || deps["node-sass"] || deps["sass-loader"])
103
+ const hasLess = Boolean(deps.less || deps["less-loader"])
104
+ if (hasSass && hasLess) {
105
+ return "both"
106
+ }
107
+ if (hasLess) {
108
+ return "less"
109
+ }
110
+ if (hasSass) {
111
+ return "scss"
112
+ }
113
+ return null
114
+ }
115
+
116
+ /**
117
+ * 依赖判定 + Vue/uni-app 项目默认走统一(SCSS)入口 —— 与 bin/init 的兜底一致。
118
+ * @returns {"less"|"scss"|"both"|null}
119
+ */
120
+ function variantFromDepsWithVueDefault(deps, isVue) {
121
+ return variantFromDeps(deps) ?? (isVue ? "scss" : null)
122
+ }
123
+
124
+ /**
125
+ * 体检口径(doctor):配置内容优先,认不出再看依赖。
126
+ * @returns {"less"|"scss"|"both"|null} null = 项目不涉及样式检查
127
+ */
128
+ function detectVariant(projectRoot, deps, isVue) {
129
+ const fromConfig = variantFromConfig(projectRoot)
130
+ if (fromConfig && fromConfig !== "unknown") {
131
+ return fromConfig
132
+ }
133
+ return variantFromDepsWithVueDefault(deps, isVue)
134
+ }
135
+
136
+ module.exports = {
137
+ readStylelintConfig,
138
+ variantFromConfig,
139
+ variantFromDeps,
140
+ variantFromDepsWithVueDefault,
141
+ detectVariant,
142
+ }