feima-shortcuts 0.2.3 → 0.3.0-beta.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/bin/feima.js CHANGED
@@ -1,8 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  const inquirer = require("inquirer");
3
+ const { Command } = require('commander')
3
4
  const feima = require("../src/generate");
4
5
  const { version } = require("../package.json"); // 读取 package.json 中的版本号
5
6
 
7
+ const program = new Command()
8
+
9
+ program
10
+ .name('feima')
11
+ .version(version)
12
+
13
+ program
14
+ .command('check-locale')
15
+ .description('检查未使用的 locale')
16
+ .action(() => {
17
+ require('../src/scripts/check-locale')
18
+ })
19
+
20
+
6
21
  const run = () => {
7
22
  console.log(`🚀 当前版本:v${version}`);
8
23
 
@@ -81,4 +96,9 @@ const run = () => {
81
96
  });
82
97
  };
83
98
 
84
- run();
99
+ if (process.argv.length <= 2) {
100
+ // 等价于你之前 bin/feima.js 的全部内容
101
+ run();
102
+ } else {
103
+ program.parse(process.argv)
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feima-shortcuts",
3
- "version": "0.2.3",
3
+ "version": "0.3.0-beta.0",
4
4
  "description": "快捷指令",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+ const glob = require('glob')
5
+ const ts = require('typescript')
6
+
7
+ /* 工具函数,保持你原逻辑 */
8
+ function flattenLocale(obj, prefix = '') {
9
+ let res = []
10
+ for (const k in obj) {
11
+ const key = prefix ? `${prefix}.${k}` : k
12
+ if (typeof obj[k] === 'object' && obj[k] !== null) {
13
+ res = res.concat(flattenLocale(obj[k], key))
14
+ } else {
15
+ res.push(key)
16
+ }
17
+ }
18
+ return res
19
+ }
20
+
21
+ function loadLocaleKeys(localePath) {
22
+ const code = fs.readFileSync(localePath, 'utf-8')
23
+ const sf = ts.createSourceFile(localePath, code, ts.ScriptTarget.Latest, true)
24
+
25
+ let obj = null
26
+
27
+ function visit(node) {
28
+ if (
29
+ ts.isExportAssignment(node) &&
30
+ ts.isObjectLiteralExpression(node.expression)
31
+ ) {
32
+ obj = eval(`(${node.expression.getText()})`)
33
+ }
34
+ ts.forEachChild(node, visit)
35
+ }
36
+
37
+ visit(sf)
38
+ return obj ? flattenLocale(obj) : []
39
+ }
40
+
41
+ function extractUsedKeys(code) {
42
+ const reg = /t\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g
43
+ const set = new Set()
44
+ let m
45
+ while ((m = reg.exec(code))) set.add(m[1])
46
+ return set
47
+ }
48
+
49
+ function detectLocaleImport(code) {
50
+ const res = {
51
+ correct: false,
52
+ invalid: [],
53
+ external: [],
54
+ }
55
+
56
+ const reg = /import\s+locale\s+from\s+['"]([^'"]+)['"]/g
57
+ let m
58
+
59
+ while ((m = reg.exec(code))) {
60
+ const src = m[1]
61
+ if (src === './locale') {
62
+ res.correct = true
63
+ } else if (src.startsWith('../')) {
64
+ res.invalid.push(src)
65
+ } else {
66
+ res.external.push(src)
67
+ }
68
+ }
69
+
70
+ return res
71
+ }
72
+
73
+ function detectUseLocale(code) {
74
+ return {
75
+ local: /useLocale\s*\(\s*\{\s*locale\s*\}\s*\)/.test(code),
76
+ global: /useLocale\s*\(\s*\)/.test(code),
77
+ }
78
+ }
79
+
80
+ /* 页面 locale 检测 */
81
+ function checkPageLocale(pageDir) {
82
+ const localePath = path.join(pageDir, 'locale.ts')
83
+ const indexVue = path.join(pageDir, 'index.vue')
84
+
85
+ if (!fs.existsSync(localePath) || !fs.existsSync(indexVue)) return false
86
+
87
+ const code = fs.readFileSync(indexVue, 'utf-8')
88
+ const localeImport = detectLocaleImport(code)
89
+ const useLocale = detectUseLocale(code)
90
+
91
+ const warnings = []
92
+
93
+ if (localeImport.invalid.length) {
94
+ warnings.push('使用了 ../locale')
95
+ }
96
+ if (!localeImport.correct) {
97
+ warnings.push('未使用 ./locale')
98
+ }
99
+ if (!useLocale.local) {
100
+ warnings.push('未使用 useLocale({ locale })')
101
+ }
102
+
103
+ if (warnings.length) {
104
+ console.log(`\n⚠️ 页面存在不规范 locale 用法`)
105
+ console.log(` ${indexVue}`)
106
+ warnings.forEach((w) => console.log(` - ${w}`))
107
+ return false
108
+ }
109
+
110
+ const localeKeys = loadLocaleKeys(localePath)
111
+ const usedKeys = extractUsedKeys(code)
112
+ const unused = localeKeys.filter((k) => !usedKeys.has(k))
113
+
114
+ if (unused.length) {
115
+ console.log(`\n❌ 页面未使用 locale key`)
116
+ console.log(` ${localePath}`)
117
+ unused.forEach((k) => console.log(` - ${k}`))
118
+ return true
119
+ }
120
+
121
+ return false
122
+ }
123
+
124
+ /* 组件 locale 检测 */
125
+ function checkComponentLocale(componentDir) {
126
+ const localePath = path.join(componentDir, 'locale.ts')
127
+ if (!fs.existsSync(localePath)) return false
128
+
129
+ const vueFiles = glob.sync('**/*.vue', {
130
+ cwd: componentDir,
131
+ absolute: true,
132
+ })
133
+
134
+ const localeKeys = loadLocaleKeys(localePath)
135
+ const usedKeys = new Set()
136
+
137
+ const invalidFiles = []
138
+ const skippedFiles = []
139
+
140
+ vueFiles.forEach((file) => {
141
+ const code = fs.readFileSync(file, 'utf-8')
142
+ const localeImport = detectLocaleImport(code)
143
+ const useLocale = detectUseLocale(code)
144
+
145
+ if (localeImport.invalid.length) {
146
+ invalidFiles.push({ file, src: localeImport.invalid })
147
+ return
148
+ }
149
+
150
+ if (!localeImport.correct || !useLocale.local) {
151
+ skippedFiles.push(file)
152
+ return
153
+ }
154
+
155
+ extractUsedKeys(code).forEach((k) => usedKeys.add(k))
156
+ })
157
+
158
+ let hasError = false
159
+
160
+ const unused = localeKeys.filter((k) => !usedKeys.has(k))
161
+ if (unused.length) {
162
+ hasError = true
163
+ console.log(`\n❌ 组件未使用 locale key`)
164
+ console.log(` ${localePath}`)
165
+ unused.forEach((k) => console.log(` - ${k}`))
166
+ }
167
+
168
+ if (invalidFiles.length) {
169
+ hasError = true
170
+ console.log(`\n🚫 组件存在错误的 locale 引入方式`)
171
+ invalidFiles.forEach((f) => {
172
+ console.log(` ${path.relative(process.cwd(), f.file)}`)
173
+ f.src.forEach((s) => console.log(` import locale from '${s}'`))
174
+ })
175
+ }
176
+
177
+ if (skippedFiles.length) {
178
+ console.log(`\n⚠️ 组件中以下文件未绑定本地 locale(已跳过)`)
179
+ skippedFiles.forEach((f) =>
180
+ console.log(` ${path.relative(process.cwd(), f)}`)
181
+ )
182
+ }
183
+
184
+ return hasError
185
+ }
186
+
187
+ /* 主入口 */
188
+
189
+ function run() {
190
+ const root = path.resolve(process.cwd(), 'src/views')
191
+ let hasError = false
192
+
193
+ // 页面
194
+ glob.sync('**/index.vue', { cwd: root }).forEach((p) => {
195
+ const dir = path.join(root, path.dirname(p))
196
+ if (checkPageLocale(dir)) hasError = true
197
+ })
198
+
199
+ // 组件
200
+ glob.sync('**/components/**/locale.ts', { cwd: root }).forEach((p) => {
201
+ const dir = path.join(root, path.dirname(p))
202
+ if (checkComponentLocale(dir)) hasError = true
203
+ })
204
+
205
+ if (hasError) {
206
+ console.log('\n❗ locale 治理未通过')
207
+ process.exit(1)
208
+ } else {
209
+ console.log('\n✅ locale 治理通过')
210
+ }
211
+ }
212
+
213
+ if (require.main === module) {
214
+ run()
215
+ }
216
+
217
+ module.exports = run
@@ -34,15 +34,10 @@ function splitString(input) {
34
34
  return { path, functionName };
35
35
  }
36
36
 
37
- const isFunctionExists = (data, functionName) => {
38
- const regex = new RegExp(`export\\s+const\\s+${functionName}\\s*=`, 'g');
39
- return regex.test(data);
40
- };
41
-
42
37
  const fileData = (answers, functionName) => {
43
38
  if (fs.existsSync(`./${fileName}`)) {
44
39
  const data = fs.readFileSync(`./${fileName}`, "utf8");
45
- if (isFunctionExists(data, functionName)) {
40
+ if (data.includes(`export const ${functionName}: any`)) {
46
41
  return "";
47
42
  }
48
43
  return data;