my-code-style 1.4.0 → 1.6.0
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 +131 -0
- package/LICENSE +21 -0
- package/README.md +185 -9
- package/bin/init +365 -66
- package/package.json +13 -10
- package/src/commitlint/scopes.cjs +83 -22
- package/src/eslint/base.cjs +21 -11
- package/src/eslint/flat/_shared.mjs +16 -11
- package/src/eslint/flat/base.mjs +21 -11
- package/src/eslint/flat/vue3.mjs +6 -1
- package/src/eslint/vue3.cjs +5 -1
- package/src/gitignore +1 -0
- package/src/husky/commit-msg +5 -1
- package/src/husky/pre-commit +6 -1
package/bin/init
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require("fs")
|
|
14
14
|
const path = require("path")
|
|
15
|
-
const { execSync } = require("child_process")
|
|
16
15
|
|
|
17
16
|
const PROJECT_ROOT = process.cwd()
|
|
18
17
|
const PACKAGE_DIR = path.resolve(__dirname, "..")
|
|
@@ -51,6 +50,24 @@ function readTemplate(relativePath) {
|
|
|
51
50
|
return fs.readFileSync(path.resolve(PACKAGE_DIR, relativePath), "utf8")
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Append an entry to the project .gitignore (idempotent, keeps existing lines).
|
|
55
|
+
*/
|
|
56
|
+
function ensureGitignoreEntry(entry) {
|
|
57
|
+
const gitignorePath = path.resolve(PROJECT_ROOT, ".gitignore")
|
|
58
|
+
if (!fs.existsSync(gitignorePath)) return
|
|
59
|
+
const content = fs.readFileSync(gitignorePath, "utf8")
|
|
60
|
+
const hasEntry = content.split("\n").some((line) => line.trim() === entry)
|
|
61
|
+
if (hasEntry) return
|
|
62
|
+
const separator = content.length === 0 || content.endsWith("\n") ? "" : "\n"
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
gitignorePath,
|
|
65
|
+
`${content}${separator}\n# my-code-style 初始化备份目录\n${entry}\n`,
|
|
66
|
+
"utf8",
|
|
67
|
+
)
|
|
68
|
+
log(`已将 ${entry} 追加到 .gitignore`)
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
function getProjectPkg() {
|
|
55
72
|
const pkgPath = path.resolve(PROJECT_ROOT, "package.json")
|
|
56
73
|
if (!fs.existsSync(pkgPath)) return null
|
|
@@ -79,25 +96,195 @@ function isEsmProject(pkg = getProjectPkg()) {
|
|
|
79
96
|
return pkg?.type === "module"
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
// --- Semver range helpers (dependency-free) ---
|
|
100
|
+
// Detection only needs to know which major versions a declared range can
|
|
101
|
+
// install. Regex guessing on the raw string misreads "<9.0.0" (8 only) and
|
|
102
|
+
// ">=8.0.0" (8 or 9), so ranges are parsed into half-open [lower, upper)
|
|
103
|
+
// intervals with the subset of npm range syntax that appears in practice.
|
|
104
|
+
|
|
105
|
+
const MAX_VERSION = [Number.POSITIVE_INFINITY, 0, 0]
|
|
106
|
+
|
|
107
|
+
function compareVersion(a, b) {
|
|
108
|
+
for (let i = 0; i < 3; i += 1) {
|
|
109
|
+
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1
|
|
110
|
+
}
|
|
111
|
+
return 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function isWildcardField(field) {
|
|
115
|
+
return field === undefined || field === "" || /^[xX*]$/.test(field)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function parseVersionFields(text) {
|
|
119
|
+
const [release] = String(text).trim().replace(/^v/, "").split("-")
|
|
120
|
+
const fields = release.split(".")
|
|
121
|
+
if (fields.length > 3) return null
|
|
122
|
+
const parts = [0, 1, 2].map((index) => {
|
|
123
|
+
const field = fields[index]
|
|
124
|
+
if (isWildcardField(field)) return null
|
|
125
|
+
return /^\d+$/.test(field) ? parseInt(field, 10) : NaN
|
|
126
|
+
})
|
|
127
|
+
return parts.some((part) => Number.isNaN(part)) ? null : parts
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function lowerBound(parts) {
|
|
131
|
+
return [parts[0] || 0, parts[1] || 0, parts[2] || 0]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function partialInterval(parts) {
|
|
135
|
+
const lower = lowerBound(parts)
|
|
136
|
+
const missing = parts.findIndex((part) => part === null)
|
|
137
|
+
if (missing === -1) return [lower, [lower[0], lower[1], lower[2] + 1]]
|
|
138
|
+
if (missing === 0) return [[0, 0, 0], MAX_VERSION]
|
|
139
|
+
const upper = [0, 0, 0]
|
|
140
|
+
for (let i = 0; i < missing; i += 1) upper[i] = parts[i]
|
|
141
|
+
upper[missing - 1] += 1
|
|
142
|
+
return [lower, upper]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function caretInterval(parts) {
|
|
146
|
+
const lower = lowerBound(parts)
|
|
147
|
+
if (parts[0] === null) return [[0, 0, 0], MAX_VERSION]
|
|
148
|
+
if (parts[0] > 0 || parts[1] === null) return [lower, [parts[0] + 1, 0, 0]]
|
|
149
|
+
if (parts[1] > 0 || parts[2] === null) return [lower, [0, parts[1] + 1, 0]]
|
|
150
|
+
return [lower, [0, 0, parts[2] + 1]]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function tildeInterval(parts) {
|
|
154
|
+
const lower = lowerBound(parts)
|
|
155
|
+
if (parts[0] === null) return [[0, 0, 0], MAX_VERSION]
|
|
156
|
+
if (parts[1] === null) return [lower, [parts[0] + 1, 0, 0]]
|
|
157
|
+
return [lower, [parts[0], parts[1] + 1, 0]]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function tokenInterval(token) {
|
|
161
|
+
const match = /^(>=|<=|>|<|\^|~|=)?\s*(.*)$/.exec(token.trim())
|
|
162
|
+
if (!match || !match[2]) return null
|
|
163
|
+
const [, operator = "", text] = match
|
|
164
|
+
if (/^[xX*]$/.test(text.trim())) return [[0, 0, 0], MAX_VERSION]
|
|
165
|
+
const parts = parseVersionFields(text)
|
|
166
|
+
if (!parts) return null
|
|
167
|
+
const lower = lowerBound(parts)
|
|
168
|
+
switch (operator) {
|
|
169
|
+
case ">=":
|
|
170
|
+
return [lower, MAX_VERSION]
|
|
171
|
+
case ">":
|
|
172
|
+
return [[lower[0], lower[1], lower[2] + 1], MAX_VERSION]
|
|
173
|
+
case "<":
|
|
174
|
+
return [[0, 0, 0], lower]
|
|
175
|
+
case "<=":
|
|
176
|
+
return [
|
|
177
|
+
[0, 0, 0],
|
|
178
|
+
[lower[0], lower[1], lower[2] + 1],
|
|
179
|
+
]
|
|
180
|
+
case "^":
|
|
181
|
+
return caretInterval(parts)
|
|
182
|
+
case "~":
|
|
183
|
+
return tildeInterval(parts)
|
|
184
|
+
default:
|
|
185
|
+
return partialInterval(parts)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function rangeIntervals(range) {
|
|
190
|
+
const intervals = []
|
|
191
|
+
for (const clause of range.split("||")) {
|
|
192
|
+
const text = clause.trim()
|
|
193
|
+
if (!text) continue
|
|
194
|
+
const hyphen = text.split(/\s+-\s+/)
|
|
195
|
+
const tokens =
|
|
196
|
+
hyphen.length === 2 ? [`>=${hyphen[0]}`, `<=${hyphen[1]}`] : text.split(/\s+/)
|
|
197
|
+
let lower = [0, 0, 0]
|
|
198
|
+
let upper = MAX_VERSION
|
|
199
|
+
let valid = true
|
|
200
|
+
for (const token of tokens) {
|
|
201
|
+
const interval = tokenInterval(token)
|
|
202
|
+
if (!interval) {
|
|
203
|
+
valid = false
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
if (compareVersion(interval[0], lower) > 0) lower = interval[0]
|
|
207
|
+
if (compareVersion(interval[1], upper) < 0) upper = interval[1]
|
|
208
|
+
}
|
|
209
|
+
if (valid && compareVersion(lower, upper) < 0) intervals.push([lower, upper])
|
|
210
|
+
}
|
|
211
|
+
return intervals
|
|
212
|
+
}
|
|
213
|
+
|
|
82
214
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
215
|
+
* List every major version a declared range can install (0-12), or null when
|
|
216
|
+
* the range cannot be parsed.
|
|
85
217
|
*/
|
|
86
|
-
function
|
|
87
|
-
|
|
218
|
+
function majorsForRange(range) {
|
|
219
|
+
const intervals = rangeIntervals(range)
|
|
220
|
+
if (!intervals.length) return null
|
|
221
|
+
const majors = []
|
|
222
|
+
for (let major = 0; major <= 12; major += 1) {
|
|
223
|
+
const lower = [major, 0, 0]
|
|
224
|
+
const upper = [major + 1, 0, 0]
|
|
225
|
+
const intersects = intervals.some(
|
|
226
|
+
([intervalLower, intervalUpper]) =>
|
|
227
|
+
compareVersion(intervalLower, upper) < 0 &&
|
|
228
|
+
compareVersion(intervalUpper, lower) > 0,
|
|
229
|
+
)
|
|
230
|
+
if (intersects) majors.push(major)
|
|
231
|
+
}
|
|
232
|
+
return majors.length ? majors : null
|
|
233
|
+
}
|
|
88
234
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
235
|
+
/**
|
|
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).
|
|
240
|
+
*/
|
|
241
|
+
function findInstalledEslintMajor(rootDir) {
|
|
242
|
+
let dir = rootDir
|
|
243
|
+
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
|
+
const isProjectRoot = dir === rootDir
|
|
249
|
+
const isWorkspaceRoot =
|
|
250
|
+
fs.existsSync(path.resolve(dir, "package.json")) ||
|
|
251
|
+
fs.existsSync(path.resolve(dir, "pnpm-workspace.yaml")) ||
|
|
252
|
+
fs.existsSync(path.resolve(dir, "lerna.json"))
|
|
253
|
+
|
|
254
|
+
if (isProjectRoot || isWorkspaceRoot) {
|
|
255
|
+
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
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch {
|
|
265
|
+
// ignore unreadable/partial installs and keep walking up
|
|
97
266
|
}
|
|
98
267
|
}
|
|
99
|
-
|
|
100
|
-
|
|
268
|
+
|
|
269
|
+
const parent = path.dirname(dir)
|
|
270
|
+
if (parent === dir) break
|
|
271
|
+
dir = parent
|
|
272
|
+
}
|
|
273
|
+
return null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Detect ESLint major version from the installed package or the declared range.
|
|
278
|
+
* Returns 8, 9, 10, … or null (not installed and no usable range).
|
|
279
|
+
*/
|
|
280
|
+
function detectEslintVersion(pkg = getProjectPkg(), rootDir = PROJECT_ROOT) {
|
|
281
|
+
if (!pkg) return null
|
|
282
|
+
|
|
283
|
+
// 1. If eslint is installed in node_modules (or hoisted to the workspace root),
|
|
284
|
+
// read its actual major version directly
|
|
285
|
+
const installedMajor = findInstalledEslintMajor(rootDir)
|
|
286
|
+
if (installedMajor !== null) {
|
|
287
|
+
return installedMajor
|
|
101
288
|
}
|
|
102
289
|
|
|
103
290
|
// 2. Parse package.json dependency declaration
|
|
@@ -138,31 +325,22 @@ function detectEslintVersion(pkg = getProjectPkg(), rootDir = PROJECT_ROOT) {
|
|
|
138
325
|
return 9
|
|
139
326
|
}
|
|
140
327
|
|
|
141
|
-
|
|
142
|
-
if (
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
if (has8 && has9) {
|
|
147
|
-
log("ESLint 依赖同时支持 8 与 9 且未安装本地副本,默认采用 ESLint 9 Flat Config")
|
|
148
|
-
return 9
|
|
149
|
-
}
|
|
328
|
+
const majors = majorsForRange(version)
|
|
329
|
+
if (!majors) {
|
|
330
|
+
// Fallback: check leading digit if available
|
|
331
|
+
const match = version.match(/(\d+)/)
|
|
332
|
+
return match ? parseInt(match[1], 10) : null
|
|
150
333
|
}
|
|
151
334
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (isMajor9 && !isMajor8) return 9
|
|
158
|
-
|
|
159
|
-
// Fallback: check leading digit if available
|
|
160
|
-
const match = version.match(/(\d+)/)
|
|
161
|
-
if (match) {
|
|
162
|
-
return parseInt(match[1], 10)
|
|
335
|
+
// Ranges such as ">=8.0.0" or "^8.57.0 || ^9.0.0" can install either
|
|
336
|
+
// major; without a local install npm resolves the newest one (9).
|
|
337
|
+
if (majors.includes(8) && majors.includes(9)) {
|
|
338
|
+
log("ESLint 依赖同时支持 8 与 9 且未安装本地副本,默认采用 ESLint 9 Flat Config")
|
|
339
|
+
return 9
|
|
163
340
|
}
|
|
164
|
-
|
|
165
|
-
return
|
|
341
|
+
if (majors.includes(9)) return 9
|
|
342
|
+
if (majors.includes(8)) return 8
|
|
343
|
+
return majors[0]
|
|
166
344
|
}
|
|
167
345
|
|
|
168
346
|
/**
|
|
@@ -193,6 +371,52 @@ function isVueProject() {
|
|
|
193
371
|
return !!(allDeps["vue"] || allDeps["@dcloudio/uni-app"] || allDeps["uni-app"])
|
|
194
372
|
}
|
|
195
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Detect the package manager in use ("pnpm" | "npm" | "yarn" | "bun" | null).
|
|
376
|
+
* The lockfile is the project's own truth, so it wins over the user agent of
|
|
377
|
+
* whatever launched this CLI (e.g. `npx` inside a pnpm project).
|
|
378
|
+
*/
|
|
379
|
+
function detectPackageManager() {
|
|
380
|
+
if (fileExists("pnpm-lock.yaml")) return "pnpm"
|
|
381
|
+
if (fileExists("yarn.lock")) return "yarn"
|
|
382
|
+
if (fileExists("bun.lockb") || fileExists("bun.lock")) return "bun"
|
|
383
|
+
if (fileExists("package-lock.json")) return "npm"
|
|
384
|
+
|
|
385
|
+
const agent = process.env.npm_config_user_agent || ""
|
|
386
|
+
if (agent.startsWith("pnpm/")) return "pnpm"
|
|
387
|
+
if (agent.startsWith("yarn/")) return "yarn"
|
|
388
|
+
if (agent.startsWith("bun/")) return "bun"
|
|
389
|
+
if (agent.startsWith("npm/")) return "npm"
|
|
390
|
+
return null
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function addDevCommand(packageManager) {
|
|
394
|
+
switch (packageManager) {
|
|
395
|
+
case "npm":
|
|
396
|
+
return "npm i -D"
|
|
397
|
+
case "yarn":
|
|
398
|
+
return "yarn add -D"
|
|
399
|
+
case "bun":
|
|
400
|
+
return "bun add -d"
|
|
401
|
+
default:
|
|
402
|
+
return "pnpm add -D"
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* `pnpm prepare` / `yarn prepare` run the script directly, but npm needs an
|
|
408
|
+
* explicit `npm run prepare` (plain `npm prepare` is not a command).
|
|
409
|
+
*/
|
|
410
|
+
function runScriptCommand(packageManager, script) {
|
|
411
|
+
switch (packageManager) {
|
|
412
|
+
case "npm":
|
|
413
|
+
case "bun":
|
|
414
|
+
return `${packageManager} run ${script}`
|
|
415
|
+
default:
|
|
416
|
+
return `${packageManager} ${script}`
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
196
420
|
/**
|
|
197
421
|
* Detect project type: "uniapp", "vue", or "base"
|
|
198
422
|
*/
|
|
@@ -208,12 +432,13 @@ function detectProjectType() {
|
|
|
208
432
|
*/
|
|
209
433
|
function detectCssPreprocessor() {
|
|
210
434
|
const pkg = getProjectPkg()
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
435
|
+
const allDeps = pkg
|
|
436
|
+
? {
|
|
437
|
+
...(pkg.dependencies || {}),
|
|
438
|
+
...(pkg.devDependencies || {}),
|
|
439
|
+
...(pkg.peerDependencies || {}),
|
|
440
|
+
}
|
|
441
|
+
: {}
|
|
217
442
|
|
|
218
443
|
const hasSass = allDeps["sass"] || allDeps["node-sass"] || allDeps["sass-loader"]
|
|
219
444
|
const hasLess = allDeps["less"] || allDeps["less-loader"]
|
|
@@ -224,20 +449,34 @@ function detectCssPreprocessor() {
|
|
|
224
449
|
|
|
225
450
|
// Fallback: check for existing stylelint config
|
|
226
451
|
if (fileExists(".stylelintrc.cjs") || fileExists(".stylelintrc.js")) {
|
|
452
|
+
const stylelintFile = fileExists(".stylelintrc.cjs")
|
|
453
|
+
? ".stylelintrc.cjs"
|
|
454
|
+
: ".stylelintrc.js"
|
|
455
|
+
let content = ""
|
|
227
456
|
try {
|
|
228
|
-
|
|
229
|
-
? ".stylelintrc.cjs"
|
|
230
|
-
: ".stylelintrc.js"
|
|
231
|
-
const content = fs.readFileSync(path.resolve(PROJECT_ROOT, stylelintFile), "utf8")
|
|
232
|
-
const hasPostcssLess = content.includes("postcss-less")
|
|
233
|
-
const hasPostcssScss =
|
|
234
|
-
content.includes("postcss-scss") || content.includes("my-code-style/stylelint")
|
|
235
|
-
if (hasPostcssLess && hasPostcssScss) return "both"
|
|
236
|
-
if (hasPostcssLess) return "less"
|
|
457
|
+
content = fs.readFileSync(path.resolve(PROJECT_ROOT, stylelintFile), "utf8")
|
|
237
458
|
} catch {}
|
|
459
|
+
// "my-code-style/stylelint" is a prefix of "my-code-style/stylelint/less",
|
|
460
|
+
// so the Less entry must be matched before the SCSS one.
|
|
461
|
+
const usesLess =
|
|
462
|
+
/my-code-style\/stylelint\/less(?:-override)?/.test(content) ||
|
|
463
|
+
content.includes("postcss-less") ||
|
|
464
|
+
content.includes("stylelint-config-recommended-less")
|
|
465
|
+
const usesScss =
|
|
466
|
+
/my-code-style\/stylelint(?![-\w/])/.test(content) ||
|
|
467
|
+
content.includes("postcss-scss") ||
|
|
468
|
+
content.includes("stylelint-config-recommended-scss")
|
|
469
|
+
if (usesLess && usesScss) return "both"
|
|
470
|
+
if (usesLess) return "less"
|
|
471
|
+
if (usesScss) return "scss"
|
|
238
472
|
return "scss"
|
|
239
473
|
}
|
|
240
474
|
|
|
475
|
+
// 无 package.json 时无法判断项目类型,不猜测样式方案(避免凭空安装一堆 stylelint 依赖)
|
|
476
|
+
if (!pkg) {
|
|
477
|
+
return "none"
|
|
478
|
+
}
|
|
479
|
+
|
|
241
480
|
// If not a Vue/uni-app project and no css preprocessor found, skip stylelint
|
|
242
481
|
if (!isVueProject()) {
|
|
243
482
|
return "none"
|
|
@@ -306,6 +545,11 @@ module.exports = require("${importPath}")
|
|
|
306
545
|
}
|
|
307
546
|
|
|
308
547
|
function commitlintrcContent() {
|
|
548
|
+
const mockScope = ["mock", "mocks", "src/mock", "src/mocks"].some((dir) =>
|
|
549
|
+
fs.existsSync(path.resolve(PROJECT_ROOT, dir)),
|
|
550
|
+
)
|
|
551
|
+
? `, "mock"`
|
|
552
|
+
: ""
|
|
309
553
|
return `// Commitlint config — powered by my-code-style
|
|
310
554
|
const base = require("my-code-style/commitlint")
|
|
311
555
|
const { generateScopes, guessCurrentScope } = require("my-code-style/commitlint/scopes")
|
|
@@ -319,7 +563,7 @@ module.exports = {
|
|
|
319
563
|
...base.prompt,
|
|
320
564
|
customScopesAlign: !scopeComplete ? "top" : "bottom",
|
|
321
565
|
defaultScope: scopeComplete,
|
|
322
|
-
scopes: [...scopes
|
|
566
|
+
scopes: [...scopes${mockScope}],
|
|
323
567
|
allowEmptyIssuePrefixs: false,
|
|
324
568
|
allowCustomIssuePrefixs: false,
|
|
325
569
|
},
|
|
@@ -333,12 +577,14 @@ module.exports = require("my-code-style/versionrc")
|
|
|
333
577
|
`
|
|
334
578
|
}
|
|
335
579
|
|
|
580
|
+
// Hook bodies live in src/husky/* so the shipped templates cannot drift from
|
|
581
|
+
// what the CLI writes. Normalise the trailing newline.
|
|
336
582
|
function huskyCommitMsg() {
|
|
337
|
-
return
|
|
583
|
+
return readTemplate("src/husky/commit-msg").replace(/\s*$/, "\n")
|
|
338
584
|
}
|
|
339
585
|
|
|
340
586
|
function huskyPreCommit() {
|
|
341
|
-
return
|
|
587
|
+
return readTemplate("src/husky/pre-commit").replace(/\s*$/, "\n")
|
|
342
588
|
}
|
|
343
589
|
|
|
344
590
|
function editorconfigContent() {
|
|
@@ -450,6 +696,7 @@ function main() {
|
|
|
450
696
|
}
|
|
451
697
|
|
|
452
698
|
// --- Detection phase ---
|
|
699
|
+
const pm = detectPackageManager() || "pnpm"
|
|
453
700
|
const eslintVersion = detectEslintVersion()
|
|
454
701
|
const cssPreprocessor = detectCssPreprocessor()
|
|
455
702
|
const projectType = detectProjectType()
|
|
@@ -470,11 +717,19 @@ function main() {
|
|
|
470
717
|
fileExists("eslint.config.cjs")
|
|
471
718
|
|
|
472
719
|
if (eslintVersion !== null && eslintVersion < 8) {
|
|
473
|
-
console.error(
|
|
720
|
+
console.error(
|
|
721
|
+
`不支持 ESLint ${eslintVersion} 版本;本配置包仅支持 ESLint 8 / 9 / 10,未修改任何文件`,
|
|
722
|
+
)
|
|
474
723
|
process.exitCode = 1
|
|
475
724
|
return
|
|
476
725
|
}
|
|
477
726
|
|
|
727
|
+
if (eslintVersion !== null && eslintVersion > 10) {
|
|
728
|
+
warn(
|
|
729
|
+
`ESLint ${eslintVersion} 尚未在 peerDependencies 声明支持(^8.57.0 || ^9.0.0 || ^10.0.0),将按 Flat Config 生成`,
|
|
730
|
+
)
|
|
731
|
+
}
|
|
732
|
+
|
|
478
733
|
// Do not silently mix an installed major version with an incompatible format.
|
|
479
734
|
if (
|
|
480
735
|
(eslintVersion >= 9 && hasLegacyConfigFile && !hasFlatConfigFile) ||
|
|
@@ -562,7 +817,9 @@ function main() {
|
|
|
562
817
|
}
|
|
563
818
|
|
|
564
819
|
if (isEsm && fileExists(".versionrc.js") && !fileExists(".versionrc.cjs")) {
|
|
565
|
-
warn(
|
|
820
|
+
warn(
|
|
821
|
+
"检测到 ESM 项目 (type: module) 存在旧的 .versionrc.js,standard-version 需使用 .versionrc.cjs",
|
|
822
|
+
)
|
|
566
823
|
}
|
|
567
824
|
|
|
568
825
|
files.push({
|
|
@@ -632,7 +889,9 @@ function main() {
|
|
|
632
889
|
if (backup) {
|
|
633
890
|
const backupDir = path.resolve(PROJECT_ROOT, ".my-code-style-backup")
|
|
634
891
|
ensureDir(backupDir)
|
|
635
|
-
|
|
892
|
+
// package.json is rewritten (scripts + lint-staged), so it must be backed
|
|
893
|
+
// up as well — users lose custom lint-staged groups otherwise.
|
|
894
|
+
for (const item of [...files, ...hooks, { path: "package.json" }]) {
|
|
636
895
|
if (fileExists(item.path)) {
|
|
637
896
|
const srcPath = path.resolve(PROJECT_ROOT, item.path)
|
|
638
897
|
const targetPath = path.resolve(backupDir, item.path)
|
|
@@ -722,6 +981,11 @@ function main() {
|
|
|
722
981
|
"lint-staged": getLintStagedConfig(cssPreprocessor),
|
|
723
982
|
})
|
|
724
983
|
success("更新 package.json (scripts + lint-staged)")
|
|
984
|
+
|
|
985
|
+
// The backup directory is ours, not the user's source — keep it out of git
|
|
986
|
+
if (backup) {
|
|
987
|
+
ensureGitignoreEntry(".my-code-style-backup/")
|
|
988
|
+
}
|
|
725
989
|
} catch (err) {
|
|
726
990
|
console.error(`\n ✖ 初始化写入失败: ${err.message};正在回滚...`)
|
|
727
991
|
for (const [relPath, info] of snapshot.entries()) {
|
|
@@ -764,19 +1028,28 @@ function main() {
|
|
|
764
1028
|
console.log("")
|
|
765
1029
|
console.log(" 下一步:")
|
|
766
1030
|
console.log(" 1. 确保已安装 peerDependencies:")
|
|
767
|
-
console.log(
|
|
1031
|
+
console.log(` ${addDevCommand(pm)} my-code-style`)
|
|
768
1032
|
if (useFlatConfig) {
|
|
769
1033
|
console.log(" 2. Flat Config 需要的额外依赖:")
|
|
770
|
-
console.log(
|
|
1034
|
+
console.log(
|
|
1035
|
+
` ${addDevCommand(pm)} typescript-eslint globals eslint-plugin-import-x @eslint/js`,
|
|
1036
|
+
)
|
|
771
1037
|
console.log(" 3. 初始化 husky:")
|
|
772
|
-
console.log(
|
|
1038
|
+
console.log(` ${runScriptCommand(pm, "prepare")}`)
|
|
773
1039
|
console.log(" 4. 使用 czg 提交 commit:")
|
|
774
1040
|
} else {
|
|
775
1041
|
console.log(" 2. 初始化 husky:")
|
|
776
|
-
console.log(
|
|
1042
|
+
console.log(` ${runScriptCommand(pm, "prepare")}`)
|
|
777
1043
|
console.log(" 3. 使用 czg 提交 commit:")
|
|
778
1044
|
}
|
|
779
|
-
console.log(
|
|
1045
|
+
console.log(` ${runScriptCommand(pm, "cz")}`)
|
|
1046
|
+
if (pm === "pnpm") {
|
|
1047
|
+
console.log("")
|
|
1048
|
+
console.log(" 提示:pnpm 10+ 默认拦截依赖的构建脚本,若安装时提示")
|
|
1049
|
+
console.log(
|
|
1050
|
+
" ERR_PNPM_IGNORED_BUILDS(如 unrs-resolver),执行 pnpm approve-builds 后重试。",
|
|
1051
|
+
)
|
|
1052
|
+
}
|
|
780
1053
|
console.log("")
|
|
781
1054
|
}
|
|
782
1055
|
|
|
@@ -787,6 +1060,20 @@ function modifyPackageJson(updates) {
|
|
|
787
1060
|
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
788
1061
|
}
|
|
789
1062
|
|
|
1063
|
+
// A generated manifest must be installable on its own: npm requires
|
|
1064
|
+
// name/version (e.g. running init in an empty directory).
|
|
1065
|
+
if (typeof pkg.name !== "string" || !pkg.name.trim()) {
|
|
1066
|
+
pkg.name =
|
|
1067
|
+
path
|
|
1068
|
+
.basename(PROJECT_ROOT)
|
|
1069
|
+
.toLowerCase()
|
|
1070
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
1071
|
+
.replace(/^[._-]+|[._-]+$/g, "") || "my-project"
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
1074
|
+
pkg.version = "1.0.0"
|
|
1075
|
+
}
|
|
1076
|
+
|
|
790
1077
|
// Merge scripts: updates won't overwrite existing scripts with the same key
|
|
791
1078
|
if (updates.scripts) {
|
|
792
1079
|
pkg.scripts = { ...updates.scripts, ...(pkg.scripts || {}) }
|
|
@@ -797,7 +1084,14 @@ function modifyPackageJson(updates) {
|
|
|
797
1084
|
pkg["lint-staged"] = updates["lint-staged"]
|
|
798
1085
|
}
|
|
799
1086
|
|
|
800
|
-
|
|
1087
|
+
// Keep the project's own indentation (a 2-space repo should not be reflowed)
|
|
1088
|
+
const previous = fileExists("package.json") ? fs.readFileSync(pkgPath, "utf8") : ""
|
|
1089
|
+
const indentMatch = previous.match(/^[ \t]+(?=")/m)
|
|
1090
|
+
fs.writeFileSync(
|
|
1091
|
+
pkgPath,
|
|
1092
|
+
JSON.stringify(pkg, null, indentMatch ? indentMatch[0] : 4) + "\n",
|
|
1093
|
+
"utf8",
|
|
1094
|
+
)
|
|
801
1095
|
}
|
|
802
1096
|
|
|
803
1097
|
function checkPeerDeps({
|
|
@@ -815,6 +1109,8 @@ function checkPeerDeps({
|
|
|
815
1109
|
|
|
816
1110
|
const baseDeps = [
|
|
817
1111
|
"eslint",
|
|
1112
|
+
// Required by @typescript-eslint/parser and typescript-eslint at runtime
|
|
1113
|
+
"typescript",
|
|
818
1114
|
"prettier",
|
|
819
1115
|
...(projectType !== "base" ? ["eslint-plugin-vue"] : []),
|
|
820
1116
|
"@commitlint/cli",
|
|
@@ -904,7 +1200,7 @@ function checkPeerDeps({
|
|
|
904
1200
|
warn(`以下依赖未安装:${missing.join(", ")}`)
|
|
905
1201
|
console.log("")
|
|
906
1202
|
warn("一键安装命令:")
|
|
907
|
-
console.log(`
|
|
1203
|
+
console.log(` ${addDevCommand(detectPackageManager())} my-code-style \\`)
|
|
908
1204
|
missing.forEach((dep, i) => {
|
|
909
1205
|
const version =
|
|
910
1206
|
dep === "eslint" ? (useFlatConfig ? "^9.0.0" : "^8.57.0") : peerDeps[dep] || ""
|
|
@@ -915,4 +1211,7 @@ function checkPeerDeps({
|
|
|
915
1211
|
}
|
|
916
1212
|
}
|
|
917
1213
|
|
|
918
|
-
|
|
1214
|
+
// Only run when executed directly; requiring this file must be side-effect free.
|
|
1215
|
+
if (require.main === module) {
|
|
1216
|
+
main()
|
|
1217
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-code-style",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"files": [
|
|
35
35
|
"src",
|
|
36
36
|
"bin",
|
|
37
|
-
"README.md"
|
|
37
|
+
"README.md",
|
|
38
|
+
"CHANGELOG.md"
|
|
38
39
|
],
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"access": "public",
|
|
@@ -44,10 +45,12 @@
|
|
|
44
45
|
"test": "node --test tests/*.test.cjs demo-test/*.test.cjs",
|
|
45
46
|
"test:demo": "node --test demo-test/*.test.cjs",
|
|
46
47
|
"test:integration:setup": "node demo-test/setup-integration.cjs",
|
|
47
|
-
"test:integration": "node
|
|
48
|
+
"test:integration": "node scripts/test-all.cjs --suite=integration",
|
|
48
49
|
"test:legacy:setup": "node demo-test/setup-legacy.cjs",
|
|
49
|
-
"test:legacy": "node
|
|
50
|
-
"test:all": "
|
|
50
|
+
"test:legacy": "node scripts/test-all.cjs --suite=legacy",
|
|
51
|
+
"test:all": "node scripts/test-all.cjs",
|
|
52
|
+
"diagnose": "node scripts/diagnose-speed.cjs",
|
|
53
|
+
"sweep": "node scripts/sweep-concurrency.cjs",
|
|
51
54
|
"lint": "eslint .",
|
|
52
55
|
"lint:fix": "eslint . --fix",
|
|
53
56
|
"format": "prettier --write .",
|
|
@@ -66,12 +69,11 @@
|
|
|
66
69
|
"peerDependencies": {
|
|
67
70
|
"@commitlint/cli": "^19.0.0",
|
|
68
71
|
"@commitlint/config-conventional": "^19.0.0",
|
|
69
|
-
"@eslint/eslintrc": "^3.0.0",
|
|
70
72
|
"@eslint/js": "^9.0.0",
|
|
71
73
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
72
74
|
"@typescript-eslint/parser": "^8.0.0",
|
|
73
75
|
"czg": "^1.0.0",
|
|
74
|
-
"eslint": "^8.57.0 || ^9.0.0",
|
|
76
|
+
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
|
75
77
|
"eslint-config-prettier": "^10.1.8",
|
|
76
78
|
"eslint-import-resolver-typescript": "^3.0.0",
|
|
77
79
|
"eslint-plugin-import": "^2.0.0",
|
|
@@ -93,6 +95,7 @@
|
|
|
93
95
|
"stylelint-config-recommended-scss": "^16.0.0",
|
|
94
96
|
"stylelint-config-recommended-vue": "^2.0.0",
|
|
95
97
|
"stylelint-prettier": "^5.0.0",
|
|
98
|
+
"typescript": "^5.0.0",
|
|
96
99
|
"typescript-eslint": "^8.0.0",
|
|
97
100
|
"vue-eslint-parser": "^10.3.0"
|
|
98
101
|
},
|
|
@@ -103,9 +106,6 @@
|
|
|
103
106
|
"@commitlint/config-conventional": {
|
|
104
107
|
"optional": true
|
|
105
108
|
},
|
|
106
|
-
"@eslint/eslintrc": {
|
|
107
|
-
"optional": true
|
|
108
|
-
},
|
|
109
109
|
"@eslint/js": {
|
|
110
110
|
"optional": true
|
|
111
111
|
},
|
|
@@ -184,6 +184,9 @@
|
|
|
184
184
|
"stylelint-prettier": {
|
|
185
185
|
"optional": true
|
|
186
186
|
},
|
|
187
|
+
"typescript": {
|
|
188
|
+
"optional": true
|
|
189
|
+
},
|
|
187
190
|
"typescript-eslint": {
|
|
188
191
|
"optional": true
|
|
189
192
|
},
|