my-code-style 1.2.0 → 1.4.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/README.md +82 -42
- package/bin/init +524 -116
- package/package.json +219 -123
- package/src/commitlint/base.cjs +0 -1
- package/src/commitlint/scopes.cjs +119 -8
- package/src/eslint/base.cjs +14 -2
- package/src/eslint/flat/_shared.mjs +7 -8
- package/src/eslint/flat/base.mjs +32 -14
- package/src/eslint/flat/vue3.mjs +8 -7
- package/src/eslint/vue3.cjs +13 -5
- package/src/gitignore +10 -0
- package/src/husky/commit-msg +1 -4
- package/src/husky/pre-commit +0 -3
- package/src/prettier/index.cjs +1 -2
- package/src/stylelint/index.cjs +22 -1
- package/src/stylelint/less.cjs +17 -2
package/bin/init
CHANGED
|
@@ -62,42 +62,165 @@ function getProjectPkg() {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
function getDependencyVersion(pkg, depName) {
|
|
65
|
-
return (
|
|
66
|
-
|
|
67
|
-
||
|
|
65
|
+
return (
|
|
66
|
+
(pkg.dependencies && pkg.dependencies[depName]) ||
|
|
67
|
+
(pkg.devDependencies && pkg.devDependencies[depName]) ||
|
|
68
|
+
(pkg.peerDependencies && pkg.peerDependencies[depName]) ||
|
|
69
|
+
null
|
|
70
|
+
)
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
// --- Detection helpers ---
|
|
71
74
|
|
|
72
75
|
/**
|
|
73
|
-
* Detect
|
|
74
|
-
* Returns 8, 9, or null (not installed)
|
|
76
|
+
* Detect if project is configured as ECMAScript Module (ESM)
|
|
75
77
|
*/
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
+
function isEsmProject(pkg = getProjectPkg()) {
|
|
79
|
+
return pkg?.type === "module"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect ESLint major version from package.json or installed node_modules
|
|
84
|
+
* Returns 8, 9, or null (not installed or ambiguous)
|
|
85
|
+
*/
|
|
86
|
+
function detectEslintVersion(pkg = getProjectPkg(), rootDir = PROJECT_ROOT) {
|
|
78
87
|
if (!pkg) return null
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
|
|
89
|
+
// 1. If eslint is installed in node_modules, read its actual major version directly
|
|
90
|
+
try {
|
|
91
|
+
const installedPkgPath = path.resolve(rootDir, "node_modules", "eslint", "package.json")
|
|
92
|
+
if (fs.existsSync(installedPkgPath)) {
|
|
93
|
+
const installedPkg = JSON.parse(fs.readFileSync(installedPkgPath, "utf8"))
|
|
94
|
+
const installedMajor = parseInt(installedPkg.version?.split(".")[0], 10)
|
|
95
|
+
if (!Number.isNaN(installedMajor)) {
|
|
96
|
+
return installedMajor
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Fall through to package.json dependency declaration
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Parse package.json dependency declaration
|
|
104
|
+
let version = getDependencyVersion(pkg, "eslint")
|
|
105
|
+
if (!version || typeof version !== "string") return null
|
|
106
|
+
|
|
107
|
+
version = version.trim()
|
|
108
|
+
|
|
109
|
+
// Handle npm alias: e.g. "npm:eslint@^8.57.0"
|
|
110
|
+
if (version.startsWith("npm:")) {
|
|
111
|
+
const atIdx = version.lastIndexOf("@")
|
|
112
|
+
if (atIdx > 3) {
|
|
113
|
+
version = version.slice(atIdx + 1).trim()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle workspace protocol: e.g. "workspace:^8.0.0", "workspace:*"
|
|
118
|
+
if (version.startsWith("workspace:")) {
|
|
119
|
+
version = version.slice("workspace:".length).trim()
|
|
120
|
+
if (version === "*" || version === "^" || version === "~") {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Unspecified or dynamic ranges
|
|
126
|
+
if (
|
|
127
|
+
version === "*" ||
|
|
128
|
+
version === "" ||
|
|
129
|
+
version.startsWith("catalog:") ||
|
|
130
|
+
version.startsWith("file:") ||
|
|
131
|
+
version.startsWith("link:")
|
|
132
|
+
) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// npm tag: latest
|
|
137
|
+
if (version === "latest") {
|
|
138
|
+
return 9
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Range spanning both 8 and 9 (e.g. "^8.57.0 || ^9.0.0")
|
|
142
|
+
if (version.includes("||")) {
|
|
143
|
+
const parts = version.split("||").map((s) => s.trim())
|
|
144
|
+
const has8 = parts.some((p) => /\b8(?:\.\d+)?/.test(p))
|
|
145
|
+
const has9 = parts.some((p) => /\b9(?:\.\d+)?/.test(p))
|
|
146
|
+
if (has8 && has9) {
|
|
147
|
+
log("ESLint 依赖同时支持 8 与 9 且未安装本地副本,默认采用 ESLint 9 Flat Config")
|
|
148
|
+
return 9
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Explicit major 8 vs 9
|
|
153
|
+
const isMajor8 = /\b8(?:\.|\.x|\b)/.test(version) && !/^\s*(?:>=|\^)\s*9/.test(version)
|
|
154
|
+
const isMajor9 = /\b9(?:\.|\.x|\b)/.test(version) && !/<\s*9/.test(version)
|
|
155
|
+
|
|
156
|
+
if (isMajor8 && !isMajor9) return 8
|
|
157
|
+
if (isMajor9 && !isMajor8) return 9
|
|
158
|
+
|
|
159
|
+
// Fallback: check leading digit if available
|
|
82
160
|
const match = version.match(/(\d+)/)
|
|
83
|
-
|
|
161
|
+
if (match) {
|
|
162
|
+
return parseInt(match[1], 10)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Detect if project is a uni-app project
|
|
170
|
+
*/
|
|
171
|
+
function isUniAppProject() {
|
|
172
|
+
const pkg = getProjectPkg()
|
|
173
|
+
if (!pkg) return false
|
|
174
|
+
const allDeps = {
|
|
175
|
+
...(pkg.dependencies || {}),
|
|
176
|
+
...(pkg.devDependencies || {}),
|
|
177
|
+
...(pkg.peerDependencies || {}),
|
|
178
|
+
}
|
|
179
|
+
return !!(allDeps["@dcloudio/uni-app"] || allDeps["uni-app"])
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Detect if project is a Vue project
|
|
184
|
+
*/
|
|
185
|
+
function isVueProject() {
|
|
186
|
+
const pkg = getProjectPkg()
|
|
187
|
+
if (!pkg) return false
|
|
188
|
+
const allDeps = {
|
|
189
|
+
...(pkg.dependencies || {}),
|
|
190
|
+
...(pkg.devDependencies || {}),
|
|
191
|
+
...(pkg.peerDependencies || {}),
|
|
192
|
+
}
|
|
193
|
+
return !!(allDeps["vue"] || allDeps["@dcloudio/uni-app"] || allDeps["uni-app"])
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Detect project type: "uniapp", "vue", or "base"
|
|
198
|
+
*/
|
|
199
|
+
function detectProjectType() {
|
|
200
|
+
if (isUniAppProject()) return "uniapp"
|
|
201
|
+
if (isVueProject()) return "vue"
|
|
202
|
+
return "base"
|
|
84
203
|
}
|
|
85
204
|
|
|
86
205
|
/**
|
|
87
206
|
* Detect CSS preprocessor in use
|
|
88
|
-
* Returns "scss", "less", or "none"
|
|
207
|
+
* Returns "scss", "less", "both", or "none"
|
|
89
208
|
*/
|
|
90
209
|
function detectCssPreprocessor() {
|
|
91
210
|
const pkg = getProjectPkg()
|
|
92
211
|
if (!pkg) return "scss"
|
|
93
|
-
const allDeps = {
|
|
212
|
+
const allDeps = {
|
|
213
|
+
...(pkg.dependencies || {}),
|
|
214
|
+
...(pkg.devDependencies || {}),
|
|
215
|
+
...(pkg.peerDependencies || {}),
|
|
216
|
+
}
|
|
94
217
|
|
|
95
218
|
const hasSass = allDeps["sass"] || allDeps["node-sass"] || allDeps["sass-loader"]
|
|
96
219
|
const hasLess = allDeps["less"] || allDeps["less-loader"]
|
|
97
220
|
|
|
221
|
+
if (hasSass && hasLess) return "both"
|
|
98
222
|
if (hasLess && !hasSass) return "less"
|
|
99
223
|
if (hasSass) return "scss"
|
|
100
|
-
if (hasLess) return "less"
|
|
101
224
|
|
|
102
225
|
// Fallback: check for existing stylelint config
|
|
103
226
|
if (fileExists(".stylelintrc.cjs") || fileExists(".stylelintrc.js")) {
|
|
@@ -105,33 +228,33 @@ function detectCssPreprocessor() {
|
|
|
105
228
|
const stylelintFile = fileExists(".stylelintrc.cjs")
|
|
106
229
|
? ".stylelintrc.cjs"
|
|
107
230
|
: ".stylelintrc.js"
|
|
108
|
-
const content = fs.readFileSync(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (
|
|
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"
|
|
113
237
|
} catch {}
|
|
238
|
+
return "scss"
|
|
114
239
|
}
|
|
115
240
|
|
|
116
|
-
|
|
117
|
-
|
|
241
|
+
// If not a Vue/uni-app project and no css preprocessor found, skip stylelint
|
|
242
|
+
if (!isVueProject()) {
|
|
243
|
+
return "none"
|
|
244
|
+
}
|
|
118
245
|
|
|
119
|
-
|
|
120
|
-
* Detect if project is a uni-app project
|
|
121
|
-
*/
|
|
122
|
-
function isUniAppProject() {
|
|
123
|
-
const pkg = getProjectPkg()
|
|
124
|
-
if (!pkg) return false
|
|
125
|
-
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) }
|
|
126
|
-
return !!(allDeps["@dcloudio/uni-app"] || allDeps["uni-app"])
|
|
246
|
+
return "scss" // default for Vue/uni-app projects
|
|
127
247
|
}
|
|
128
248
|
|
|
129
249
|
// --- Config templates ---
|
|
130
250
|
|
|
131
|
-
function eslintrcContent(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
251
|
+
function eslintrcContent(projectType) {
|
|
252
|
+
let importPath = "my-code-style/eslint"
|
|
253
|
+
if (projectType === "uniapp") {
|
|
254
|
+
importPath = "my-code-style/eslint/uniapp"
|
|
255
|
+
} else if (projectType === "vue") {
|
|
256
|
+
importPath = "my-code-style/eslint/vue3"
|
|
257
|
+
}
|
|
135
258
|
|
|
136
259
|
return `// ESLint config — powered by my-code-style
|
|
137
260
|
// https://www.npmjs.com/package/my-code-style
|
|
@@ -144,19 +267,22 @@ module.exports = config
|
|
|
144
267
|
`
|
|
145
268
|
}
|
|
146
269
|
|
|
147
|
-
function eslintFlatConfigContent(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
270
|
+
function eslintFlatConfigContent(projectType) {
|
|
271
|
+
let importPath = "my-code-style/eslint/flat"
|
|
272
|
+
let varName = "baseConfig"
|
|
273
|
+
if (projectType === "uniapp") {
|
|
274
|
+
importPath = "my-code-style/eslint/flat/uniapp"
|
|
275
|
+
varName = "uniappConfig"
|
|
276
|
+
} else if (projectType === "vue") {
|
|
277
|
+
importPath = "my-code-style/eslint/flat/vue3"
|
|
278
|
+
varName = "vue3Config"
|
|
279
|
+
}
|
|
152
280
|
|
|
153
281
|
return `// ESLint Flat Config — powered by my-code-style
|
|
154
282
|
// https://www.npmjs.com/package/my-code-style
|
|
155
283
|
import ${varName} from "${importPath}"
|
|
156
284
|
|
|
157
|
-
export default [
|
|
158
|
-
...${varName},
|
|
159
|
-
]
|
|
285
|
+
export default [...${varName}]
|
|
160
286
|
`
|
|
161
287
|
}
|
|
162
288
|
|
|
@@ -171,9 +297,8 @@ function prettierignoreContent() {
|
|
|
171
297
|
}
|
|
172
298
|
|
|
173
299
|
function stylelintrcContent(cssPreprocessor) {
|
|
174
|
-
const importPath =
|
|
175
|
-
? "my-code-style/stylelint/less"
|
|
176
|
-
: "my-code-style/stylelint"
|
|
300
|
+
const importPath =
|
|
301
|
+
cssPreprocessor === "less" ? "my-code-style/stylelint/less" : "my-code-style/stylelint"
|
|
177
302
|
|
|
178
303
|
return `// Stylelint config — powered by my-code-style
|
|
179
304
|
module.exports = require("${importPath}")
|
|
@@ -209,19 +334,11 @@ module.exports = require("my-code-style/versionrc")
|
|
|
209
334
|
}
|
|
210
335
|
|
|
211
336
|
function huskyCommitMsg() {
|
|
212
|
-
return
|
|
213
|
-
. "$(dirname -- "$0")/_/husky.sh"
|
|
214
|
-
|
|
215
|
-
npx --no-install commitlint --edit
|
|
216
|
-
`
|
|
337
|
+
return `npx --no-install commitlint --edit "\${1}"\n`
|
|
217
338
|
}
|
|
218
339
|
|
|
219
340
|
function huskyPreCommit() {
|
|
220
|
-
return
|
|
221
|
-
. "$(dirname -- "$0")/_/husky.sh"
|
|
222
|
-
|
|
223
|
-
npx --no-install -- lint-staged
|
|
224
|
-
`
|
|
341
|
+
return `npx --no-install -- lint-staged\n`
|
|
225
342
|
}
|
|
226
343
|
|
|
227
344
|
function editorconfigContent() {
|
|
@@ -232,26 +349,59 @@ function gitattributesContent() {
|
|
|
232
349
|
return readTemplate("src/gitattributes")
|
|
233
350
|
}
|
|
234
351
|
|
|
352
|
+
function gitignoreContent() {
|
|
353
|
+
return readTemplate("src/gitignore")
|
|
354
|
+
}
|
|
355
|
+
|
|
235
356
|
/**
|
|
236
357
|
* Generate the lint-staged config based on detected CSS preprocessor
|
|
237
358
|
*/
|
|
238
359
|
function getLintStagedConfig(cssPreprocessor) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
360
|
+
// Disjoint groups: a file is owned by exactly one task array.
|
|
361
|
+
// lint-staged runs each array in order, while unrelated groups may run concurrently.
|
|
362
|
+
const componentTasks = ["prettier --write", "eslint --fix"]
|
|
363
|
+
const styleTasks = ["prettier --write"]
|
|
364
|
+
if (cssPreprocessor !== "none") {
|
|
365
|
+
componentTasks.push("stylelint --fix")
|
|
366
|
+
styleTasks.push("stylelint --fix")
|
|
367
|
+
}
|
|
368
|
+
const styleFiles =
|
|
369
|
+
cssPreprocessor === "both"
|
|
370
|
+
? "**/*.{html,css,scss,less}"
|
|
371
|
+
: cssPreprocessor === "less"
|
|
372
|
+
? "**/*.{html,css,less}"
|
|
373
|
+
: cssPreprocessor === "scss"
|
|
374
|
+
? "**/*.{html,css,scss}"
|
|
375
|
+
: "**/*.{html,css}"
|
|
376
|
+
const config = {
|
|
377
|
+
"**/*.{vue,nvue}": componentTasks,
|
|
378
|
+
"**/*.{js,ts,jsx,tsx,cjs,mjs,mts,cts}": ["prettier --write", "eslint --fix"],
|
|
379
|
+
[styleFiles]: styleTasks,
|
|
380
|
+
"**/*.{json,json5,md,yml,yaml}": ["prettier --write"],
|
|
249
381
|
}
|
|
382
|
+
|
|
383
|
+
return config
|
|
250
384
|
}
|
|
251
385
|
|
|
252
386
|
// --- Main ---
|
|
253
387
|
|
|
254
388
|
function main() {
|
|
389
|
+
const args = process.argv.slice(2)
|
|
390
|
+
const supported = new Set(["--dry-run", "--backup", "--version", "-v", "--help", "-h"])
|
|
391
|
+
const unknown = args.filter((arg) => !supported.has(arg))
|
|
392
|
+
if (unknown.length) {
|
|
393
|
+
console.error(`未知参数: ${unknown.join(", ")};使用 --help 查看帮助`)
|
|
394
|
+
process.exitCode = 1
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
398
|
+
console.log("Usage: my-code-style-init [--dry-run] [--backup] [--version|-v] [--help|-h]")
|
|
399
|
+
console.log(" --backup 覆盖已有配置前将其备份到 .my-code-style-backup/ 目录")
|
|
400
|
+
console.log(" --dry-run 试运行,仅输出将执行的变更,不写入任何文件")
|
|
401
|
+
console.log("初始化会覆盖部分配置和 hooks;请先备份并运行 --dry-run。")
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
255
405
|
// Handle --version / -v
|
|
256
406
|
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
257
407
|
const pkg = require("../package.json")
|
|
@@ -259,6 +409,34 @@ function main() {
|
|
|
259
409
|
return
|
|
260
410
|
}
|
|
261
411
|
|
|
412
|
+
// Validate before detection or any filesystem writes. Missing package.json
|
|
413
|
+
// remains supported, but malformed manifests must never be treated as empty.
|
|
414
|
+
if (fileExists("package.json")) {
|
|
415
|
+
try {
|
|
416
|
+
const pkg = JSON.parse(
|
|
417
|
+
fs.readFileSync(path.resolve(PROJECT_ROOT, "package.json"), "utf8"),
|
|
418
|
+
)
|
|
419
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) {
|
|
420
|
+
throw new Error("package.json 必须是 JSON 对象")
|
|
421
|
+
}
|
|
422
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies", "scripts"]) {
|
|
423
|
+
if (pkg[key] === undefined) continue
|
|
424
|
+
if (
|
|
425
|
+
!pkg[key] ||
|
|
426
|
+
typeof pkg[key] !== "object" ||
|
|
427
|
+
Array.isArray(pkg[key]) ||
|
|
428
|
+
Object.values(pkg[key]).some((value) => typeof value !== "string")
|
|
429
|
+
) {
|
|
430
|
+
throw new Error(`${key} 必须是值为字符串的对象`)
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error(`无法读取有效的 package.json:${error.message};未修改任何文件`)
|
|
435
|
+
process.exitCode = 1
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
262
440
|
const dryRun = process.argv.includes("--dry-run")
|
|
263
441
|
const label = dryRun ? "[DRY RUN] " : ""
|
|
264
442
|
|
|
@@ -274,31 +452,77 @@ function main() {
|
|
|
274
452
|
// --- Detection phase ---
|
|
275
453
|
const eslintVersion = detectEslintVersion()
|
|
276
454
|
const cssPreprocessor = detectCssPreprocessor()
|
|
277
|
-
const
|
|
455
|
+
const projectType = detectProjectType()
|
|
278
456
|
|
|
279
457
|
// Determine ESLint format
|
|
280
|
-
const
|
|
458
|
+
const hasLegacyConfigFile =
|
|
459
|
+
fileExists(".eslintrc.js") ||
|
|
460
|
+
fileExists(".eslintrc.cjs") ||
|
|
461
|
+
fileExists(".eslintrc.json") ||
|
|
462
|
+
fileExists(".eslintrc.yaml") ||
|
|
463
|
+
fileExists(".eslintrc.yml") ||
|
|
464
|
+
fileExists(".eslintrc")
|
|
465
|
+
|
|
466
|
+
const hasFlatConfigFile =
|
|
467
|
+
fileExists("eslint.config.mjs") ||
|
|
468
|
+
fileExists("eslint.config.js") ||
|
|
469
|
+
fileExists("eslint.config.ts") ||
|
|
470
|
+
fileExists("eslint.config.cjs")
|
|
471
|
+
|
|
472
|
+
if (eslintVersion !== null && eslintVersion < 8) {
|
|
473
|
+
console.error(`不支持 ESLint ${eslintVersion} 版本;本配置包仅支持 ESLint 8 或 9,未修改任何文件`)
|
|
474
|
+
process.exitCode = 1
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Do not silently mix an installed major version with an incompatible format.
|
|
479
|
+
if (
|
|
480
|
+
(eslintVersion >= 9 && hasLegacyConfigFile && !hasFlatConfigFile) ||
|
|
481
|
+
(eslintVersion === 8 && hasFlatConfigFile)
|
|
482
|
+
) {
|
|
483
|
+
console.error("ESLint 版本与已有配置格式冲突;请先手动迁移或确认配置格式,未修改任何文件")
|
|
484
|
+
process.exitCode = 1
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
if (!eslintVersion && hasLegacyConfigFile && !hasFlatConfigFile) {
|
|
488
|
+
console.error(
|
|
489
|
+
"检测到传统 ESLint 配置但无法确定版本;请先声明 ESLint 版本或迁移到 Flat Config,未修改任何文件",
|
|
490
|
+
)
|
|
491
|
+
process.exitCode = 1
|
|
492
|
+
return
|
|
493
|
+
}
|
|
494
|
+
const useFlatConfig = eslintVersion !== 8
|
|
281
495
|
const eslintFormat = useFlatConfig ? "flat" : "eslintrc"
|
|
282
496
|
|
|
283
|
-
log(`检测到 ESLint 版本: ${eslintVersion || "
|
|
284
|
-
log(
|
|
285
|
-
|
|
497
|
+
log(`检测到 ESLint 版本: ${eslintVersion || "默认最新 (9+)"} → 使用 ${eslintFormat} 格式`)
|
|
498
|
+
log(
|
|
499
|
+
`检测到项目类型: ${projectType === "uniapp" ? "uni-app" : projectType === "vue" ? "Vue" : "Node/TS 基础"}`,
|
|
500
|
+
)
|
|
501
|
+
log(
|
|
502
|
+
`检测到 CSS 预处理器: ${cssPreprocessor === "both" ? "SCSS + Less (混合)" : cssPreprocessor}`,
|
|
503
|
+
)
|
|
286
504
|
console.log("")
|
|
287
505
|
|
|
288
506
|
// --- Build file list based on detection ---
|
|
289
507
|
const files = []
|
|
290
508
|
|
|
291
|
-
if (
|
|
509
|
+
if (hasFlatConfigFile || hasLegacyConfigFile) {
|
|
510
|
+
log("保留已有 ESLint 配置,不生成额外入口")
|
|
511
|
+
} else if (useFlatConfig) {
|
|
292
512
|
files.push({
|
|
293
513
|
path: "eslint.config.mjs",
|
|
294
|
-
exists:
|
|
295
|
-
|
|
514
|
+
exists:
|
|
515
|
+
fileExists("eslint.config.mjs") ||
|
|
516
|
+
fileExists("eslint.config.js") ||
|
|
517
|
+
fileExists("eslint.config.ts") ||
|
|
518
|
+
fileExists("eslint.config.cjs"),
|
|
519
|
+
content: eslintFlatConfigContent(projectType),
|
|
296
520
|
})
|
|
297
521
|
} else {
|
|
298
522
|
files.push({
|
|
299
523
|
path: ".eslintrc.cjs",
|
|
300
524
|
exists: fileExists(".eslintrc.cjs"),
|
|
301
|
-
content: eslintrcContent(
|
|
525
|
+
content: eslintrcContent(projectType),
|
|
302
526
|
})
|
|
303
527
|
}
|
|
304
528
|
|
|
@@ -331,9 +555,19 @@ function main() {
|
|
|
331
555
|
content: commitlintrcContent(),
|
|
332
556
|
})
|
|
333
557
|
|
|
558
|
+
const isEsm = isEsmProject()
|
|
559
|
+
let versionrcPath = ".versionrc.js"
|
|
560
|
+
if (fileExists(".versionrc.cjs") || isEsm) {
|
|
561
|
+
versionrcPath = ".versionrc.cjs"
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (isEsm && fileExists(".versionrc.js") && !fileExists(".versionrc.cjs")) {
|
|
565
|
+
warn("检测到 ESM 项目 (type: module) 存在旧的 .versionrc.js,standard-version 需使用 .versionrc.cjs")
|
|
566
|
+
}
|
|
567
|
+
|
|
334
568
|
files.push({
|
|
335
|
-
path:
|
|
336
|
-
exists: fileExists(
|
|
569
|
+
path: versionrcPath,
|
|
570
|
+
exists: fileExists(versionrcPath),
|
|
337
571
|
content: versionrcContent(),
|
|
338
572
|
})
|
|
339
573
|
|
|
@@ -349,6 +583,14 @@ function main() {
|
|
|
349
583
|
content: gitattributesContent(),
|
|
350
584
|
})
|
|
351
585
|
|
|
586
|
+
if (!fileExists(".gitignore")) {
|
|
587
|
+
files.push({
|
|
588
|
+
path: ".gitignore",
|
|
589
|
+
exists: false,
|
|
590
|
+
content: gitignoreContent(),
|
|
591
|
+
})
|
|
592
|
+
}
|
|
593
|
+
|
|
352
594
|
// Show what will be created/overwritten
|
|
353
595
|
for (const f of files) {
|
|
354
596
|
if (f.exists) {
|
|
@@ -385,33 +627,137 @@ function main() {
|
|
|
385
627
|
return
|
|
386
628
|
}
|
|
387
629
|
|
|
388
|
-
//
|
|
389
|
-
|
|
390
|
-
|
|
630
|
+
// Optional backup of existing files before modifying
|
|
631
|
+
const backup = args.includes("--backup")
|
|
632
|
+
if (backup) {
|
|
633
|
+
const backupDir = path.resolve(PROJECT_ROOT, ".my-code-style-backup")
|
|
634
|
+
ensureDir(backupDir)
|
|
635
|
+
for (const item of [...files, ...hooks]) {
|
|
636
|
+
if (fileExists(item.path)) {
|
|
637
|
+
const srcPath = path.resolve(PROJECT_ROOT, item.path)
|
|
638
|
+
const targetPath = path.resolve(backupDir, item.path)
|
|
639
|
+
ensureDir(path.dirname(targetPath))
|
|
640
|
+
fs.copyFileSync(srcPath, targetPath)
|
|
641
|
+
log(`已备份 ${item.path} -> .my-code-style-backup/${item.path}`)
|
|
642
|
+
}
|
|
643
|
+
}
|
|
391
644
|
}
|
|
392
645
|
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
646
|
+
// ESLint 8 directory traversal defaults to .js; explicitly include typed
|
|
647
|
+
// files and the selected component formats so `lint` cannot silently skip them.
|
|
648
|
+
const legacyExtensions =
|
|
649
|
+
".js,.cjs,.mjs,.ts,.mts,.cts,.jsx,.tsx" +
|
|
650
|
+
(projectType !== "base" ? ",.vue" : "") +
|
|
651
|
+
(projectType === "uniapp" ? ",.nvue" : "")
|
|
652
|
+
const lintCommand = useFlatConfig ? "eslint ." : `eslint . --ext ${legacyExtensions}`
|
|
653
|
+
|
|
654
|
+
// Take snapshot of files and hooks before making any filesystem modifications
|
|
655
|
+
const snapshot = new Map()
|
|
656
|
+
for (const f of files) {
|
|
657
|
+
const abs = path.resolve(PROJECT_ROOT, f.path)
|
|
658
|
+
let existed = false
|
|
659
|
+
let isFile = false
|
|
660
|
+
let content = null
|
|
661
|
+
try {
|
|
662
|
+
if (fs.existsSync(abs)) {
|
|
663
|
+
existed = true
|
|
664
|
+
isFile = fs.statSync(abs).isFile()
|
|
665
|
+
if (isFile) content = fs.readFileSync(abs, "utf8")
|
|
666
|
+
}
|
|
667
|
+
} catch {}
|
|
668
|
+
snapshot.set(f.path, { existed, isFile, content })
|
|
396
669
|
}
|
|
397
670
|
for (const h of hooks) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
671
|
+
const abs = path.resolve(PROJECT_ROOT, h.path)
|
|
672
|
+
let existed = false
|
|
673
|
+
let isFile = false
|
|
674
|
+
let content = null
|
|
675
|
+
let mode = undefined
|
|
676
|
+
try {
|
|
677
|
+
if (fs.existsSync(abs)) {
|
|
678
|
+
existed = true
|
|
679
|
+
const stat = fs.statSync(abs)
|
|
680
|
+
isFile = stat.isFile()
|
|
681
|
+
mode = stat.mode
|
|
682
|
+
if (isFile) content = fs.readFileSync(abs, "utf8")
|
|
683
|
+
}
|
|
684
|
+
} catch {}
|
|
685
|
+
snapshot.set(h.path, { existed, isFile, content, mode })
|
|
686
|
+
}
|
|
687
|
+
const pkgPath = path.resolve(PROJECT_ROOT, "package.json")
|
|
688
|
+
const hadPkg = fs.existsSync(pkgPath)
|
|
689
|
+
let pkgContent = null
|
|
690
|
+
try {
|
|
691
|
+
if (hadPkg && fs.statSync(pkgPath).isFile()) {
|
|
692
|
+
pkgContent = fs.readFileSync(pkgPath, "utf8")
|
|
693
|
+
}
|
|
694
|
+
} catch {}
|
|
695
|
+
const hadHuskyDir = fs.existsSync(huskyDir)
|
|
696
|
+
|
|
697
|
+
try {
|
|
698
|
+
// Actually write files
|
|
699
|
+
for (const f of files) {
|
|
700
|
+
writeFile(f.path, f.content)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Husky
|
|
704
|
+
if (!hasHusky) {
|
|
705
|
+
ensureDir(huskyDir)
|
|
706
|
+
}
|
|
707
|
+
for (const h of hooks) {
|
|
708
|
+
writeFile(h.path, h.content)
|
|
709
|
+
fs.chmodSync(path.resolve(PROJECT_ROOT, h.path), 0o755)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// package.json scripts + lint-staged
|
|
713
|
+
modifyPackageJson({
|
|
714
|
+
scripts: {
|
|
715
|
+
lint: lintCommand,
|
|
716
|
+
"lint:fix": `${lintCommand} --fix`,
|
|
717
|
+
format: "prettier --write .",
|
|
718
|
+
prepare: "husky",
|
|
719
|
+
release: "standard-version",
|
|
720
|
+
cz: "czg",
|
|
721
|
+
},
|
|
722
|
+
"lint-staged": getLintStagedConfig(cssPreprocessor),
|
|
723
|
+
})
|
|
724
|
+
success("更新 package.json (scripts + lint-staged)")
|
|
725
|
+
} catch (err) {
|
|
726
|
+
console.error(`\n ✖ 初始化写入失败: ${err.message};正在回滚...`)
|
|
727
|
+
for (const [relPath, info] of snapshot.entries()) {
|
|
728
|
+
const abs = path.resolve(PROJECT_ROOT, relPath)
|
|
729
|
+
try {
|
|
730
|
+
if (info.existed && info.isFile) {
|
|
731
|
+
fs.writeFileSync(abs, info.content, "utf8")
|
|
732
|
+
if (info.mode !== undefined) {
|
|
733
|
+
fs.chmodSync(abs, info.mode)
|
|
734
|
+
}
|
|
735
|
+
} else if (!info.existed && fs.existsSync(abs)) {
|
|
736
|
+
fs.rmSync(abs, { force: true, recursive: true })
|
|
737
|
+
}
|
|
738
|
+
} catch (rollbackErr) {
|
|
739
|
+
console.error(` 回滚文件失败: ${relPath} (${rollbackErr.message})`)
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
if (hadPkg) {
|
|
743
|
+
fs.writeFileSync(pkgPath, pkgContent, "utf8")
|
|
744
|
+
} else if (fs.existsSync(pkgPath)) {
|
|
745
|
+
fs.rmSync(pkgPath, { force: true })
|
|
746
|
+
}
|
|
747
|
+
if (!hadHuskyDir && fs.existsSync(huskyDir)) {
|
|
748
|
+
try {
|
|
749
|
+
if (fs.readdirSync(huskyDir).length === 0) {
|
|
750
|
+
fs.rmdirSync(huskyDir)
|
|
751
|
+
}
|
|
752
|
+
} catch {}
|
|
753
|
+
}
|
|
754
|
+
console.error(" ✓ 已成功回滚所有更改,工作区已恢复原始状态。\n")
|
|
755
|
+
process.exitCode = 1
|
|
756
|
+
return
|
|
757
|
+
}
|
|
412
758
|
|
|
413
759
|
// Check for missing peerDependencies
|
|
414
|
-
checkPeerDeps({ useFlatConfig, cssPreprocessor })
|
|
760
|
+
checkPeerDeps({ useFlatConfig, cssPreprocessor, projectType })
|
|
415
761
|
|
|
416
762
|
console.log("")
|
|
417
763
|
success("配置初始化完成!")
|
|
@@ -441,25 +787,38 @@ function modifyPackageJson(updates) {
|
|
|
441
787
|
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
442
788
|
}
|
|
443
789
|
|
|
444
|
-
// Merge scripts
|
|
790
|
+
// Merge scripts: updates won't overwrite existing scripts with the same key
|
|
445
791
|
if (updates.scripts) {
|
|
446
|
-
pkg.scripts = { ...
|
|
792
|
+
pkg.scripts = { ...updates.scripts, ...(pkg.scripts || {}) }
|
|
447
793
|
}
|
|
448
794
|
|
|
449
|
-
//
|
|
795
|
+
// Set lint-staged (replace existing lint-staged to avoid stale/duplicate patterns)
|
|
450
796
|
if (updates["lint-staged"]) {
|
|
451
|
-
pkg["lint-staged"] =
|
|
797
|
+
pkg["lint-staged"] = updates["lint-staged"]
|
|
452
798
|
}
|
|
453
799
|
|
|
454
800
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n", "utf8")
|
|
455
801
|
}
|
|
456
802
|
|
|
457
|
-
function checkPeerDeps({
|
|
803
|
+
function checkPeerDeps({
|
|
804
|
+
useFlatConfig = false,
|
|
805
|
+
cssPreprocessor = "scss",
|
|
806
|
+
projectType = "base",
|
|
807
|
+
} = {}) {
|
|
808
|
+
const pkg = getProjectPkg()
|
|
809
|
+
if (!pkg) return
|
|
810
|
+
|
|
811
|
+
// If running within the package itself, skip external missing deps check
|
|
812
|
+
if (pkg.name === "my-code-style") {
|
|
813
|
+
return
|
|
814
|
+
}
|
|
815
|
+
|
|
458
816
|
const baseDeps = [
|
|
459
817
|
"eslint",
|
|
460
818
|
"prettier",
|
|
461
|
-
"eslint-plugin-vue",
|
|
819
|
+
...(projectType !== "base" ? ["eslint-plugin-vue"] : []),
|
|
462
820
|
"@commitlint/cli",
|
|
821
|
+
"@commitlint/config-conventional",
|
|
463
822
|
"husky",
|
|
464
823
|
"lint-staged",
|
|
465
824
|
"czg",
|
|
@@ -467,25 +826,73 @@ function checkPeerDeps({ useFlatConfig = false, cssPreprocessor = "scss" } = {})
|
|
|
467
826
|
]
|
|
468
827
|
|
|
469
828
|
const flatDeps = useFlatConfig
|
|
470
|
-
? [
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
829
|
+
? [
|
|
830
|
+
"typescript-eslint",
|
|
831
|
+
"eslint-import-resolver-typescript",
|
|
832
|
+
...(projectType !== "base" ? ["vue-eslint-parser"] : []),
|
|
833
|
+
"globals",
|
|
834
|
+
"eslint-plugin-import-x",
|
|
835
|
+
"@eslint/js",
|
|
836
|
+
"eslint-plugin-prettier",
|
|
837
|
+
"eslint-config-prettier",
|
|
838
|
+
]
|
|
839
|
+
: [
|
|
840
|
+
"@typescript-eslint/parser",
|
|
841
|
+
"@typescript-eslint/eslint-plugin",
|
|
842
|
+
...(projectType !== "base" ? ["vue-eslint-parser"] : []),
|
|
843
|
+
"eslint-plugin-import",
|
|
844
|
+
"eslint-import-resolver-typescript",
|
|
845
|
+
"eslint-plugin-prettier",
|
|
846
|
+
"eslint-config-prettier",
|
|
847
|
+
]
|
|
848
|
+
|
|
849
|
+
const cssDeps =
|
|
850
|
+
cssPreprocessor === "both"
|
|
851
|
+
? [
|
|
852
|
+
"stylelint",
|
|
853
|
+
"postcss-scss",
|
|
854
|
+
"postcss-less",
|
|
855
|
+
"postcss-html",
|
|
856
|
+
"stylelint-config-recommended",
|
|
857
|
+
"stylelint-config-recommended-scss",
|
|
858
|
+
"stylelint-config-recommended-vue",
|
|
859
|
+
"stylelint-config-html",
|
|
860
|
+
"stylelint-config-recess-order",
|
|
861
|
+
"stylelint-prettier",
|
|
862
|
+
]
|
|
863
|
+
: cssPreprocessor === "scss"
|
|
864
|
+
? [
|
|
865
|
+
"stylelint",
|
|
866
|
+
"postcss-scss",
|
|
867
|
+
"postcss-html",
|
|
868
|
+
"stylelint-config-recommended",
|
|
869
|
+
"stylelint-config-recommended-scss",
|
|
870
|
+
"stylelint-config-recommended-vue",
|
|
871
|
+
"stylelint-config-html",
|
|
872
|
+
"stylelint-config-recess-order",
|
|
873
|
+
"stylelint-prettier",
|
|
874
|
+
]
|
|
875
|
+
: cssPreprocessor === "less"
|
|
876
|
+
? [
|
|
877
|
+
"stylelint",
|
|
878
|
+
"postcss-less",
|
|
879
|
+
"postcss-html",
|
|
880
|
+
"stylelint-config-recommended",
|
|
881
|
+
"stylelint-config-recommended-vue",
|
|
882
|
+
"stylelint-config-html",
|
|
883
|
+
"stylelint-config-recess-order",
|
|
884
|
+
"stylelint-prettier",
|
|
885
|
+
]
|
|
886
|
+
: []
|
|
478
887
|
|
|
479
888
|
const allPeerDeps = [...baseDeps, ...flatDeps, ...cssDeps]
|
|
480
889
|
|
|
481
890
|
// Read peer dependency versions from my-code-style package.json
|
|
482
|
-
const packagePkg = JSON.parse(
|
|
891
|
+
const packagePkg = JSON.parse(
|
|
892
|
+
fs.readFileSync(path.resolve(PACKAGE_DIR, "package.json"), "utf8"),
|
|
893
|
+
)
|
|
483
894
|
const peerDeps = packagePkg.peerDependencies || {}
|
|
484
895
|
|
|
485
|
-
const pkgPath = path.resolve(PROJECT_ROOT, "package.json")
|
|
486
|
-
if (!fileExists("package.json")) return
|
|
487
|
-
|
|
488
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
489
896
|
const allDeps = {
|
|
490
897
|
...(pkg.dependencies || {}),
|
|
491
898
|
...(pkg.devDependencies || {}),
|
|
@@ -499,7 +906,8 @@ function checkPeerDeps({ useFlatConfig = false, cssPreprocessor = "scss" } = {})
|
|
|
499
906
|
warn("一键安装命令:")
|
|
500
907
|
console.log(` pnpm add -D my-code-style \\`)
|
|
501
908
|
missing.forEach((dep, i) => {
|
|
502
|
-
const version =
|
|
909
|
+
const version =
|
|
910
|
+
dep === "eslint" ? (useFlatConfig ? "^9.0.0" : "^8.57.0") : peerDeps[dep] || ""
|
|
503
911
|
const depWithVersion = version ? `${dep}@${version}` : dep
|
|
504
912
|
const suffix = i < missing.length - 1 ? " \\" : ""
|
|
505
913
|
console.log(` ${depWithVersion}${suffix}`)
|