@agile-team/wl-skills-kit 1.1.6 → 1.2.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/CHANGELOG.md +29 -0
- package/README.md +75 -5
- package/bin/wl-skills.js +257 -114
- package/files/.github/skills/menu-sync/SKILL.md +10 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-04-10
|
|
4
|
+
|
|
5
|
+
### 新增:CLI v2.0 — update 增量更新 + clean 构建清理
|
|
6
|
+
|
|
7
|
+
- **`update` 命令**:基于 MD5 增量更新,仅覆盖有变化的文件
|
|
8
|
+
- 对比 `.wl-skills-manifest.json` 清单中的 MD5 哈希
|
|
9
|
+
- 输出新增/更新/未变文件数 + 版本变化提示
|
|
10
|
+
- 支持 `--dry-run` 预览
|
|
11
|
+
- **`clean` 命令**:构建前清理 AI 开发辅助文件
|
|
12
|
+
- 删除 `.github/`、`docs/`、`demo/`、编辑器配置等非组件文件
|
|
13
|
+
- **保护路径**:`src/components/` + `src/types/` 永远不会被清理
|
|
14
|
+
- 删除空父目录(自动清理目录树)
|
|
15
|
+
- 清除 `.wl-skills-manifest.json` 自身
|
|
16
|
+
- 支持 `--dry-run` 预览
|
|
17
|
+
- **Manifest 清单系统**(`.wl-skills-manifest.json`):
|
|
18
|
+
- `init` / `update` 执行后自动生成,记录版本 + 文件路径 → MD5 映射
|
|
19
|
+
- 供 `update` 做增量比对、`clean` 做精准删除
|
|
20
|
+
- 已加入 `.gitignore`
|
|
21
|
+
- CLI 重构为 3 个子命令:`init`(默认,向后兼容)、`update`、`clean`
|
|
22
|
+
- README.md 新增「CLI 命令」章节,更新「快速开始」「更新策略」「安装行为说明」
|
|
23
|
+
|
|
24
|
+
## [1.1.7] - 2026-04-10
|
|
25
|
+
|
|
26
|
+
### 修复:menu-sync SKILL.md 漏同步
|
|
27
|
+
|
|
28
|
+
- `files/.github/skills/menu-sync/SKILL.md` 未随 v1.1.6 同步源仓库版本
|
|
29
|
+
- 修复内容:sysAppNo 字段补齐(配置模板/表格/示例)、`{appNo}` → `{sysAppNo}` 变量统一、响应码说明补充
|
|
30
|
+
- 全量审计确认 19 个同步文件 + 9 个 TPL 模板零差异
|
|
31
|
+
|
|
3
32
|
## [1.1.6] - 2026-04-10
|
|
4
33
|
|
|
5
34
|
### 修复:menu-config.md 残留引用 + env 模板字段缺失
|
package/README.md
CHANGED
|
@@ -15,11 +15,14 @@ npx @agile-team/wl-skills-kit
|
|
|
15
15
|
# 预览将写入哪些文件(不实际写入)
|
|
16
16
|
npx @agile-team/wl-skills-kit --dry-run
|
|
17
17
|
|
|
18
|
-
#
|
|
19
|
-
npx @agile-team/wl-skills-kit@latest
|
|
18
|
+
# 增量更新(仅覆盖有变化的文件)
|
|
19
|
+
npx @agile-team/wl-skills-kit@latest update
|
|
20
|
+
|
|
21
|
+
# 构建前清理 AI 开发辅助文件(保留组件代码)
|
|
22
|
+
npx @agile-team/wl-skills-kit clean
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
117 个文件按原始路径导入到你的项目,**无其他副作用**。
|
|
23
26
|
|
|
24
27
|
---
|
|
25
28
|
|
|
@@ -240,6 +243,61 @@ npx @agile-team/wl-skills-kit
|
|
|
240
243
|
|
|
241
244
|
---
|
|
242
245
|
|
|
246
|
+
## CLI 命令
|
|
247
|
+
|
|
248
|
+
### `init`(默认)
|
|
249
|
+
|
|
250
|
+
首次安装或完整重装 — 全量写入所有文件。
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
npx @agile-team/wl-skills-kit # 等同于 init
|
|
254
|
+
npx @agile-team/wl-skills-kit init
|
|
255
|
+
npx @agile-team/wl-skills-kit --dry-run # 预览模式
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### `update`
|
|
259
|
+
|
|
260
|
+
增量更新 — 基于 MD5 比对,仅覆盖有变化的文件,跳过未变化的文件。
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
npx @agile-team/wl-skills-kit@latest update
|
|
264
|
+
npx @agile-team/wl-skills-kit update --dry-run
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
输出示例:
|
|
268
|
+
```
|
|
269
|
+
✔ 完成!
|
|
270
|
+
新增: 3 个文件 ← 新版本新增的文件
|
|
271
|
+
更新: 5 个文件 ← 内容有变化,已覆盖
|
|
272
|
+
未变: 109 个文件 ← MD5 一致,未动
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
> 需要先执行过 `init` 才能使用 `update`(依赖 `.wl-skills-manifest.json` 清单文件)。
|
|
276
|
+
|
|
277
|
+
### `clean`
|
|
278
|
+
|
|
279
|
+
构建前清理 — 移除 AI 开发辅助文件(Skill、文档、样例、编辑器配置),**保留组件代码**。
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
npx @agile-team/wl-skills-kit clean
|
|
283
|
+
npx @agile-team/wl-skills-kit clean --dry-run
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**保护路径**(永远不会被清理):
|
|
287
|
+
- `src/components/` — 全局/局部/远程组件
|
|
288
|
+
- `src/types/` — 类型桶文件
|
|
289
|
+
|
|
290
|
+
**清理范围**:
|
|
291
|
+
- `.github/` — Skill 文件 + 设计文档
|
|
292
|
+
- `docs/` — 组件 API 文档
|
|
293
|
+
- `demo/` — 领域样例
|
|
294
|
+
- 编辑器配置 — `.cursorrules` / `AGENTS.md` / `CLAUDE.md` 等 8 个文件
|
|
295
|
+
- `.wl-skills-manifest.json` — 清单文件自身
|
|
296
|
+
|
|
297
|
+
> 适用于 CI/CD 构建流程:`npx @agile-team/wl-skills-kit clean && npm run build`
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
243
301
|
## 安装行为说明
|
|
244
302
|
|
|
245
303
|
| ✅ 会做 | ❌ 不会做 |
|
|
@@ -247,7 +305,7 @@ npx @agile-team/wl-skills-kit
|
|
|
247
305
|
| 写入 `.github/` `docs/` `src/` `demo/` | 不修改 `package.json` |
|
|
248
306
|
| 已存在的同名文件会被覆盖 | 不修改 `node_modules/` |
|
|
249
307
|
| 自动创建不存在的目录 | 不执行 postinstall |
|
|
250
|
-
|
|
|
308
|
+
| 生成 `.wl-skills-manifest.json` 清单 | 不删除任何文件(init/update) |
|
|
251
309
|
|
|
252
310
|
### 安全路径(不会被覆盖)
|
|
253
311
|
|
|
@@ -310,13 +368,25 @@ import { AbstractPageQueryHook, BaseQueryItemDesc, ActionButtonDesc, TableColumn
|
|
|
310
368
|
### 5. 更新策略
|
|
311
369
|
|
|
312
370
|
```bash
|
|
313
|
-
#
|
|
371
|
+
# 方式一:增量更新(推荐 — 仅覆盖变化的文件,速度快)
|
|
372
|
+
npx @agile-team/wl-skills-kit@latest update
|
|
373
|
+
|
|
374
|
+
# 方式二:全量重装(适合版本跨度大、或清单文件丢失的情况)
|
|
314
375
|
npx @agile-team/wl-skills-kit@latest
|
|
315
376
|
```
|
|
316
377
|
|
|
317
378
|
- **通用改进** → 提 PR 到 wl-skills-kit 仓库,合并后所有项目受益
|
|
318
379
|
- **领域专有** → 放在安全路径下,永远不会被覆盖
|
|
319
380
|
|
|
381
|
+
### 6. Manifest 清单文件
|
|
382
|
+
|
|
383
|
+
`init` 和 `update` 执行后会在项目根目录生成 `.wl-skills-manifest.json`,记录所有写入的文件路径及 MD5 哈希。该文件用于:
|
|
384
|
+
|
|
385
|
+
- `update` 命令的增量比对(跳过未变化的文件)
|
|
386
|
+
- `clean` 命令的精准删除(只删除 Skill 体系写入的文件)
|
|
387
|
+
|
|
388
|
+
> 建议将 `.wl-skills-manifest.json` 加入 `.gitignore`(每台开发机独立维护即可)。
|
|
389
|
+
|
|
320
390
|
---
|
|
321
391
|
|
|
322
392
|
## 技术栈适配
|
package/bin/wl-skills.js
CHANGED
|
@@ -1,52 +1,61 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* wl-skills-kit CLI
|
|
4
|
+
* wl-skills-kit CLI v2.0
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* 命令:
|
|
7
|
+
* init 全量安装(默认,向后兼容)
|
|
8
|
+
* update 增量更新(仅覆盖有变化的文件,展示 diff 摘要)
|
|
9
|
+
* clean 构建清理(移除 AI 指令/文档/样例,保留组件和类型)
|
|
10
|
+
* --help 帮助
|
|
11
|
+
* --dry-run 预览模式(所有命令均支持)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const fs = require("fs");
|
|
15
15
|
const path = require("path");
|
|
16
|
+
const crypto = require("crypto");
|
|
16
17
|
|
|
17
18
|
const FILES_DIR = path.resolve(__dirname, "..", "files");
|
|
18
19
|
const TARGET_DIR = process.cwd();
|
|
20
|
+
const MANIFEST_NAME = ".wl-skills-manifest.json";
|
|
21
|
+
const MANIFEST_PATH = path.join(TARGET_DIR, MANIFEST_NAME);
|
|
22
|
+
const PKG = require("../package.json");
|
|
19
23
|
const args = process.argv.slice(2);
|
|
20
24
|
const dryRun = args.includes("--dry-run");
|
|
21
25
|
const showHelp = args.includes("--help") || args.includes("-h");
|
|
26
|
+
const command = args.find((a) => !a.startsWith("-")) || "init";
|
|
22
27
|
|
|
23
28
|
if (showHelp) {
|
|
24
29
|
console.log(`
|
|
25
|
-
wl-skills-kit — AI Skill 模板包
|
|
30
|
+
wl-skills-kit v${PKG.version} — AI Skill 模板包
|
|
26
31
|
|
|
27
32
|
用法:
|
|
28
|
-
npx @agile-team/wl-skills-kit
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
npx @agile-team/wl-skills-kit [命令] [选项]
|
|
34
|
+
|
|
35
|
+
命令:
|
|
36
|
+
init 全量安装模板文件到当前项目(默认)
|
|
37
|
+
update 增量更新(仅覆盖有变化的文件,展示变更摘要)
|
|
38
|
+
clean 构建清理(移除开发期 AI 文件,保留 src/components + src/types)
|
|
39
|
+
|
|
40
|
+
选项:
|
|
41
|
+
--dry-run 预览模式,不实际写入/删除任何文件
|
|
42
|
+
--help 显示帮助
|
|
43
|
+
|
|
44
|
+
示例:
|
|
45
|
+
npx @agile-team/wl-skills-kit 安装全量文件
|
|
46
|
+
npx @agile-team/wl-skills-kit update 仅更新有变化的文件
|
|
47
|
+
npx @agile-team/wl-skills-kit clean 清理开发期文件
|
|
48
|
+
npx @agile-team/wl-skills-kit clean --dry-run 预览将要清理哪些文件
|
|
49
|
+
|
|
50
|
+
清理保护路径(clean 不删除):
|
|
51
|
+
src/components/ 通用组件(被业务页面 import,构建必需)
|
|
52
|
+
src/types/ 类型桶文件(构建必需)
|
|
44
53
|
`);
|
|
45
54
|
process.exit(0);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|
|
49
|
-
*
|
|
58
|
+
* 递归遍历目录,返回所有文件的相对路径(正斜杠)
|
|
50
59
|
*/
|
|
51
60
|
function walkDir(dir, baseDir, fileList) {
|
|
52
61
|
fileList = fileList || [];
|
|
@@ -56,12 +65,22 @@ function walkDir(dir, baseDir, fileList) {
|
|
|
56
65
|
if (entry.isDirectory()) {
|
|
57
66
|
walkDir(fullPath, baseDir, fileList);
|
|
58
67
|
} else {
|
|
59
|
-
fileList.push(path.relative(baseDir, fullPath));
|
|
68
|
+
fileList.push(path.relative(baseDir, fullPath).replace(/\\/g, "/"));
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
71
|
return fileList;
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
/** 计算文件 md5 */
|
|
75
|
+
function fileMd5(fp) {
|
|
76
|
+
return crypto.createHash("md5").update(fs.readFileSync(fp)).digest("hex");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 计算字符串内容 md5 */
|
|
80
|
+
function contentMd5(c) {
|
|
81
|
+
return crypto.createHash("md5").update(c, "utf8").digest("hex");
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
/**
|
|
66
85
|
* 写入文件(自动创建目录)
|
|
67
86
|
* @returns {"created"|"updated"}
|
|
@@ -78,121 +97,245 @@ function writeFile(destPath, content) {
|
|
|
78
97
|
return exists ? "updated" : "created";
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (!
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
/** 复制文件(自动创建目录) */
|
|
101
|
+
function copyFileSafe(srcPath, destPath) {
|
|
102
|
+
const destDir = path.dirname(destPath);
|
|
103
|
+
if (!fs.existsSync(destDir)) {
|
|
104
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
const exists = fs.existsSync(destPath);
|
|
107
|
+
if (!dryRun) {
|
|
108
|
+
fs.copyFileSync(srcPath, destPath);
|
|
109
|
+
}
|
|
110
|
+
return exists ? "updated" : "created";
|
|
91
111
|
}
|
|
92
112
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
/** 删除文件,并向上清理空父目录 */
|
|
114
|
+
function removeFileAndEmptyParents(filePath) {
|
|
115
|
+
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
116
|
+
let dir = path.dirname(filePath);
|
|
117
|
+
while (dir !== TARGET_DIR && dir.length > TARGET_DIR.length) {
|
|
118
|
+
try {
|
|
119
|
+
if (fs.readdirSync(dir).length === 0) {
|
|
120
|
+
fs.rmdirSync(dir);
|
|
121
|
+
dir = path.dirname(dir);
|
|
122
|
+
} else { break; }
|
|
123
|
+
} catch (e) { break; }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
97
126
|
|
|
98
|
-
|
|
127
|
+
/** 读取 manifest */
|
|
128
|
+
function readManifest() {
|
|
129
|
+
if (fs.existsSync(MANIFEST_PATH)) {
|
|
130
|
+
try { return JSON.parse(fs.readFileSync(MANIFEST_PATH, "utf8")); }
|
|
131
|
+
catch (e) { return null; }
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
136
|
+
/** 写入 manifest */
|
|
137
|
+
function writeManifest(data) {
|
|
138
|
+
fs.writeFileSync(MANIFEST_PATH, JSON.stringify(data, null, 2), "utf8");
|
|
102
139
|
}
|
|
103
140
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
// 受保护路径(clean 不删除)
|
|
142
|
+
const PROTECTED_PREFIXES = ["src/components/", "src/types/"];
|
|
143
|
+
function isProtected(relPath) {
|
|
144
|
+
return PROTECTED_PREFIXES.some((p) => relPath.startsWith(p));
|
|
145
|
+
}
|
|
108
146
|
|
|
109
|
-
|
|
110
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
111
|
-
}
|
|
147
|
+
// ─── 编辑器配置生成 ─────────────────────────────────────────────────────
|
|
112
148
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
149
|
+
const AUTO_HEADER =
|
|
150
|
+
"<!-- 由 @agile-team/wl-skills-kit 自动生成。源文件:.github/copilot-instructions.md -->\n" +
|
|
151
|
+
"<!-- 请勿手动编辑本文件,更新时重新执行:npx @agile-team/wl-skills-kit@latest -->\n\n";
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
const CURSOR_MDC_HEADER =
|
|
154
|
+
"---\n" +
|
|
155
|
+
'description: "项目编码规范(由 wl-skills-kit 自动生成)"\n' +
|
|
156
|
+
"alwaysApply: true\n" +
|
|
157
|
+
"---\n\n" +
|
|
158
|
+
AUTO_HEADER;
|
|
124
159
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// 读取 copilot-instructions.md(来自 files/ 包内,保证内容与安装文件一致)
|
|
128
|
-
const INSTRUCTIONS_SRC = path.join(
|
|
129
|
-
FILES_DIR,
|
|
130
|
-
".github",
|
|
131
|
-
"copilot-instructions.md"
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (fs.existsSync(INSTRUCTIONS_SRC)) {
|
|
135
|
-
const raw = fs.readFileSync(INSTRUCTIONS_SRC, "utf8");
|
|
136
|
-
const AUTO_HEADER =
|
|
137
|
-
"<!-- 由 @agile-team/wl-skills-kit 自动生成。源文件:.github/copilot-instructions.md -->\n" +
|
|
138
|
-
"<!-- 请勿手动编辑本文件,更新时重新执行:npx @agile-team/wl-skills-kit@latest -->\n\n";
|
|
139
|
-
|
|
140
|
-
// Cursor 新格式 .mdc 需要 YAML frontmatter
|
|
141
|
-
const CURSOR_MDC_HEADER =
|
|
142
|
-
"---\n" +
|
|
143
|
-
'description: "项目编码规范(由 wl-skills-kit 自动生成)"\n' +
|
|
144
|
-
"alwaysApply: true\n" +
|
|
145
|
-
"---\n\n" +
|
|
146
|
-
AUTO_HEADER;
|
|
147
|
-
|
|
148
|
-
// [目标相对路径, 文件内容]
|
|
149
|
-
const editorConfigs = [
|
|
150
|
-
// 通用兜底(Linux Foundation 标准,绝大多数 AI 工具均支持)
|
|
160
|
+
function getEditorConfigs(raw) {
|
|
161
|
+
return [
|
|
151
162
|
["AGENTS.md", AUTO_HEADER + raw],
|
|
152
|
-
// Claude Code / Claude CLI
|
|
153
163
|
["CLAUDE.md", AUTO_HEADER + raw],
|
|
154
|
-
// Roo Code / Cline
|
|
155
164
|
[".clinerules", AUTO_HEADER + raw],
|
|
156
|
-
// Cursor — legacy(根目录,仍被新版支持)
|
|
157
165
|
[".cursorrules", AUTO_HEADER + raw],
|
|
158
|
-
// Cursor — 新格式(带 frontmatter 的 .mdc)
|
|
159
166
|
[".cursor/rules/conventions.mdc", CURSOR_MDC_HEADER + raw],
|
|
160
|
-
// Windsurf (Cascade)
|
|
161
167
|
[".windsurfrules", AUTO_HEADER + raw],
|
|
162
|
-
// Kiro
|
|
163
168
|
[".kiro/steering/conventions.md", AUTO_HEADER + raw],
|
|
164
|
-
|
|
165
|
-
[".trae/rules/conventions.md", AUTO_HEADER + raw]
|
|
169
|
+
[".trae/rules/conventions.md", AUTO_HEADER + raw],
|
|
166
170
|
];
|
|
171
|
+
}
|
|
167
172
|
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
// ─── 命令: init / update ────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
function runInstall(incremental) {
|
|
176
|
+
const label = incremental ? "update" : "init";
|
|
177
|
+
console.log("");
|
|
178
|
+
console.log(" wl-skills-kit v" + PKG.version + " [" + label + "]");
|
|
179
|
+
console.log(" 目标目录: " + TARGET_DIR);
|
|
180
|
+
if (dryRun) console.log(" 模式: --dry-run(预览)");
|
|
181
|
+
console.log("");
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(FILES_DIR)) {
|
|
184
|
+
console.error(" ✖ files/ 目录不存在,包可能已损坏");
|
|
185
|
+
process.exit(1);
|
|
170
186
|
}
|
|
171
187
|
|
|
172
|
-
|
|
188
|
+
const oldManifest = readManifest();
|
|
189
|
+
const newManifest = { version: PKG.version, files: {} };
|
|
190
|
+
let created = 0, updated = 0, unchanged = 0;
|
|
191
|
+
|
|
192
|
+
// ── Step 1: 复制 files/ 静态文件 ───────────────────────────────────
|
|
193
|
+
|
|
194
|
+
const files = walkDir(FILES_DIR, FILES_DIR);
|
|
195
|
+
if (dryRun) console.log(" [Step 1] files/ 静态文件:\n");
|
|
196
|
+
|
|
197
|
+
for (const relPath of files) {
|
|
198
|
+
const src = path.join(FILES_DIR, relPath);
|
|
173
199
|
const dest = path.join(TARGET_DIR, relPath);
|
|
174
|
-
const
|
|
200
|
+
const srcHash = fileMd5(src);
|
|
201
|
+
newManifest.files[relPath] = srcHash;
|
|
202
|
+
|
|
203
|
+
// update 模式: 跳过内容相同的文件
|
|
204
|
+
if (incremental && fs.existsSync(dest)) {
|
|
205
|
+
if (srcHash === fileMd5(dest)) { unchanged++; continue; }
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (dryRun) {
|
|
209
|
+
const exists = fs.existsSync(dest);
|
|
210
|
+
console.log(" " + (exists ? "覆盖" : "新增") + " " + relPath);
|
|
211
|
+
exists ? updated++ : created++;
|
|
212
|
+
} else {
|
|
213
|
+
copyFileSafe(src, dest) === "created" ? created++ : updated++;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Step 2: 动态生成编辑器配置文件 ────────────────────────────────
|
|
218
|
+
|
|
219
|
+
const INSTRUCTIONS_SRC = path.join(FILES_DIR, ".github", "copilot-instructions.md");
|
|
220
|
+
if (fs.existsSync(INSTRUCTIONS_SRC)) {
|
|
221
|
+
const raw = fs.readFileSync(INSTRUCTIONS_SRC, "utf8");
|
|
222
|
+
const editorConfigs = getEditorConfigs(raw);
|
|
223
|
+
|
|
175
224
|
if (dryRun) {
|
|
176
|
-
console.log("
|
|
225
|
+
console.log("\n [Step 2] 编辑器配置文件(从 copilot-instructions.md 生成):\n");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
for (const [ecPath, ecContent] of editorConfigs) {
|
|
229
|
+
const ecDest = path.join(TARGET_DIR, ecPath);
|
|
230
|
+
const ecHash = contentMd5(ecContent);
|
|
231
|
+
newManifest.files[ecPath] = ecHash;
|
|
232
|
+
|
|
233
|
+
if (incremental && fs.existsSync(ecDest)) {
|
|
234
|
+
if (ecHash === fileMd5(ecDest)) { unchanged++; continue; }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (dryRun) {
|
|
238
|
+
const ecExists = fs.existsSync(ecDest);
|
|
239
|
+
console.log(" " + (ecExists ? "覆盖" : "新增") + " [编辑器] " + ecPath);
|
|
240
|
+
ecExists ? updated++ : created++;
|
|
241
|
+
} else {
|
|
242
|
+
writeFile(ecDest, ecContent) === "created" ? created++ : updated++;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Step 3: 写 manifest ────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
if (!dryRun) writeManifest(newManifest);
|
|
250
|
+
|
|
251
|
+
// ── 输出统计 ──────────────────────────────────────────────────────
|
|
252
|
+
|
|
253
|
+
const total = created + updated + unchanged;
|
|
254
|
+
if (dryRun) {
|
|
255
|
+
console.log("");
|
|
256
|
+
if (incremental) {
|
|
257
|
+
console.log(" 共 " + total + " 个文件(新增 " + created + ",变更 " + updated + ",未变 " + unchanged + ")(未实际写入)");
|
|
258
|
+
} else {
|
|
259
|
+
console.log(" 共 " + total + " 个文件(未实际写入)");
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
console.log(" ✔ 完成!");
|
|
263
|
+
if (incremental) {
|
|
264
|
+
console.log(" 新增: " + created + " 个文件");
|
|
265
|
+
console.log(" 更新: " + updated + " 个文件");
|
|
266
|
+
console.log(" 未变: " + unchanged + " 个文件");
|
|
267
|
+
if (oldManifest && oldManifest.version !== PKG.version) {
|
|
268
|
+
console.log(" 版本: " + oldManifest.version + " → " + PKG.version);
|
|
269
|
+
}
|
|
177
270
|
} else {
|
|
178
|
-
|
|
179
|
-
|
|
271
|
+
console.log(" 新增: " + created + " 个文件");
|
|
272
|
+
console.log(" 覆盖: " + updated + " 个文件");
|
|
273
|
+
console.log(" 总计: " + (created + updated) + " 个文件");
|
|
180
274
|
}
|
|
181
275
|
}
|
|
276
|
+
console.log("");
|
|
182
277
|
}
|
|
183
278
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
191
|
-
|
|
279
|
+
// ─── 命令: clean ────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
function runClean() {
|
|
282
|
+
console.log("");
|
|
283
|
+
console.log(" wl-skills-kit v" + PKG.version + " [clean]");
|
|
284
|
+
console.log(" 目标目录: " + TARGET_DIR);
|
|
285
|
+
if (dryRun) console.log(" 模式: --dry-run(预览)");
|
|
286
|
+
console.log("");
|
|
287
|
+
|
|
288
|
+
const manifest = readManifest();
|
|
289
|
+
if (!manifest) {
|
|
290
|
+
console.log(" ⚠ 未找到 " + MANIFEST_NAME);
|
|
291
|
+
console.log(" 请先执行 npx @agile-team/wl-skills-kit init 安装一次。");
|
|
292
|
+
console.log("");
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const allFiles = Object.keys(manifest.files);
|
|
297
|
+
const toRemove = allFiles.filter((f) => !isProtected(f));
|
|
298
|
+
const toKeep = allFiles.filter((f) => isProtected(f));
|
|
299
|
+
|
|
300
|
+
if (dryRun) {
|
|
301
|
+
console.log(" 将要删除(" + toRemove.length + " 个文件):\n");
|
|
302
|
+
for (const f of toRemove) {
|
|
303
|
+
const exists = fs.existsSync(path.join(TARGET_DIR, f));
|
|
304
|
+
console.log(" " + (exists ? "删除" : "跳过(不存在)") + " " + f);
|
|
305
|
+
}
|
|
306
|
+
console.log("\n 保留(" + toKeep.length + " 个文件):\n");
|
|
307
|
+
for (const f of toKeep) {
|
|
308
|
+
console.log(" 保留 " + f);
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
let removed = 0, skipped = 0;
|
|
312
|
+
for (const f of toRemove) {
|
|
313
|
+
const fullPath = path.join(TARGET_DIR, f);
|
|
314
|
+
if (fs.existsSync(fullPath)) {
|
|
315
|
+
removeFileAndEmptyParents(fullPath);
|
|
316
|
+
removed++;
|
|
317
|
+
} else {
|
|
318
|
+
skipped++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// 删除 manifest 自身
|
|
322
|
+
if (fs.existsSync(MANIFEST_PATH)) fs.unlinkSync(MANIFEST_PATH);
|
|
323
|
+
|
|
324
|
+
console.log(" ✔ 清理完成!");
|
|
325
|
+
console.log(" 删除: " + removed + " 个文件");
|
|
326
|
+
if (skipped > 0) console.log(" 跳过: " + skipped + " 个(已不存在)");
|
|
327
|
+
console.log(" 保留: " + toKeep.length + " 个文件(src/components/ + src/types/)");
|
|
328
|
+
}
|
|
329
|
+
console.log("");
|
|
192
330
|
}
|
|
193
331
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
332
|
+
// ─── 主路由 ─────────────────────────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
switch (command) {
|
|
335
|
+
case "init": runInstall(false); break;
|
|
336
|
+
case "update": runInstall(true); break;
|
|
337
|
+
case "clean": runClean(); break;
|
|
338
|
+
default:
|
|
339
|
+
console.error(' ✖ 未知命令: "' + command + '",请使用 --help 查看可用命令');
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
@@ -21,9 +21,9 @@ description: "Use when: creating system menus for newly generated pages, batch r
|
|
|
21
21
|
| ------------------------------------------------ | ------------------ | --------------------------------------- |
|
|
22
22
|
| 菜单名称、路径、组件、权限、隐藏、排序、应用编码 | `SYS_MENU_INFO.md` | 原型/详设阶段自动生成,AI 直接读取 |
|
|
23
23
|
| `parentMenuNameCode` | API 自动查询 | AI 调 children 接口获取,无需手填 |
|
|
24
|
-
| **gatewayPath、parentMenuId、token**
|
|
24
|
+
| **gatewayPath、parentMenuId、sysAppNo、token** | `env.local.json` | 每套环境不同,唯一需要手动维护的 4 个值 |
|
|
25
25
|
|
|
26
|
-
### 配置文件(只需填
|
|
26
|
+
### 配置文件(只需填 4 个字段)
|
|
27
27
|
|
|
28
28
|
`.github/skills/menu-sync/env/env.local.json`(已加入 `.gitignore`,本地维护,不提交)
|
|
29
29
|
|
|
@@ -31,6 +31,7 @@ description: "Use when: creating system menus for newly generated pages, batch r
|
|
|
31
31
|
{
|
|
32
32
|
"gatewayPath": "http://网关地址:端口",
|
|
33
33
|
"parentMenuId": "父级菜单ID",
|
|
34
|
+
"sysAppNo": "应用编码(从已有菜单的sysAppNo字段获取,非明文)",
|
|
34
35
|
"token": "Bearer Token(不含bearer前缀)"
|
|
35
36
|
}
|
|
36
37
|
```
|
|
@@ -39,7 +40,7 @@ description: "Use when: creating system menus for newly generated pages, batch r
|
|
|
39
40
|
|
|
40
41
|
### 使用步骤
|
|
41
42
|
|
|
42
|
-
1. **首次**:按 `env/guide.md` 填写 `env.local.json` 的
|
|
43
|
+
1. **首次**:按 `env/guide.md` 填写 `env.local.json` 的 4 个字段
|
|
43
44
|
2. **之后**:直接对 AI 说「帮我创建菜单」/「同步菜单」/「补菜单」
|
|
44
45
|
3. AI 自动执行:读 `SYS_MENU_INFO.md` → 读 `env.local.json` → 查父级已有子节点 → 逐条对比去重 → 调 `/system/menu/save` → 输出 created/skipped 结果表
|
|
45
46
|
4. **全程无需手动执行任何命令**
|
|
@@ -90,18 +91,20 @@ description: "Use when: creating system menus for newly generated pages, batch r
|
|
|
90
91
|
GET {gatewayPath}/system/menu/children?current=1&size=100&menuId={parentMenuId}
|
|
91
92
|
Headers:
|
|
92
93
|
authorization: bearer {token}
|
|
93
|
-
Sysappno: {
|
|
94
|
+
Sysappno: {sysAppNo}
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
#### Step 2: 逐条创建菜单
|
|
97
98
|
|
|
98
99
|
对于每条待创建的菜单,先检查是否与已有菜单重名(`menuName` 或 `path` 相同),重复则跳过。
|
|
99
100
|
|
|
101
|
+
> **响应码说明**:后端成功响应为 `code: 2000`(非标准 HTTP 200),判断成功应检查 `response.body.code === 2000` 或 `message` 包含"成功"。
|
|
102
|
+
|
|
100
103
|
```
|
|
101
104
|
POST {gatewayPath}/system/menu/save
|
|
102
105
|
Headers:
|
|
103
106
|
authorization: bearer {token}
|
|
104
|
-
Sysappno: {
|
|
107
|
+
Sysappno: {sysAppNo}
|
|
105
108
|
Content-Type: application/json
|
|
106
109
|
|
|
107
110
|
Body:
|
|
@@ -112,12 +115,12 @@ Body:
|
|
|
112
115
|
"hidden": false,
|
|
113
116
|
"type": "C",
|
|
114
117
|
"parentId": "{parentMenuId}",
|
|
115
|
-
"sysAppNo": "{
|
|
118
|
+
"sysAppNo": "{sysAppNo}",
|
|
116
119
|
"orderNum": {nextOrder},
|
|
117
120
|
"menuName": "客户档案",
|
|
118
121
|
"menuNameCode": "{parentMenuNameCode}:{pinyinName}",
|
|
119
122
|
"path": "mmwrCustomerArchive",
|
|
120
|
-
"permission": "
|
|
123
|
+
"permission": "mmwrCustomerArchive",
|
|
121
124
|
"component": "produce/production-mmwr/aiflow/mmwr-customer-archive/index.vue"
|
|
122
125
|
}
|
|
123
126
|
```
|
package/package.json
CHANGED