my-code-style 1.0.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 +918 -0
- package/package.json +220 -118
- package/src/commitlint/base.cjs +0 -1
- package/src/commitlint/scopes.cjs +182 -12
- package/src/eslint/base.cjs +43 -8
- package/src/eslint/flat/_shared.mjs +32 -9
- package/src/eslint/flat/base.mjs +35 -12
- package/src/eslint/flat/vue3.mjs +17 -7
- package/src/eslint/vue3.cjs +22 -2
- package/src/gitattributes +8 -2
- package/src/gitignore +10 -0
- package/src/husky/commit-msg +1 -4
- package/src/husky/pre-commit +0 -3
- package/src/prettier/index.cjs +12 -3
- package/src/stylelint/index.cjs +22 -1
- package/src/stylelint/less.cjs +17 -2
- package/bin/init.cjs +0 -476
package/bin/init
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* my-code-style-init CLI
|
|
4
|
+
*
|
|
5
|
+
* Scaffolds lint/format/git config files into the current project.
|
|
6
|
+
* Auto-detects ESLint version and CSS preprocessor for optimal config generation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx my-code-style-init
|
|
10
|
+
* npx my-code-style-init --dry-run
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs")
|
|
14
|
+
const path = require("path")
|
|
15
|
+
const { execSync } = require("child_process")
|
|
16
|
+
|
|
17
|
+
const PROJECT_ROOT = process.cwd()
|
|
18
|
+
const PACKAGE_DIR = path.resolve(__dirname, "..")
|
|
19
|
+
|
|
20
|
+
// --- Helpers ---
|
|
21
|
+
|
|
22
|
+
function log(msg) {
|
|
23
|
+
console.log(` ${msg}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function success(msg) {
|
|
27
|
+
console.log(` ✓ ${msg}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function warn(msg) {
|
|
31
|
+
console.log(` ⚠ ${msg}`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ensureDir(dirPath) {
|
|
35
|
+
if (!fs.existsSync(dirPath)) {
|
|
36
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeFile(dest, content) {
|
|
41
|
+
const destDir = path.dirname(dest)
|
|
42
|
+
ensureDir(destDir)
|
|
43
|
+
fs.writeFileSync(dest, content, "utf8")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function fileExists(relative) {
|
|
47
|
+
return fs.existsSync(path.resolve(PROJECT_ROOT, relative))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readTemplate(relativePath) {
|
|
51
|
+
return fs.readFileSync(path.resolve(PACKAGE_DIR, relativePath), "utf8")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getProjectPkg() {
|
|
55
|
+
const pkgPath = path.resolve(PROJECT_ROOT, "package.json")
|
|
56
|
+
if (!fs.existsSync(pkgPath)) return null
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
59
|
+
} catch {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getDependencyVersion(pkg, depName) {
|
|
65
|
+
return (
|
|
66
|
+
(pkg.dependencies && pkg.dependencies[depName]) ||
|
|
67
|
+
(pkg.devDependencies && pkg.devDependencies[depName]) ||
|
|
68
|
+
(pkg.peerDependencies && pkg.peerDependencies[depName]) ||
|
|
69
|
+
null
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- Detection helpers ---
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect if project is configured as ECMAScript Module (ESM)
|
|
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) {
|
|
87
|
+
if (!pkg) return null
|
|
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
|
|
160
|
+
const match = version.match(/(\d+)/)
|
|
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"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect CSS preprocessor in use
|
|
207
|
+
* Returns "scss", "less", "both", or "none"
|
|
208
|
+
*/
|
|
209
|
+
function detectCssPreprocessor() {
|
|
210
|
+
const pkg = getProjectPkg()
|
|
211
|
+
if (!pkg) return "scss"
|
|
212
|
+
const allDeps = {
|
|
213
|
+
...(pkg.dependencies || {}),
|
|
214
|
+
...(pkg.devDependencies || {}),
|
|
215
|
+
...(pkg.peerDependencies || {}),
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hasSass = allDeps["sass"] || allDeps["node-sass"] || allDeps["sass-loader"]
|
|
219
|
+
const hasLess = allDeps["less"] || allDeps["less-loader"]
|
|
220
|
+
|
|
221
|
+
if (hasSass && hasLess) return "both"
|
|
222
|
+
if (hasLess && !hasSass) return "less"
|
|
223
|
+
if (hasSass) return "scss"
|
|
224
|
+
|
|
225
|
+
// Fallback: check for existing stylelint config
|
|
226
|
+
if (fileExists(".stylelintrc.cjs") || fileExists(".stylelintrc.js")) {
|
|
227
|
+
try {
|
|
228
|
+
const stylelintFile = fileExists(".stylelintrc.cjs")
|
|
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"
|
|
237
|
+
} catch {}
|
|
238
|
+
return "scss"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// If not a Vue/uni-app project and no css preprocessor found, skip stylelint
|
|
242
|
+
if (!isVueProject()) {
|
|
243
|
+
return "none"
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return "scss" // default for Vue/uni-app projects
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// --- Config templates ---
|
|
250
|
+
|
|
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
|
+
}
|
|
258
|
+
|
|
259
|
+
return `// ESLint config — powered by my-code-style
|
|
260
|
+
// https://www.npmjs.com/package/my-code-style
|
|
261
|
+
const config = require("${importPath}")
|
|
262
|
+
|
|
263
|
+
// 如果需要引入 unplugin-auto-import 生成的 globals,取消下面的注释:
|
|
264
|
+
// config.extends = [...config.extends, "./.eslintrc-auto-import.json"]
|
|
265
|
+
|
|
266
|
+
module.exports = config
|
|
267
|
+
`
|
|
268
|
+
}
|
|
269
|
+
|
|
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
|
+
}
|
|
280
|
+
|
|
281
|
+
return `// ESLint Flat Config — powered by my-code-style
|
|
282
|
+
// https://www.npmjs.com/package/my-code-style
|
|
283
|
+
import ${varName} from "${importPath}"
|
|
284
|
+
|
|
285
|
+
export default [...${varName}]
|
|
286
|
+
`
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function prettierrcContent() {
|
|
290
|
+
return `// Prettier config — powered by my-code-style
|
|
291
|
+
module.exports = require("my-code-style/prettier")
|
|
292
|
+
`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function prettierignoreContent() {
|
|
296
|
+
return readTemplate("src/prettierignore")
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function stylelintrcContent(cssPreprocessor) {
|
|
300
|
+
const importPath =
|
|
301
|
+
cssPreprocessor === "less" ? "my-code-style/stylelint/less" : "my-code-style/stylelint"
|
|
302
|
+
|
|
303
|
+
return `// Stylelint config — powered by my-code-style
|
|
304
|
+
module.exports = require("${importPath}")
|
|
305
|
+
`
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function commitlintrcContent() {
|
|
309
|
+
return `// Commitlint config — powered by my-code-style
|
|
310
|
+
const base = require("my-code-style/commitlint")
|
|
311
|
+
const { generateScopes, guessCurrentScope } = require("my-code-style/commitlint/scopes")
|
|
312
|
+
|
|
313
|
+
const scopes = generateScopes("src")
|
|
314
|
+
const scopeComplete = guessCurrentScope()
|
|
315
|
+
|
|
316
|
+
module.exports = {
|
|
317
|
+
...base,
|
|
318
|
+
prompt: {
|
|
319
|
+
...base.prompt,
|
|
320
|
+
customScopesAlign: !scopeComplete ? "top" : "bottom",
|
|
321
|
+
defaultScope: scopeComplete,
|
|
322
|
+
scopes: [...scopes, "mock"],
|
|
323
|
+
allowEmptyIssuePrefixs: false,
|
|
324
|
+
allowCustomIssuePrefixs: false,
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
`
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function versionrcContent() {
|
|
331
|
+
return `// standard-version config — powered by my-code-style
|
|
332
|
+
module.exports = require("my-code-style/versionrc")
|
|
333
|
+
`
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function huskyCommitMsg() {
|
|
337
|
+
return `npx --no-install commitlint --edit "\${1}"\n`
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function huskyPreCommit() {
|
|
341
|
+
return `npx --no-install -- lint-staged\n`
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function editorconfigContent() {
|
|
345
|
+
return readTemplate("src/editorconfig")
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function gitattributesContent() {
|
|
349
|
+
return readTemplate("src/gitattributes")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function gitignoreContent() {
|
|
353
|
+
return readTemplate("src/gitignore")
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Generate the lint-staged config based on detected CSS preprocessor
|
|
358
|
+
*/
|
|
359
|
+
function getLintStagedConfig(cssPreprocessor) {
|
|
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"],
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return config
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// --- Main ---
|
|
387
|
+
|
|
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
|
+
|
|
405
|
+
// Handle --version / -v
|
|
406
|
+
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
|
407
|
+
const pkg = require("../package.json")
|
|
408
|
+
console.log(pkg.version)
|
|
409
|
+
return
|
|
410
|
+
}
|
|
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
|
+
|
|
440
|
+
const dryRun = process.argv.includes("--dry-run")
|
|
441
|
+
const label = dryRun ? "[DRY RUN] " : ""
|
|
442
|
+
|
|
443
|
+
console.log("")
|
|
444
|
+
console.log(" my-code-style-init — 初始化项目配置")
|
|
445
|
+
console.log("")
|
|
446
|
+
|
|
447
|
+
if (dryRun) {
|
|
448
|
+
warn("Dry run mode — no files will be written")
|
|
449
|
+
console.log("")
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// --- Detection phase ---
|
|
453
|
+
const eslintVersion = detectEslintVersion()
|
|
454
|
+
const cssPreprocessor = detectCssPreprocessor()
|
|
455
|
+
const projectType = detectProjectType()
|
|
456
|
+
|
|
457
|
+
// Determine ESLint format
|
|
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
|
|
495
|
+
const eslintFormat = useFlatConfig ? "flat" : "eslintrc"
|
|
496
|
+
|
|
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
|
+
)
|
|
504
|
+
console.log("")
|
|
505
|
+
|
|
506
|
+
// --- Build file list based on detection ---
|
|
507
|
+
const files = []
|
|
508
|
+
|
|
509
|
+
if (hasFlatConfigFile || hasLegacyConfigFile) {
|
|
510
|
+
log("保留已有 ESLint 配置,不生成额外入口")
|
|
511
|
+
} else if (useFlatConfig) {
|
|
512
|
+
files.push({
|
|
513
|
+
path: "eslint.config.mjs",
|
|
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),
|
|
520
|
+
})
|
|
521
|
+
} else {
|
|
522
|
+
files.push({
|
|
523
|
+
path: ".eslintrc.cjs",
|
|
524
|
+
exists: fileExists(".eslintrc.cjs"),
|
|
525
|
+
content: eslintrcContent(projectType),
|
|
526
|
+
})
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
files.push({
|
|
530
|
+
path: ".prettierrc.cjs",
|
|
531
|
+
exists: fileExists(".prettierrc.cjs"),
|
|
532
|
+
content: prettierrcContent(),
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
files.push({
|
|
536
|
+
path: ".prettierignore",
|
|
537
|
+
exists: fileExists(".prettierignore"),
|
|
538
|
+
content: prettierignoreContent(),
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
// Stylelint: always generate, but note if no CSS preprocessor
|
|
542
|
+
if (cssPreprocessor !== "none") {
|
|
543
|
+
files.push({
|
|
544
|
+
path: ".stylelintrc.cjs",
|
|
545
|
+
exists: fileExists(".stylelintrc.cjs"),
|
|
546
|
+
content: stylelintrcContent(cssPreprocessor),
|
|
547
|
+
})
|
|
548
|
+
} else {
|
|
549
|
+
log("跳过 Stylelint 配置 (未检测到 CSS 预处理器)")
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
files.push({
|
|
553
|
+
path: ".commitlintrc.cjs",
|
|
554
|
+
exists: fileExists(".commitlintrc.cjs"),
|
|
555
|
+
content: commitlintrcContent(),
|
|
556
|
+
})
|
|
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
|
+
|
|
568
|
+
files.push({
|
|
569
|
+
path: versionrcPath,
|
|
570
|
+
exists: fileExists(versionrcPath),
|
|
571
|
+
content: versionrcContent(),
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
files.push({
|
|
575
|
+
path: ".editorconfig",
|
|
576
|
+
exists: fileExists(".editorconfig"),
|
|
577
|
+
content: editorconfigContent(),
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
files.push({
|
|
581
|
+
path: ".gitattributes",
|
|
582
|
+
exists: fileExists(".gitattributes"),
|
|
583
|
+
content: gitattributesContent(),
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
if (!fileExists(".gitignore")) {
|
|
587
|
+
files.push({
|
|
588
|
+
path: ".gitignore",
|
|
589
|
+
exists: false,
|
|
590
|
+
content: gitignoreContent(),
|
|
591
|
+
})
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Show what will be created/overwritten
|
|
595
|
+
for (const f of files) {
|
|
596
|
+
if (f.exists) {
|
|
597
|
+
warn(`${label}覆盖 ${f.path}`)
|
|
598
|
+
} else {
|
|
599
|
+
success(`${label}创建 ${f.path}`)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Husky hooks
|
|
604
|
+
const huskyDir = path.resolve(PROJECT_ROOT, ".husky")
|
|
605
|
+
const hasHusky = fs.existsSync(huskyDir)
|
|
606
|
+
if (!hasHusky) {
|
|
607
|
+
success(`${label}创建 .husky/ 目录`)
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const hooks = [
|
|
611
|
+
{ path: ".husky/commit-msg", content: huskyCommitMsg() },
|
|
612
|
+
{ path: ".husky/pre-commit", content: huskyPreCommit() },
|
|
613
|
+
]
|
|
614
|
+
|
|
615
|
+
for (const h of hooks) {
|
|
616
|
+
if (fileExists(h.path)) {
|
|
617
|
+
warn(`${label}覆盖 ${h.path}`)
|
|
618
|
+
} else {
|
|
619
|
+
success(`${label}创建 ${h.path}`)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (dryRun) {
|
|
624
|
+
console.log("")
|
|
625
|
+
warn("Dry run 结束,未修改任何文件")
|
|
626
|
+
console.log("")
|
|
627
|
+
return
|
|
628
|
+
}
|
|
629
|
+
|
|
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
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
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 })
|
|
669
|
+
}
|
|
670
|
+
for (const h of hooks) {
|
|
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
|
+
}
|
|
758
|
+
|
|
759
|
+
// Check for missing peerDependencies
|
|
760
|
+
checkPeerDeps({ useFlatConfig, cssPreprocessor, projectType })
|
|
761
|
+
|
|
762
|
+
console.log("")
|
|
763
|
+
success("配置初始化完成!")
|
|
764
|
+
console.log("")
|
|
765
|
+
console.log(" 下一步:")
|
|
766
|
+
console.log(" 1. 确保已安装 peerDependencies:")
|
|
767
|
+
console.log(" pnpm add -D my-code-style")
|
|
768
|
+
if (useFlatConfig) {
|
|
769
|
+
console.log(" 2. Flat Config 需要的额外依赖:")
|
|
770
|
+
console.log(" pnpm add -D typescript-eslint globals eslint-plugin-import-x @eslint/js")
|
|
771
|
+
console.log(" 3. 初始化 husky:")
|
|
772
|
+
console.log(" pnpm prepare")
|
|
773
|
+
console.log(" 4. 使用 czg 提交 commit:")
|
|
774
|
+
} else {
|
|
775
|
+
console.log(" 2. 初始化 husky:")
|
|
776
|
+
console.log(" pnpm prepare")
|
|
777
|
+
console.log(" 3. 使用 czg 提交 commit:")
|
|
778
|
+
}
|
|
779
|
+
console.log(" pnpm cz")
|
|
780
|
+
console.log("")
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function modifyPackageJson(updates) {
|
|
784
|
+
const pkgPath = path.resolve(PROJECT_ROOT, "package.json")
|
|
785
|
+
let pkg = {}
|
|
786
|
+
if (fileExists("package.json")) {
|
|
787
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Merge scripts: updates won't overwrite existing scripts with the same key
|
|
791
|
+
if (updates.scripts) {
|
|
792
|
+
pkg.scripts = { ...updates.scripts, ...(pkg.scripts || {}) }
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Set lint-staged (replace existing lint-staged to avoid stale/duplicate patterns)
|
|
796
|
+
if (updates["lint-staged"]) {
|
|
797
|
+
pkg["lint-staged"] = updates["lint-staged"]
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n", "utf8")
|
|
801
|
+
}
|
|
802
|
+
|
|
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
|
+
|
|
816
|
+
const baseDeps = [
|
|
817
|
+
"eslint",
|
|
818
|
+
"prettier",
|
|
819
|
+
...(projectType !== "base" ? ["eslint-plugin-vue"] : []),
|
|
820
|
+
"@commitlint/cli",
|
|
821
|
+
"@commitlint/config-conventional",
|
|
822
|
+
"husky",
|
|
823
|
+
"lint-staged",
|
|
824
|
+
"czg",
|
|
825
|
+
"standard-version",
|
|
826
|
+
]
|
|
827
|
+
|
|
828
|
+
const flatDeps = useFlatConfig
|
|
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
|
+
: []
|
|
887
|
+
|
|
888
|
+
const allPeerDeps = [...baseDeps, ...flatDeps, ...cssDeps]
|
|
889
|
+
|
|
890
|
+
// Read peer dependency versions from my-code-style package.json
|
|
891
|
+
const packagePkg = JSON.parse(
|
|
892
|
+
fs.readFileSync(path.resolve(PACKAGE_DIR, "package.json"), "utf8"),
|
|
893
|
+
)
|
|
894
|
+
const peerDeps = packagePkg.peerDependencies || {}
|
|
895
|
+
|
|
896
|
+
const allDeps = {
|
|
897
|
+
...(pkg.dependencies || {}),
|
|
898
|
+
...(pkg.devDependencies || {}),
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const missing = allPeerDeps.filter((dep) => !allDeps[dep])
|
|
902
|
+
if (missing.length > 0) {
|
|
903
|
+
console.log("")
|
|
904
|
+
warn(`以下依赖未安装:${missing.join(", ")}`)
|
|
905
|
+
console.log("")
|
|
906
|
+
warn("一键安装命令:")
|
|
907
|
+
console.log(` pnpm add -D my-code-style \\`)
|
|
908
|
+
missing.forEach((dep, i) => {
|
|
909
|
+
const version =
|
|
910
|
+
dep === "eslint" ? (useFlatConfig ? "^9.0.0" : "^8.57.0") : peerDeps[dep] || ""
|
|
911
|
+
const depWithVersion = version ? `${dep}@${version}` : dep
|
|
912
|
+
const suffix = i < missing.length - 1 ? " \\" : ""
|
|
913
|
+
console.log(` ${depWithVersion}${suffix}`)
|
|
914
|
+
})
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
main()
|