@agile-team/wl-skills-kit 2.4.1 → 2.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.
@@ -1,228 +1,440 @@
1
- 'use strict'
1
+ "use strict";
2
2
 
3
- const fs = require('fs')
4
- const path = require('path')
5
- const { execFileSync } = require('child_process')
6
- const https = require('https')
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { execFileSync } = require("child_process");
6
+ const https = require("https");
7
7
 
8
8
  function getProjectRoot() {
9
- return process.env.WL_PROJECT_ROOT ? path.resolve(process.env.WL_PROJECT_ROOT) : process.cwd()
9
+ return process.env.WL_PROJECT_ROOT
10
+ ? path.resolve(process.env.WL_PROJECT_ROOT)
11
+ : process.cwd();
10
12
  }
11
13
 
12
14
  function normalizePath(p) {
13
- return p.replace(/\\/g, '/')
15
+ return p.replace(/\\/g, "/");
14
16
  }
15
17
 
16
18
  function safeResolve(root, inputPath) {
17
- const full = inputPath ? path.resolve(root, inputPath) : root
19
+ const full = inputPath ? path.resolve(root, inputPath) : root;
18
20
  if (full !== root && !full.startsWith(root + path.sep)) {
19
- throw new Error('路径越界:只能扫描项目根目录内的文件')
21
+ throw new Error("路径越界:只能扫描项目根目录内的文件");
20
22
  }
21
- return full
23
+ return full;
22
24
  }
23
25
 
24
26
  function walkFiles(dir, baseDir, files) {
25
- files = files || []
26
- if (!fs.existsSync(dir)) return files
27
- const entries = fs.readdirSync(dir, { withFileTypes: true })
27
+ files = files || [];
28
+ if (!fs.existsSync(dir)) return files;
29
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
28
30
  for (const entry of entries) {
29
- if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue
30
- const full = path.join(dir, entry.name)
31
- if (entry.isDirectory()) walkFiles(full, baseDir, files)
32
- else files.push(normalizePath(path.relative(baseDir, full)))
31
+ if (
32
+ entry.name === "node_modules" ||
33
+ entry.name === ".git" ||
34
+ entry.name === "dist"
35
+ )
36
+ continue;
37
+ const full = path.join(dir, entry.name);
38
+ if (entry.isDirectory()) walkFiles(full, baseDir, files);
39
+ else files.push(normalizePath(path.relative(baseDir, full)));
33
40
  }
34
- return files
41
+ return files;
35
42
  }
36
43
 
37
44
  function findPageDirs(root, scanRel) {
38
- const scanDir = safeResolve(root, scanRel || 'src/views')
39
- const files = walkFiles(scanDir, root)
40
- const dirs = new Map()
45
+ const scanDir = safeResolve(root, scanRel || "src/views");
46
+ const files = walkFiles(scanDir, root);
47
+ const dirs = new Map();
41
48
  for (const rel of files) {
42
- const name = path.basename(rel)
43
- const dir = normalizePath(path.dirname(rel))
44
- if (!dirs.has(dir)) dirs.set(dir, new Set())
45
- dirs.get(dir).add(name)
49
+ const name = path.basename(rel);
50
+ const dir = normalizePath(path.dirname(rel));
51
+ if (!dirs.has(dir)) dirs.set(dir, new Set());
52
+ dirs.get(dir).add(name);
46
53
  }
47
- const pages = []
54
+ const pages = [];
48
55
  for (const [dir, names] of dirs.entries()) {
49
- if (!names.has('index.vue')) continue
50
- const dataPath = path.join(root, dir, 'data.ts')
51
- let apiConfigCount = 0
56
+ if (!names.has("index.vue")) continue;
57
+ const dataPath = path.join(root, dir, "data.ts");
58
+ let apiConfigCount = 0;
52
59
  if (fs.existsSync(dataPath)) {
53
- const content = fs.readFileSync(dataPath, 'utf8')
54
- apiConfigCount = (content.match(/API_CONFIG/g) || []).length
60
+ const content = fs.readFileSync(dataPath, "utf8");
61
+ apiConfigCount = (content.match(/API_CONFIG/g) || []).length;
55
62
  }
56
63
  pages.push({
57
64
  dir,
58
- hasIndexVue: names.has('index.vue'),
59
- hasDataTs: names.has('data.ts'),
60
- hasIndexScss: names.has('index.scss'),
61
- hasApiMd: names.has('api.md'),
65
+ hasIndexVue: names.has("index.vue"),
66
+ hasDataTs: names.has("data.ts"),
67
+ hasIndexScss: names.has("index.scss"),
68
+ hasApiMd: names.has("api.md"),
62
69
  apiConfigCount,
63
- })
70
+ });
64
71
  }
65
- pages.sort((a, b) => a.dir.localeCompare(b.dir))
66
- return pages
72
+ pages.sort((a, b) => a.dir.localeCompare(b.dir));
73
+ return pages;
67
74
  }
68
75
 
69
76
  function formatPagesTable(pages) {
70
- const lines = ['| 页面目录 | index.vue | data.ts | index.scss | api.md | API_CONFIG |', '|---|---:|---:|---:|---:|---:|']
77
+ const lines = [
78
+ "| 页面目录 | index.vue | data.ts | index.scss | api.md | API_CONFIG |",
79
+ "|---|---:|---:|---:|---:|---:|",
80
+ ];
71
81
  for (const page of pages) {
72
82
  lines.push(
73
- `| ${page.dir} | ${page.hasIndexVue ? '' : ''} | ${page.hasDataTs ? '' : ''} | ${page.hasIndexScss ? '' : ''} | ${page.hasApiMd ? '' : ''} | ${page.apiConfigCount} |`
74
- )
83
+ `| ${page.dir} | ${page.hasIndexVue ? "" : ""} | ${page.hasDataTs ? "" : ""} | ${page.hasIndexScss ? "" : ""} | ${page.hasApiMd ? "" : ""} | ${page.apiConfigCount} |`,
84
+ );
75
85
  }
76
- return lines.join('\n')
86
+ return lines.join("\n");
77
87
  }
78
88
 
79
89
  async function handleCodeScan(args) {
80
- const root = getProjectRoot()
81
- const scanPath = args && args.path ? args.path : 'src/views'
82
- const pages = findPageDirs(root, scanPath)
83
- const missingData = pages.filter((p) => !p.hasDataTs).length
84
- const missingScss = pages.filter((p) => !p.hasIndexScss).length
85
- const missingApi = pages.filter((p) => !p.hasApiMd).length
86
- const apiPages = pages.filter((p) => p.apiConfigCount > 0).length
90
+ const root = getProjectRoot();
91
+ const scanPath = args && args.path ? args.path : "src/views";
92
+ const pages = findPageDirs(root, scanPath);
93
+ const missingData = pages.filter((p) => !p.hasDataTs).length;
94
+ const missingScss = pages.filter((p) => !p.hasIndexScss).length;
95
+ const missingApi = pages.filter((p) => !p.hasApiMd).length;
96
+ const apiPages = pages.filter((p) => p.apiConfigCount > 0).length;
87
97
 
88
98
  if (pages.length === 0) {
89
- return `⚠️ 未在 ${scanPath} 下发现包含 index.vue 的页面目录`
99
+ return `⚠️ 未在 ${scanPath} 下发现包含 index.vue 的页面目录`;
90
100
  }
91
101
 
92
102
  return [
93
103
  `✅ 代码结构扫描完成:${scanPath}`,
94
- '',
104
+ "",
95
105
  `- 页面目录:${pages.length}`,
96
106
  `- 含 API_CONFIG:${apiPages}`,
97
107
  `- 缺 data.ts:${missingData}`,
98
108
  `- 缺 index.scss:${missingScss}`,
99
109
  `- 缺 api.md:${missingApi}(需结合场景判断是否必须)`,
100
- '',
110
+ "",
101
111
  formatPagesTable(pages),
102
- ].join('\n')
112
+ ].join("\n");
113
+ }
114
+
115
+ function readPackageDeps(root) {
116
+ const pkgPath = path.join(root, "package.json");
117
+ if (!fs.existsSync(pkgPath)) return {};
118
+ try {
119
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
120
+ return { ...pkg.dependencies, ...pkg.devDependencies };
121
+ } catch {
122
+ return {};
123
+ }
124
+ }
125
+
126
+ function findMockFiles(root) {
127
+ const mockDir = path.join(root, "mock");
128
+ if (!fs.existsSync(mockDir)) return [];
129
+ return walkFiles(mockDir, root).filter((rel) => /\.(ts|js)$/.test(rel));
130
+ }
131
+
132
+ async function handleValidatePage(args) {
133
+ const root = getProjectRoot();
134
+ const scanPath = args && args.path ? args.path : "src/views";
135
+ const pages = findPageDirs(root, scanPath);
136
+ const mockFiles = findMockFiles(root);
137
+ const mockContent = mockFiles
138
+ .map((rel) => fs.readFileSync(path.join(root, rel), "utf8"))
139
+ .join("\n");
140
+ const issues = [];
141
+
142
+ for (const page of pages) {
143
+ const indexPath = path.join(root, page.dir, "index.vue");
144
+ const dataPath = path.join(root, page.dir, "data.ts");
145
+ const indexContent = fs.existsSync(indexPath)
146
+ ? fs.readFileSync(indexPath, "utf8")
147
+ : "";
148
+ const dataContent = fs.existsSync(dataPath)
149
+ ? fs.readFileSync(dataPath, "utf8")
150
+ : "";
151
+ const baseTableCount = (indexContent.match(/<BaseTable\b/g) || []).length;
152
+ const agGridCount = (
153
+ indexContent.match(/render-type=["']agGrid["']/g) || []
154
+ ).length;
155
+ const cidCount = (indexContent.match(/\bcid=|:cid=/g) || []).length;
156
+
157
+ if (!page.hasDataTs) issues.push([page.dir, "warn", "缺 data.ts"]);
158
+ if (!page.hasIndexScss) issues.push([page.dir, "warn", "缺 index.scss"]);
159
+ if (page.apiConfigCount > 0 && !page.hasApiMd)
160
+ issues.push([page.dir, "warn", "检测到 API_CONFIG 但缺 api.md"]);
161
+ if (baseTableCount > 0 && agGridCount < baseTableCount)
162
+ issues.push([page.dir, "error", 'BaseTable 必须 render-type="agGrid"']);
163
+ if (baseTableCount > 0 && cidCount < baseTableCount)
164
+ issues.push([page.dir, "error", "BaseTable 必须配置 cid/:cid"]);
165
+ if (
166
+ baseTableCount > 0 &&
167
+ dataContent &&
168
+ !/defineColumns\s*\(/.test(dataContent)
169
+ )
170
+ issues.push([page.dir, "error", "列定义必须使用 defineColumns()"]);
171
+ if (/operations\s*:/.test(dataContent))
172
+ issues.push([
173
+ page.dir,
174
+ "error",
175
+ "禁止 operations 数组,必须使用 renderOps()",
176
+ ]);
177
+ if (/onClick\s*:\s*\(\s*[^)]*\s*\)\s*=>\s*\{\s*\}/.test(dataContent))
178
+ issues.push([page.dir, "error", "存在空 onClick"]);
179
+ if (page.apiConfigCount > 0 && mockFiles.length === 0)
180
+ issues.push([page.dir, "warn", "检测到 API_CONFIG 但无 mock 文件"]);
181
+ const apiUrls = Array.from(
182
+ dataContent.matchAll(/:\s*["']([^"']+\/[^"']+)["']/g),
183
+ ).map((m) => m[1]);
184
+ for (const url of apiUrls.filter((item) => item.startsWith("/"))) {
185
+ const mockUrl = `/dev-api${url}`;
186
+ if (mockContent && !mockContent.includes(mockUrl))
187
+ issues.push([page.dir, "warn", `mock 未发现端点 ${mockUrl}`]);
188
+ }
189
+ }
190
+
191
+ const errors = issues.filter((item) => item[1] === "error").length;
192
+ const lines = [
193
+ `✅ 页面校验完成:${scanPath}`,
194
+ "",
195
+ `- 页面目录:${pages.length}`,
196
+ `- error:${errors}`,
197
+ `- warn:${issues.length - errors}`,
198
+ "",
199
+ ];
200
+ if (issues.length === 0) lines.push("✔ 未发现偏差");
201
+ else {
202
+ lines.push("| 页面目录 | 级别 | 问题 |", "|---|---|---|");
203
+ for (const [dir, level, text] of issues)
204
+ lines.push(`| ${dir} | ${level} | ${text} |`);
205
+ }
206
+ return lines.join("\n");
207
+ }
208
+
209
+ async function handleDoctorUi() {
210
+ const root = getProjectRoot();
211
+ const deps = readPackageDeps(root);
212
+ const files = walkFiles(root, root).filter(
213
+ (rel) =>
214
+ /\.(ts|vue|scss|html)$/.test(rel) && !rel.startsWith("node_modules/"),
215
+ );
216
+ const content = files
217
+ .map((rel) => fs.readFileSync(path.join(root, rel), "utf8"))
218
+ .join("\n");
219
+ const checks = [
220
+ [
221
+ "@agile-team/wk-skills-ui",
222
+ Boolean(deps["@agile-team/wk-skills-ui"]),
223
+ deps["@agile-team/wk-skills-ui"] || "未安装",
224
+ ],
225
+ [
226
+ "@element-plus/icons-vue",
227
+ Boolean(deps["@element-plus/icons-vue"]),
228
+ deps["@element-plus/icons-vue"] || "未安装",
229
+ ],
230
+ [
231
+ "design tokens",
232
+ /@agile-team\/wk-skills-ui\/design\/tokens|dist\/tokens\.css/.test(
233
+ content,
234
+ ),
235
+ "tokens 引入",
236
+ ],
237
+ [
238
+ "styles preset",
239
+ /@agile-team\/wk-skills-ui\/styles/.test(content),
240
+ "styles/skin preset 引入",
241
+ ],
242
+ [
243
+ "installCommonPreset",
244
+ /installCommonPreset\s*\(/.test(content),
245
+ "runtime preset 安装",
246
+ ],
247
+ ["defineColumns", /defineColumns\s*\(/.test(content), "列定义 runtime"],
248
+ ["renderOps", /renderOps\s*\(/.test(content), "操作列 runtime"],
249
+ ];
250
+ const lines = [
251
+ "✅ wk-skills-ui 接入检查",
252
+ "",
253
+ "| 检查项 | 状态 | 说明 |",
254
+ "|---|---:|---|",
255
+ ];
256
+ for (const [name, ok, detail] of checks)
257
+ lines.push(`| ${name} | ${ok ? "✅" : "❌"} | ${detail} |`);
258
+ return lines.join("\n");
103
259
  }
104
260
 
105
261
  function findRouteFile(root, inputPath) {
106
262
  if (inputPath) {
107
- const full = safeResolve(root, inputPath)
108
- return fs.existsSync(full) ? full : null
263
+ const full = safeResolve(root, inputPath);
264
+ return fs.existsSync(full) ? full : null;
109
265
  }
110
266
  const candidates = [
111
- 'vite/plugins/shared/pages.ts',
112
- 'src/router/pages.ts',
113
- 'src/router/routes.ts',
114
- 'src/router/index.ts',
115
- ]
267
+ "vite/plugins/shared/pages.ts",
268
+ "src/router/pages.ts",
269
+ "src/router/routes.ts",
270
+ "src/router/index.ts",
271
+ ];
116
272
  for (const rel of candidates) {
117
- const full = path.join(root, rel)
118
- if (fs.existsSync(full)) return full
273
+ const full = path.join(root, rel);
274
+ if (fs.existsSync(full)) return full;
119
275
  }
120
- return null
276
+ return null;
121
277
  }
122
278
 
123
279
  async function handleRouteCheck(args) {
124
- const root = getProjectRoot()
125
- const scanPath = args && args.path ? args.path : 'src/views'
126
- const routeFile = findRouteFile(root, args && args.routeFile)
280
+ const root = getProjectRoot();
281
+ const scanPath = args && args.path ? args.path : "src/views";
282
+ const routeFile = findRouteFile(root, args && args.routeFile);
127
283
  if (!routeFile) {
128
- return '⚠️ 未找到路由文件,默认检查路径:vite/plugins/shared/pages.ts / src/router/pages.ts / src/router/routes.ts / src/router/index.ts'
284
+ return "⚠️ 未找到路由文件,默认检查路径:vite/plugins/shared/pages.ts / src/router/pages.ts / src/router/routes.ts / src/router/index.ts";
129
285
  }
130
- const routeContent = fs.readFileSync(routeFile, 'utf8').replace(/\\/g, '/')
131
- const pages = findPageDirs(root, scanPath)
132
- const rows = []
286
+ const routeContent = fs.readFileSync(routeFile, "utf8").replace(/\\/g, "/");
287
+ const pages = findPageDirs(root, scanPath);
288
+ const rows = [];
133
289
  for (const page of pages) {
134
- const viewRel = page.dir.replace(/^src\/views\//, '')
135
- const lastSegment = viewRel.split('/').filter(Boolean).pop() || viewRel
136
- const registered = routeContent.includes(viewRel) || routeContent.includes(page.dir) || routeContent.includes(lastSegment)
137
- rows.push({ dir: page.dir, registered })
290
+ const viewRel = page.dir.replace(/^src\/views\//, "");
291
+ const lastSegment = viewRel.split("/").filter(Boolean).pop() || viewRel;
292
+ const registered =
293
+ routeContent.includes(viewRel) ||
294
+ routeContent.includes(page.dir) ||
295
+ routeContent.includes(lastSegment);
296
+ rows.push({ dir: page.dir, registered });
138
297
  }
139
- const miss = rows.filter((r) => !r.registered)
140
- const relRoute = normalizePath(path.relative(root, routeFile))
141
- const lines = [`✅ 路由检查完成:${relRoute}`, '', `- 页面目录:${rows.length}`, `- 疑似未注册:${miss.length}`, '', '| 页面目录 | 路由文件中可发现 |', '|---|---:|']
142
- for (const row of rows) lines.push(`| ${row.dir} | ${row.registered ? '✅' : '⚠️'} |`)
143
- return lines.join('\n')
298
+ const miss = rows.filter((r) => !r.registered);
299
+ const relRoute = normalizePath(path.relative(root, routeFile));
300
+ const lines = [
301
+ `✅ 路由检查完成:${relRoute}`,
302
+ "",
303
+ `- 页面目录:${rows.length}`,
304
+ `- 疑似未注册:${miss.length}`,
305
+ "",
306
+ "| 页面目录 | 路由文件中可发现 |",
307
+ "|---|---:|",
308
+ ];
309
+ for (const row of rows)
310
+ lines.push(`| ${row.dir} | ${row.registered ? "✅" : "⚠️"} |`);
311
+ return lines.join("\n");
144
312
  }
145
313
 
146
314
  async function handleGitLogExtract(args) {
147
- const root = getProjectRoot()
148
- const n = Math.max(1, Math.min(Number((args && args.n) || 20), 100))
149
- let output
315
+ const root = getProjectRoot();
316
+ const n = Math.max(1, Math.min(Number((args && args.n) || 20), 100));
317
+ let output;
150
318
  try {
151
- output = execFileSync('git', ['log', `-${n}`, '--pretty=format:%h%x09%s%x09%an%x09%ad', '--date=short'], { cwd: root, encoding: 'utf8' })
319
+ output = execFileSync(
320
+ "git",
321
+ [
322
+ "log",
323
+ `-${n}`,
324
+ "--pretty=format:%h%x09%s%x09%an%x09%ad",
325
+ "--date=short",
326
+ ],
327
+ { cwd: root, encoding: "utf8" },
328
+ );
152
329
  } catch (e) {
153
- return `❌ git log 提取失败:${e.message}`
330
+ return `❌ git log 提取失败:${e.message}`;
154
331
  }
155
- if (!output.trim()) return '⚠️ 未提取到 git log'
156
- const lines = ['✅ 最近提交摘要', '', '| hash | message | author | date |', '|---|---|---|---|']
157
- for (const row of output.split('\n')) {
158
- const [hash, message, author, date] = row.split('\t')
159
- lines.push(`| ${hash} | ${String(message || '').replace(/\|/g, '\\|')} | ${author || ''} | ${date || ''} |`)
332
+ if (!output.trim()) return "⚠️ 未提取到 git log";
333
+ const lines = [
334
+ "✅ 最近提交摘要",
335
+ "",
336
+ "| hash | message | author | date |",
337
+ "|---|---|---|---|",
338
+ ];
339
+ for (const row of output.split("\n")) {
340
+ const [hash, message, author, date] = row.split("\t");
341
+ lines.push(
342
+ `| ${hash} | ${String(message || "").replace(/\|/g, "\\|")} | ${author || ""} | ${date || ""} |`,
343
+ );
160
344
  }
161
- return lines.join('\n')
345
+ return lines.join("\n");
162
346
  }
163
347
 
164
348
  function readEnvLocal(root) {
165
- const envPath = path.join(root, '.github', 'skills', 'sync', 'env.local.json')
166
- if (!fs.existsSync(envPath)) return null
349
+ const envPath = path.join(
350
+ root,
351
+ ".github",
352
+ "skills",
353
+ "sync",
354
+ "env.local.json",
355
+ );
356
+ if (!fs.existsSync(envPath)) return null;
167
357
  try {
168
- return JSON.parse(fs.readFileSync(envPath, 'utf8'))
358
+ return JSON.parse(fs.readFileSync(envPath, "utf8"));
169
359
  } catch (e) {
170
- return null
360
+ return null;
171
361
  }
172
362
  }
173
363
 
174
364
  function findLatestAuditReport(root, inputPath) {
175
365
  if (inputPath) {
176
- const full = safeResolve(root, inputPath)
177
- return fs.existsSync(full) ? full : null
366
+ const full = safeResolve(root, inputPath);
367
+ return fs.existsSync(full) ? full : null;
178
368
  }
179
- const reportDir = path.join(root, '.github', 'reports')
180
- if (!fs.existsSync(reportDir)) return null
369
+ const reportDir = path.join(root, ".github", "reports");
370
+ if (!fs.existsSync(reportDir)) return null;
181
371
  const files = fs
182
372
  .readdirSync(reportDir)
183
373
  .filter((name) => /^AUDIT_.*\.md$|规范审查报告\.md$/.test(name))
184
374
  .map((name) => path.join(reportDir, name))
185
- .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
186
- return files[0] || null
375
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
376
+ return files[0] || null;
187
377
  }
188
378
 
189
379
  function postWebhook(webhook, payload) {
190
380
  return new Promise((resolve) => {
191
- const body = JSON.stringify(payload)
381
+ const body = JSON.stringify(payload);
192
382
  const req = https.request(
193
383
  webhook,
194
- { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },
384
+ {
385
+ method: "POST",
386
+ headers: {
387
+ "Content-Type": "application/json",
388
+ "Content-Length": Buffer.byteLength(body),
389
+ },
390
+ },
195
391
  (res) => {
196
- const chunks = []
197
- res.on('data', (chunk) => chunks.push(chunk))
198
- res.on('end', () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }))
199
- }
200
- )
201
- req.on('error', (e) => resolve({ ok: false, error: e.message }))
202
- req.write(body)
203
- req.end()
204
- })
392
+ const chunks = [];
393
+ res.on("data", (chunk) => chunks.push(chunk));
394
+ res.on("end", () =>
395
+ resolve({
396
+ ok: res.statusCode >= 200 && res.statusCode < 300,
397
+ statusCode: res.statusCode,
398
+ body: Buffer.concat(chunks).toString("utf8"),
399
+ }),
400
+ );
401
+ },
402
+ );
403
+ req.on("error", (e) => resolve({ ok: false, error: e.message }));
404
+ req.write(body);
405
+ req.end();
406
+ });
205
407
  }
206
408
 
207
409
  async function handleAuditReportPush(args) {
208
- const root = getProjectRoot()
209
- const env = readEnvLocal(root)
210
- const webhook = env && env.feishu_webhook
211
- if (!webhook || String(webhook).includes('你的') || String(webhook).includes('webhook')) {
212
- return 'ℹ️ 未配置 env.local.json 的 feishu_webhook,已跳过审计报告推送'
410
+ const root = getProjectRoot();
411
+ const env = readEnvLocal(root);
412
+ const webhook = env && env.feishu_webhook;
413
+ if (
414
+ !webhook ||
415
+ String(webhook).includes("你的") ||
416
+ String(webhook).includes("webhook")
417
+ ) {
418
+ return "ℹ️ 未配置 env.local.json 的 feishu_webhook,已跳过审计报告推送";
213
419
  }
214
- const report = findLatestAuditReport(root, args && args.reportPath)
215
- if (!report) return '⚠️ 未找到可推送的审计报告'
216
- const rel = normalizePath(path.relative(root, report))
217
- const content = fs.readFileSync(report, 'utf8').slice(0, 3500)
218
- const result = await postWebhook(webhook, { msg_type: 'text', content: { text: `wl-skills 审计报告:${rel}\n\n${content}` } })
219
- if (!result.ok) return `❌ 飞书推送失败:${result.error || result.statusCode}`
220
- return `✅ 审计报告已推送:${rel}`
420
+ const report = findLatestAuditReport(root, args && args.reportPath);
421
+ if (!report) return "⚠️ 未找到可推送的审计报告";
422
+ const rel = normalizePath(path.relative(root, report));
423
+ const content = fs.readFileSync(report, "utf8").slice(0, 3500);
424
+ const result = await postWebhook(webhook, {
425
+ msg_type: "text",
426
+ content: { text: `wl-skills 审计报告:${rel}\n\n${content}` },
427
+ });
428
+ if (!result.ok)
429
+ return `❌ 飞书推送失败:${result.error || result.statusCode}`;
430
+ return `✅ 审计报告已推送:${rel}`;
221
431
  }
222
432
 
223
433
  module.exports = {
224
434
  handleCodeScan,
435
+ handleValidatePage,
436
+ handleDoctorUi,
225
437
  handleRouteCheck,
226
438
  handleGitLogExtract,
227
439
  handleAuditReportPush,
228
- }
440
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-kit",
3
- "version": "2.4.1",
4
- "description": "AI Skill 模板包 v2.4.0 — 13 条编码规范 + 9 个 AI Skill + 14 个 MCP Tool,一条命令导入 Vue 3 项目",
3
+ "version": "2.5.0",
4
+ "description": "AI Skill 模板包 v2.5.0 — 13 条编码规范 + 9 个 AI Skill + 17 个 MCP Tool,一条命令导入 Vue 3 项目",
5
5
  "main": "./bin/wl-skills.js",
6
6
  "bin": {
7
7
  "wl-skills": "bin/wl-skills.js"
@@ -35,7 +35,36 @@
35
35
  "engines": {
36
36
  "node": ">=16.0.0"
37
37
  },
38
+ "scripts": {
39
+ "standards:init": "npx @robot-admin/git-standards init",
40
+ "prepare": "husky",
41
+ "cz": "git-cz",
42
+ "lint": "eslint ."
43
+ },
38
44
  "dependencies": {
39
45
  "xlsx": "^0.18.5"
46
+ },
47
+ "devDependencies": {
48
+ "@commitlint/cli": "^20.5.3",
49
+ "@commitlint/config-conventional": "^20.5.3",
50
+ "@typescript-eslint/eslint-plugin": "^8.59.2",
51
+ "@typescript-eslint/parser": "^8.59.2",
52
+ "@vue/eslint-config-typescript": "^14.7.0",
53
+ "commitizen": "^4.3.1",
54
+ "cz-customizable": "^7.5.4",
55
+ "eslint": "^10.3.0",
56
+ "eslint-plugin-vue": "^10.9.0",
57
+ "husky": "^9.1.7",
58
+ "lint-staged": "^16.4.0"
59
+ },
60
+ "config": {
61
+ "commitizen": {
62
+ "path": "node_modules/cz-customizable"
63
+ }
64
+ },
65
+ "lint-staged": {
66
+ "src/**/*.{js,jsx,ts,tsx,vue}": [
67
+ "eslint --fix --no-cache"
68
+ ]
40
69
  }
41
70
  }