my-code-style 1.2.0 → 1.5.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/package.json CHANGED
@@ -1,127 +1,228 @@
1
1
  {
2
- "name": "my-code-style",
3
- "version": "1.2.0",
4
- "description": "Lint/Format/Git 配置工程化 npm 包(ESLint + Prettier + Stylelint + Commitlint + Husky + standard-version)",
5
- "type": "commonjs",
6
- "main": "src/eslint/uniapp.cjs",
7
- "bin": {
8
- "my-code-style-init": "./bin/init"
9
- },
10
- "exports": {
11
- ".": "./src/eslint/uniapp.cjs",
12
- "./eslint": "./src/eslint/base.cjs",
13
- "./eslint/vue3": "./src/eslint/vue3.cjs",
14
- "./eslint/uniapp": "./src/eslint/uniapp.cjs",
15
- "./eslint/flat": "./src/eslint/flat/base.mjs",
16
- "./eslint/flat/vue3": "./src/eslint/flat/vue3.mjs",
17
- "./eslint/flat/uniapp": "./src/eslint/flat/uniapp.mjs",
18
- "./prettier": "./src/prettier/index.cjs",
19
- "./stylelint": "./src/stylelint/index.cjs",
20
- "./stylelint/less": "./src/stylelint/less.cjs",
21
- "./stylelint/less-override": "./src/stylelint/less-override.cjs",
22
- "./commitlint": "./src/commitlint/base.cjs",
23
- "./commitlint/scopes": "./src/commitlint/scopes.cjs",
24
- "./versionrc": "./src/versionrc/index.cjs"
25
- },
26
- "files": [
27
- "src",
28
- "bin",
29
- "README.md"
2
+ "name": "my-code-style",
3
+ "version": "1.5.0",
4
+ "description": "Lint/Format/Git 配置工程化 npm 包(ESLint + Prettier + Stylelint + Commitlint + Husky + standard-version)",
5
+ "type": "commonjs",
6
+ "main": "src/eslint/uniapp.cjs",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/myvn/codeStyle.git"
10
+ },
11
+ "homepage": "https://github.com/myvn/codeStyle#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/myvn/codeStyle/issues"
14
+ },
15
+ "bin": {
16
+ "my-code-style-init": "./bin/init"
17
+ },
18
+ "exports": {
19
+ ".": "./src/eslint/uniapp.cjs",
20
+ "./eslint": "./src/eslint/base.cjs",
21
+ "./eslint/vue3": "./src/eslint/vue3.cjs",
22
+ "./eslint/uniapp": "./src/eslint/uniapp.cjs",
23
+ "./eslint/flat": "./src/eslint/flat/base.mjs",
24
+ "./eslint/flat/vue3": "./src/eslint/flat/vue3.mjs",
25
+ "./eslint/flat/uniapp": "./src/eslint/flat/uniapp.mjs",
26
+ "./prettier": "./src/prettier/index.cjs",
27
+ "./stylelint": "./src/stylelint/index.cjs",
28
+ "./stylelint/less": "./src/stylelint/less.cjs",
29
+ "./stylelint/less-override": "./src/stylelint/less-override.cjs",
30
+ "./commitlint": "./src/commitlint/base.cjs",
31
+ "./commitlint/scopes": "./src/commitlint/scopes.cjs",
32
+ "./versionrc": "./src/versionrc/index.cjs"
33
+ },
34
+ "files": [
35
+ "src",
36
+ "bin",
37
+ "README.md",
38
+ "CHANGELOG.md"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "registry": "https://registry.npmjs.org/"
43
+ },
44
+ "scripts": {
45
+ "test": "node --test tests/*.test.cjs demo-test/*.test.cjs",
46
+ "test:demo": "node --test demo-test/*.test.cjs",
47
+ "test:integration:setup": "node demo-test/setup-integration.cjs",
48
+ "test:integration": "node --test demo-test/integration/*.test.cjs",
49
+ "test:legacy:setup": "node demo-test/setup-legacy.cjs",
50
+ "test:legacy": "node --test demo-test/legacy/*.test.cjs",
51
+ "test:all": "npm test && npm run test:integration && npm run test:legacy",
52
+ "lint": "eslint .",
53
+ "lint:fix": "eslint . --fix",
54
+ "format": "prettier --write .",
55
+ "prepare": "node -e \"if (!process.env.CI) { try { require('child_process').execSync('husky', { stdio: 'ignore' }) } catch {} }\"",
56
+ "release": "standard-version",
57
+ "release:push": "standard-version && git push --follow-tags origin HEAD",
58
+ "pub": "npm run release:push",
59
+ "cz": "czg",
60
+ "link": "pnpm link --global",
61
+ "unlink": "pnpm unlink --global",
62
+ "pack": "npm pack"
63
+ },
64
+ "engines": {
65
+ "node": ">=18"
66
+ },
67
+ "peerDependencies": {
68
+ "@commitlint/cli": "^19.0.0",
69
+ "@commitlint/config-conventional": "^19.0.0",
70
+ "@eslint/eslintrc": "^3.0.0",
71
+ "@eslint/js": "^9.0.0",
72
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
73
+ "@typescript-eslint/parser": "^8.0.0",
74
+ "czg": "^1.0.0",
75
+ "eslint": "^8.57.0 || ^9.0.0",
76
+ "eslint-config-prettier": "^10.1.8",
77
+ "eslint-import-resolver-typescript": "^3.0.0",
78
+ "eslint-plugin-import": "^2.0.0",
79
+ "eslint-plugin-import-x": "^4.0.0",
80
+ "eslint-plugin-prettier": "^5.2.0",
81
+ "eslint-plugin-vue": "^10.0.0",
82
+ "globals": "^16.0.0",
83
+ "husky": "^9.0.0",
84
+ "lint-staged": "^16.0.0",
85
+ "postcss-html": "^2.0.0",
86
+ "postcss-less": "^6.0.0",
87
+ "postcss-scss": "^4.0.0",
88
+ "prettier": "^3.0.0",
89
+ "standard-version": "^9.0.0",
90
+ "stylelint": "^16.0.0",
91
+ "stylelint-config-html": "^2.0.0",
92
+ "stylelint-config-recess-order": "^5.0.0",
93
+ "stylelint-config-recommended": "^17.0.0",
94
+ "stylelint-config-recommended-scss": "^16.0.0",
95
+ "stylelint-config-recommended-vue": "^2.0.0",
96
+ "stylelint-prettier": "^5.0.0",
97
+ "typescript": "^5.0.0",
98
+ "typescript-eslint": "^8.0.0",
99
+ "vue-eslint-parser": "^10.3.0"
100
+ },
101
+ "peerDependenciesMeta": {
102
+ "@commitlint/cli": {
103
+ "optional": true
104
+ },
105
+ "@commitlint/config-conventional": {
106
+ "optional": true
107
+ },
108
+ "@eslint/eslintrc": {
109
+ "optional": true
110
+ },
111
+ "@eslint/js": {
112
+ "optional": true
113
+ },
114
+ "@typescript-eslint/eslint-plugin": {
115
+ "optional": true
116
+ },
117
+ "@typescript-eslint/parser": {
118
+ "optional": true
119
+ },
120
+ "czg": {
121
+ "optional": true
122
+ },
123
+ "eslint": {
124
+ "optional": true
125
+ },
126
+ "eslint-config-prettier": {
127
+ "optional": true
128
+ },
129
+ "eslint-import-resolver-typescript": {
130
+ "optional": true
131
+ },
132
+ "eslint-plugin-import": {
133
+ "optional": true
134
+ },
135
+ "eslint-plugin-import-x": {
136
+ "optional": true
137
+ },
138
+ "eslint-plugin-prettier": {
139
+ "optional": true
140
+ },
141
+ "eslint-plugin-vue": {
142
+ "optional": true
143
+ },
144
+ "globals": {
145
+ "optional": true
146
+ },
147
+ "husky": {
148
+ "optional": true
149
+ },
150
+ "lint-staged": {
151
+ "optional": true
152
+ },
153
+ "postcss-html": {
154
+ "optional": true
155
+ },
156
+ "postcss-less": {
157
+ "optional": true
158
+ },
159
+ "postcss-scss": {
160
+ "optional": true
161
+ },
162
+ "prettier": {
163
+ "optional": true
164
+ },
165
+ "standard-version": {
166
+ "optional": true
167
+ },
168
+ "stylelint": {
169
+ "optional": true
170
+ },
171
+ "stylelint-config-html": {
172
+ "optional": true
173
+ },
174
+ "stylelint-config-recess-order": {
175
+ "optional": true
176
+ },
177
+ "stylelint-config-recommended": {
178
+ "optional": true
179
+ },
180
+ "stylelint-config-recommended-scss": {
181
+ "optional": true
182
+ },
183
+ "stylelint-config-recommended-vue": {
184
+ "optional": true
185
+ },
186
+ "stylelint-prettier": {
187
+ "optional": true
188
+ },
189
+ "typescript": {
190
+ "optional": true
191
+ },
192
+ "typescript-eslint": {
193
+ "optional": true
194
+ },
195
+ "vue-eslint-parser": {
196
+ "optional": true
197
+ }
198
+ },
199
+ "devDependencies": {
200
+ "standard-version": "^9.5.0"
201
+ },
202
+ "keywords": [
203
+ "eslint-config",
204
+ "prettier-config",
205
+ "stylelint-config",
206
+ "commitlint-config",
207
+ "vue",
208
+ "uni-app",
209
+ "typescript"
210
+ ],
211
+ "license": "MIT",
212
+ "lint-staged": {
213
+ "**/*.{vue,nvue}": [
214
+ "prettier --write",
215
+ "eslint --fix"
216
+ ],
217
+ "**/*.{js,ts,jsx,tsx,cjs,mjs,mts,cts}": [
218
+ "prettier --write",
219
+ "eslint --fix"
30
220
  ],
31
- "scripts": {
32
- "link": "pnpm link --global",
33
- "unlink": "pnpm unlink --global",
34
- "pack": "npm pack",
35
- "release": "standard-version"
36
- },
37
- "engines": {
38
- "node": ">=18"
39
- },
40
- "peerDependencies": {
41
- "@commitlint/cli": "^19.0.0",
42
- "@commitlint/config-conventional": "^19.0.0",
43
- "@eslint/eslintrc": "^3.0.0",
44
- "@eslint/js": "^9.0.0",
45
- "@typescript-eslint/eslint-plugin": "^8.0.0",
46
- "@typescript-eslint/parser": "^8.0.0",
47
- "czg": "^1.0.0",
48
- "eslint": "^9.0.0",
49
- "eslint-config-prettier": "^10.1.8",
50
- "eslint-import-resolver-typescript": "^3.0.0",
51
- "eslint-plugin-import": "^2.0.0",
52
- "eslint-plugin-import-x": "^4.0.0",
53
- "eslint-plugin-prettier": "^5.2.0",
54
- "eslint-plugin-vue": "^10.0.0",
55
- "globals": "^16.0.0",
56
- "husky": "^9.0.0",
57
- "lint-staged": "^16.0.0",
58
- "postcss-html": "^1.0.0",
59
- "postcss-less": "^6.0.0",
60
- "postcss-scss": "^4.0.0",
61
- "prettier": "^3.0.0",
62
- "standard-version": "^9.0.0",
63
- "stylelint": "^16.0.0",
64
- "stylelint-config-html": "^1.0.0",
65
- "stylelint-config-recess-order": "^5.0.0",
66
- "stylelint-config-recommended": "^17.0.0",
67
- "stylelint-config-recommended-scss": "^16.0.0",
68
- "stylelint-config-recommended-vue": "^2.0.0",
69
- "stylelint-prettier": "^5.0.0",
70
- "typescript-eslint": "^8.0.0",
71
- "vue-eslint-parser": "^9.0.0"
72
- },
73
- "peerDependenciesMeta": {
74
- "@eslint/eslintrc": {
75
- "optional": true
76
- },
77
- "@eslint/js": {
78
- "optional": true
79
- },
80
- "eslint-plugin-vue": {
81
- "optional": true
82
- },
83
- "eslint-plugin-import-x": {
84
- "optional": true
85
- },
86
- "globals": {
87
- "optional": true
88
- },
89
- "postcss-less": {
90
- "optional": true
91
- },
92
- "postcss-scss": {
93
- "optional": true
94
- },
95
- "postcss-html": {
96
- "optional": true
97
- },
98
- "stylelint-config-recommended-scss": {
99
- "optional": true
100
- },
101
- "stylelint-config-recommended-vue": {
102
- "optional": true
103
- },
104
- "stylelint-config-html": {
105
- "optional": true
106
- },
107
- "typescript-eslint": {
108
- "optional": true
109
- },
110
- "vue-eslint-parser": {
111
- "optional": true
112
- }
113
- },
114
- "devDependencies": {
115
- "standard-version": "^9.5.0"
116
- },
117
- "keywords": [
118
- "eslint-config",
119
- "prettier-config",
120
- "stylelint-config",
121
- "commitlint-config",
122
- "vue",
123
- "uni-app",
124
- "typescript"
221
+ "**/*.{html,css}": [
222
+ "prettier --write"
125
223
  ],
126
- "license": "MIT"
224
+ "**/*.{json,json5,md,yml,yaml}": [
225
+ "prettier --write"
226
+ ]
227
+ }
127
228
  }
@@ -1,6 +1,5 @@
1
1
  // Commitlint base config (without project-specific dynamic scopes)
2
2
  module.exports = {
3
- ignores: [(commit) => commit.includes("init")],
4
3
  extends: ["@commitlint/config-conventional"],
5
4
  rules: {
6
5
  "body-leading-blank": [2, "always"],
@@ -25,34 +25,88 @@ const PLURAL_MAP = {
25
25
  assets: "asset",
26
26
  }
27
27
 
28
+ // Plurals whose singular cannot be derived by rule: uncountables, irregulars,
29
+ // and the "-e" + "s" words that would otherwise lose their stem ("caches" →
30
+ // "cach"). Everything else is handled by the suffix rules below.
31
+ const EXACT_SINGULAR = {
32
+ news: "news",
33
+ series: "series",
34
+ movies: "movie",
35
+ cookies: "cookie",
36
+ caches: "cache",
37
+ chases: "chase",
38
+ niches: "niche",
39
+ }
40
+
41
+ // Directory names that already read as singular (or are conventional as-is)
42
+ const NON_PLURAL = [
43
+ "status",
44
+ "canvas",
45
+ "bus",
46
+ "pass",
47
+ "process",
48
+ "css",
49
+ "less",
50
+ "scss",
51
+ "js",
52
+ "ts",
53
+ "types",
54
+ ]
55
+
56
+ // Singulars that legitimately end in "s"/"z" and therefore take "-es":
57
+ // bus → buses, status → statuses. Without this list "buses" would follow the
58
+ // "-se + s" reading and become "buse".
59
+ const SIBILANT_SINGULARS = [
60
+ "bus",
61
+ "gas",
62
+ "canvas",
63
+ "alias",
64
+ "atlas",
65
+ "bias",
66
+ "virus",
67
+ "status",
68
+ "process",
69
+ "lens",
70
+ "plus",
71
+ "focus",
72
+ "campus",
73
+ "analysis",
74
+ "axis",
75
+ "basis",
76
+ "crisis",
77
+ "thesis",
78
+ "quiz",
79
+ ]
80
+
81
+ // Unambiguous "-es" plurals: the stem ends in a doubled sibilant or a digraph
82
+ // (classes, boxes, churches, dishes, buzzes, batches, fixes).
83
+ const ES_PLURAL = /(?:ss|zz|ch|sh|x)es$/i
84
+ // "-ses"/"-zes" are ambiguous: "cases" is case + s, "buses" is bus + es.
85
+ const S_OR_Z_ES_PLURAL = /[sz]es$/i
86
+
28
87
  function toSingular(name) {
29
88
  if (!name) {
30
89
  return name
31
90
  }
32
- if (PLURAL_MAP[name]) {
33
- return PLURAL_MAP[name]
91
+ const lower = name.toLowerCase()
92
+ if (PLURAL_MAP[lower]) {
93
+ return PLURAL_MAP[lower]
94
+ }
95
+ if (EXACT_SINGULAR[lower]) {
96
+ return EXACT_SINGULAR[lower]
97
+ }
98
+ if (NON_PLURAL.includes(lower)) {
99
+ return name
34
100
  }
35
- if (name.endsWith("ies") && name.length > 4) {
101
+ if (/ies$/i.test(name) && name.length > 4) {
36
102
  return name.slice(0, -3) + "y"
37
103
  }
38
- if (/(?:[sxz]|[ch|sh])es$/.test(name)) {
104
+ if (ES_PLURAL.test(name)) {
39
105
  return name.slice(0, -2)
40
106
  }
41
- const nonPluralEndingInS = [
42
- "status",
43
- "canvas",
44
- "bus",
45
- "pass",
46
- "process",
47
- "css",
48
- "less",
49
- "scss",
50
- "js",
51
- "ts",
52
- "types",
53
- ]
54
- if (nonPluralEndingInS.includes(name.toLowerCase())) {
55
- return name
107
+ if (S_OR_Z_ES_PLURAL.test(name)) {
108
+ const esStem = name.slice(0, -2).toLowerCase()
109
+ return SIBILANT_SINGULARS.includes(esStem) ? name.slice(0, -2) : name.slice(0, -1)
56
110
  }
57
111
  if (name.endsWith("s") && !name.endsWith("ss") && name.length > 3) {
58
112
  return name.slice(0, -1)
@@ -71,11 +125,15 @@ function generateScopes(srcDirs = "src") {
71
125
 
72
126
  for (const srcDir of dirs) {
73
127
  const resolved = path.resolve(process.cwd(), srcDir)
74
- if (!fs.existsSync(resolved)) {
128
+ let entries
129
+ try {
130
+ // A missing path, a plain file, or an unreadable directory must not
131
+ // break the commitlint config load — just yield no scopes.
132
+ entries = fs.readdirSync(resolved, { withFileTypes: true })
133
+ } catch {
75
134
  continue
76
135
  }
77
- const scopes = fs
78
- .readdirSync(resolved, { withFileTypes: true })
136
+ const scopes = entries
79
137
  .filter((dirent) => dirent.isDirectory())
80
138
  .map((dirent) => toSingular(dirent.name))
81
139
  allScopes.push(...scopes)
@@ -84,19 +142,130 @@ function generateScopes(srcDirs = "src") {
84
142
  return [...new Set(allScopes)]
85
143
  }
86
144
 
145
+ function getStagedFiles(cwd) {
146
+ try {
147
+ const output = execSync("git diff --cached --name-only -z", {
148
+ cwd,
149
+ stdio: ["pipe", "pipe", "ignore"],
150
+ }).toString()
151
+ return output.split("\0").filter(Boolean)
152
+ } catch {
153
+ try {
154
+ const output = execSync("git status --porcelain -z", {
155
+ cwd,
156
+ stdio: ["pipe", "pipe", "ignore"],
157
+ }).toString()
158
+ const parts = output.split("\0")
159
+ const staged = []
160
+ for (let i = 0; i < parts.length; i++) {
161
+ const entry = parts[i]
162
+ if (!entry) continue
163
+ const x = entry[0]
164
+ const isRenameOrCopy = x === "R" || x === "C"
165
+ const filePath = entry.slice(3)
166
+ if (x !== " " && x !== "?" && x !== "!") {
167
+ if (filePath) staged.push(filePath)
168
+ }
169
+ if (isRenameOrCopy) {
170
+ i++
171
+ }
172
+ }
173
+ return staged
174
+ } catch {
175
+ return []
176
+ }
177
+ }
178
+ }
179
+
87
180
  /**
88
- * Guess the current scope from git status (modified files under src/)
89
- * @returns {string|undefined}
181
+ * Guess the current scope from staged files in the git index.
182
+ * Only staged files are considered (unstaged and untracked changes are ignored).
183
+ * Multi-directory strategy: votes by the number of staged changes under each candidate scope directory;
184
+ * in case of a tie, the first encountered scope among staged files is chosen.
185
+ *
186
+ * @param {string|string[]} [srcDirs="src"] - Source directory name(s) relative to cwd
187
+ * @returns {string|undefined} Guessed scope name, or undefined if none matched
90
188
  */
91
- function guessCurrentScope() {
189
+ function guessCurrentScope(srcDirs = "src") {
92
190
  try {
93
- const output = execSync("git status --porcelain || true").toString().trim()
94
- const line = output.split("\n").find((r) => r.includes("M src"))
95
- if (!line) {
191
+ const cwd = process.cwd()
192
+ const rawPrefix = execSync("git rev-parse --show-prefix", {
193
+ cwd,
194
+ stdio: ["pipe", "pipe", "ignore"],
195
+ })
196
+ .toString()
197
+ .trim()
198
+ const prefix = rawPrefix.replace(/\\/g, "/")
199
+
200
+ const stagedFiles = getStagedFiles(cwd)
201
+ if (!stagedFiles || stagedFiles.length === 0) {
96
202
  return undefined
97
203
  }
98
- const rawName = line.replace(/\//g, "%%").match(/src%%((\w|-)*)/)?.[1]
99
- return toSingular(rawName)
204
+
205
+ const normalizedDirs = (Array.isArray(srcDirs) ? srcDirs : [srcDirs])
206
+ .map((d) => d.replace(/\\/g, "/").replace(/^\/+|\/+$/g, ""))
207
+ .filter(Boolean)
208
+
209
+ const candidateScopes = []
210
+
211
+ for (const file of stagedFiles) {
212
+ const normalizedFile = file.replace(/\\/g, "/")
213
+ if (prefix && !normalizedFile.startsWith(prefix)) {
214
+ continue
215
+ }
216
+ const relativeToCwd = prefix ? normalizedFile.slice(prefix.length) : normalizedFile
217
+
218
+ for (const srcDir of normalizedDirs) {
219
+ const dirPrefix = srcDir ? `${srcDir}/` : ""
220
+ if (relativeToCwd.startsWith(dirPrefix)) {
221
+ const subPath = relativeToCwd.slice(dirPrefix.length)
222
+ const segments = subPath.split("/").filter(Boolean)
223
+ // Must have at least 2 segments (directory + file) to belong to a scope directory
224
+ if (segments.length >= 2) {
225
+ const rawName = segments[0]
226
+ const resolvedDir = path.resolve(cwd, srcDir, rawName)
227
+ if (fs.existsSync(resolvedDir)) {
228
+ if (fs.statSync(resolvedDir).isDirectory()) {
229
+ candidateScopes.push(toSingular(rawName))
230
+ break
231
+ }
232
+ } else {
233
+ // If deleted on disk, having segments >= 2 indicates it was a directory in git
234
+ candidateScopes.push(toSingular(rawName))
235
+ break
236
+ }
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ if (candidateScopes.length === 0) {
243
+ return undefined
244
+ }
245
+
246
+ // Multi-directory selection strategy: vote by frequency; first encountered breaks ties
247
+ const counts = new Map()
248
+ const order = []
249
+
250
+ for (const scope of candidateScopes) {
251
+ if (!counts.has(scope)) {
252
+ counts.set(scope, 0)
253
+ order.push(scope)
254
+ }
255
+ counts.set(scope, counts.get(scope) + 1)
256
+ }
257
+
258
+ let bestScope = undefined
259
+ let maxCount = 0
260
+ for (const scope of order) {
261
+ const count = counts.get(scope)
262
+ if (count > maxCount) {
263
+ maxCount = count
264
+ bestScope = scope
265
+ }
266
+ }
267
+
268
+ return bestScope
100
269
  } catch {
101
270
  return undefined
102
271
  }