@agile-team/wl-skills-kit 2.4.2 → 2.5.1

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/wl-skills.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * wl-skills-kit CLI v2.4.2
4
+ * wl-skills-kit CLI v2.5.0
5
5
  *
6
6
  * 命令:
7
7
  * init 全量安装(默认,向后兼容)
@@ -9,7 +9,9 @@
9
9
  * clean 构建清理(移除 AI 指令/文档/样例,保留组件和类型)
10
10
  * check 环境预检(工具链 / MCP 配置 / manifest)
11
11
  * diff 对比已安装文件与当前 kit 版本
12
- * validate 静态检查 src/views 页面文件完整性
12
+ * validate 静态检查 src/views 页面文件完整性、AGGrid、skills-ui runtime、mock
13
+ * validate-page validate 的别名,适用于单页/目录检查
14
+ * doctor-ui 检查 @agile-team/wk-skills-ui 接入完整性
13
15
  * export 导出 SYS_MENU / SYS_DICT / SYS_PERMISSION 为 xlsx
14
16
  * --help 帮助
15
17
  * --dry-run 预览模式(所有命令均支持)
@@ -44,7 +46,9 @@ if (showHelp) {
44
46
  clean 构建清理(移除开发期 AI 文件,保留 src/components + src/types)
45
47
  check 环境预检(Node / 工具链 / MCP 配置 / manifest)
46
48
  diff 对比已安装文件与当前 kit 版本的差异
47
- validate 静态检查 src/views 页面文件完整性
49
+ validate 静态检查 src/views 页面文件、AGGrid、skills-ui runtime、mock
50
+ validate-page validate 的别名,适用于单页/目录检查
51
+ doctor-ui 检查 @agile-team/wk-skills-ui 接入完整性
48
52
  export 导出 reports/SYS_* 数据为 xlsx
49
53
 
50
54
  选项:
@@ -60,6 +64,8 @@ if (showHelp) {
60
64
  npx @agile-team/wl-skills-kit check 检查本地环境
61
65
  npx @agile-team/wl-skills-kit diff 查看当前项目与最新 kit 差异
62
66
  npx @agile-team/wl-skills-kit validate 检查 src/views 页面文件
67
+ npx @agile-team/wl-skills-kit validate-page src/views/mdata/model/demo
68
+ npx @agile-team/wl-skills-kit doctor-ui 检查 wk-skills-ui 接入
63
69
  npx @agile-team/wl-skills-kit export 导出菜单/字典/权限 xlsx
64
70
  npx @agile-team/wl-skills-kit clean 清理开发期文件
65
71
  npx @agile-team/wl-skills-kit clean --keep-reports 保留 reports/中的菜单/字典数据
@@ -739,30 +745,53 @@ function scanPageDirs(scanRel) {
739
745
  const pages = [];
740
746
  for (const [dir, names] of dirs.entries()) {
741
747
  if (!names.has("index.vue")) continue;
742
- let apiConfigCount = 0;
748
+ const indexPath = path.join(TARGET_DIR, dir, "index.vue");
749
+ const indexContent = fs.existsSync(indexPath)
750
+ ? fs.readFileSync(indexPath, "utf8")
751
+ : "";
743
752
  const dataPath = path.join(TARGET_DIR, dir, "data.ts");
744
- if (fs.existsSync(dataPath)) {
745
- apiConfigCount = (
746
- fs.readFileSync(dataPath, "utf8").match(/API_CONFIG/g) || []
747
- ).length;
748
- }
753
+ const dataContent = fs.existsSync(dataPath)
754
+ ? fs.readFileSync(dataPath, "utf8")
755
+ : "";
756
+ let apiConfigCount = 0;
757
+ if (dataContent)
758
+ apiConfigCount = (dataContent.match(/API_CONFIG/g) || []).length;
749
759
  pages.push({
750
760
  dir,
751
761
  hasDataTs: names.has("data.ts"),
752
762
  hasIndexScss: names.has("index.scss"),
753
763
  hasApiMd: names.has("api.md"),
754
764
  apiConfigCount,
765
+ baseTableCount: (indexContent.match(/<BaseTable\b/g) || []).length,
766
+ agGridCount: (indexContent.match(/render-type=["']agGrid["']/g) || [])
767
+ .length,
768
+ cidBindCount: (indexContent.match(/\bcid=|:cid=/g) || []).length,
769
+ hasDefineColumns: /defineColumns\s*\(/.test(dataContent),
770
+ hasRenderOps: /renderOps\s*\(/.test(dataContent),
771
+ hasOperationsArray: /operations\s*:/.test(dataContent),
772
+ hasEmptyOnClick: /onClick\s*:\s*\(\s*[^)]*\s*\)\s*=>\s*\{\s*\}/.test(
773
+ dataContent,
774
+ ),
775
+ apiUrls: Array.from(
776
+ dataContent.matchAll(/:\s*["']([^"']+\/[^"']+)["']/g),
777
+ ).map((m) => m[1]),
755
778
  });
756
779
  }
757
780
  return pages.sort((a, b) => a.dir.localeCompare(b.dir));
758
781
  }
759
782
 
783
+ function findMockFiles() {
784
+ const mockDir = path.join(TARGET_DIR, "mock");
785
+ if (!fs.existsSync(mockDir)) return [];
786
+ return walkDir(mockDir, TARGET_DIR).filter((rel) => /\.(ts|js)$/.test(rel));
787
+ }
788
+
760
789
  function runValidate() {
761
790
  const scanPath =
762
791
  args.find((a) => !a.startsWith("-") && a !== command) || "src/views";
763
792
  const pages = scanPageDirs(scanPath);
764
793
  console.log("");
765
- console.log(" wl-skills-kit v" + PKG.version + " [validate]");
794
+ console.log(" wl-skills-kit v" + PKG.version + " [" + command + "]");
766
795
  console.log(" 扫描目录: " + scanPath);
767
796
  console.log("");
768
797
 
@@ -774,6 +803,10 @@ function runValidate() {
774
803
  }
775
804
 
776
805
  const issues = [];
806
+ const mockFiles = findMockFiles();
807
+ const mockContent = mockFiles
808
+ .map((rel) => fs.readFileSync(path.join(TARGET_DIR, rel), "utf8"))
809
+ .join("\n");
777
810
  for (const page of pages) {
778
811
  if (!page.hasDataTs)
779
812
  issues.push({
@@ -789,17 +822,164 @@ function runValidate() {
789
822
  dir: page.dir,
790
823
  text: "检测到 API_CONFIG 但缺 api.md",
791
824
  });
825
+ if (page.baseTableCount > 0 && page.agGridCount < page.baseTableCount)
826
+ issues.push({
827
+ level: "error",
828
+ dir: page.dir,
829
+ text: 'BaseTable 必须显式 render-type="agGrid"',
830
+ });
831
+ if (page.baseTableCount > 0 && page.cidBindCount < page.baseTableCount)
832
+ issues.push({
833
+ level: "error",
834
+ dir: page.dir,
835
+ text: "BaseTable 必须配置全局唯一 cid / :cid",
836
+ });
837
+ if (page.hasDataTs && page.baseTableCount > 0 && !page.hasDefineColumns)
838
+ issues.push({
839
+ level: "error",
840
+ dir: page.dir,
841
+ text: "表格列必须使用 wk-skills-ui defineColumns()",
842
+ });
843
+ if (page.hasOperationsArray)
844
+ issues.push({
845
+ level: "error",
846
+ dir: page.dir,
847
+ text: "操作列禁止 operations 数组,必须使用 defaultSlot + renderOps()",
848
+ });
849
+ if (
850
+ page.hasDataTs &&
851
+ page.baseTableCount > 0 &&
852
+ !page.hasRenderOps &&
853
+ /操作|_action/.test(
854
+ fs.readFileSync(path.join(TARGET_DIR, page.dir, "data.ts"), "utf8"),
855
+ )
856
+ )
857
+ issues.push({
858
+ level: "warn",
859
+ dir: page.dir,
860
+ text: "疑似存在操作列但未使用 renderOps()",
861
+ });
862
+ if (page.hasEmptyOnClick)
863
+ issues.push({
864
+ level: "error",
865
+ dir: page.dir,
866
+ text: "存在空 onClick: () => {}",
867
+ });
868
+ if (page.apiConfigCount > 0 && mockFiles.length === 0)
869
+ issues.push({
870
+ level: "warn",
871
+ dir: page.dir,
872
+ text: "检测到 API_CONFIG 但项目 mock/ 目录无 mock 文件",
873
+ });
874
+ for (const url of page.apiUrls.filter((item) => item.startsWith("/"))) {
875
+ const mockUrl = `/dev-api${url}`;
876
+ if (mockContent && !mockContent.includes(mockUrl))
877
+ issues.push({
878
+ level: "warn",
879
+ dir: page.dir,
880
+ text: "mock 中未发现端点 " + mockUrl,
881
+ });
882
+ }
792
883
  }
793
884
 
794
885
  console.log(" 页面目录: " + pages.length);
795
886
  console.log(" 提示项: " + issues.length);
796
887
  console.log("");
888
+ const errors = issues.filter((issue) => issue.level === "error").length;
797
889
  for (const issue of issues) {
798
- console.log(" ⚠ " + issue.dir + " " + issue.text);
890
+ const icon = issue.level === "error" ? "✖" : "⚠";
891
+ console.log(" " + icon + " " + issue.dir + " — " + issue.text);
799
892
  }
800
893
  if (issues.length === 0) console.log(" ✔ 页面文件完整性检查通过");
801
894
  console.log("");
802
- if (issues.length > 0) process.exitCode = 1;
895
+ if (errors > 0 || issues.length > 0) process.exitCode = 1;
896
+ }
897
+
898
+ function readJsonSafe(filePath) {
899
+ if (!fs.existsSync(filePath)) return null;
900
+ try {
901
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
902
+ } catch {
903
+ return null;
904
+ }
905
+ }
906
+
907
+ function runDoctorUi() {
908
+ console.log("");
909
+ console.log(" wl-skills-kit v" + PKG.version + " [doctor-ui]");
910
+ console.log(" 目标目录: " + TARGET_DIR);
911
+ console.log("");
912
+
913
+ const checks = [];
914
+ function add(name, ok, detail) {
915
+ checks.push({ name, ok, detail });
916
+ }
917
+
918
+ const pkg = readJsonSafe(path.join(TARGET_DIR, "package.json"));
919
+ const deps = pkg ? { ...pkg.dependencies, ...pkg.devDependencies } : {};
920
+ add(
921
+ "@agile-team/wk-skills-ui",
922
+ Boolean(deps["@agile-team/wk-skills-ui"]),
923
+ deps["@agile-team/wk-skills-ui"] || "未安装",
924
+ );
925
+ add(
926
+ "@element-plus/icons-vue",
927
+ Boolean(deps["@element-plus/icons-vue"]),
928
+ deps["@element-plus/icons-vue"] || "未安装",
929
+ );
930
+
931
+ const files = fs.existsSync(TARGET_DIR)
932
+ ? walkDir(TARGET_DIR, TARGET_DIR)
933
+ : [];
934
+ const sourceFiles = files.filter(
935
+ (rel) =>
936
+ /\.(ts|vue|scss|html)$/.test(rel) && !rel.startsWith("node_modules/"),
937
+ );
938
+ const readAll = (pattern) =>
939
+ sourceFiles
940
+ .filter((rel) => pattern.test(rel))
941
+ .map((rel) => fs.readFileSync(path.join(TARGET_DIR, rel), "utf8"))
942
+ .join("\n");
943
+ const allSource = readAll(/.*/);
944
+
945
+ add(
946
+ "design tokens",
947
+ /@agile-team\/wk-skills-ui\/design\/tokens|dist\/tokens\.css/.test(
948
+ allSource,
949
+ ),
950
+ "需引入 design tokens",
951
+ );
952
+ add(
953
+ "styles preset",
954
+ /@agile-team\/wk-skills-ui\/styles/.test(allSource),
955
+ "需引入 styles 或 skin preset",
956
+ );
957
+ add(
958
+ "installCommonPreset",
959
+ /installCommonPreset\s*\(/.test(allSource),
960
+ "需在入口或业务 preset 中调用",
961
+ );
962
+ add(
963
+ "defineColumns",
964
+ /defineColumns\s*\(/.test(allSource),
965
+ "页面列定义需使用 defineColumns",
966
+ );
967
+ add("renderOps", /renderOps\s*\(/.test(allSource), "操作列需使用 renderOps");
968
+
969
+ for (const item of checks) {
970
+ console.log(
971
+ " " + statusIcon(item.ok) + " " + item.name + " — " + item.detail,
972
+ );
973
+ }
974
+ const failed = checks.filter((item) => !item.ok).length;
975
+ console.log("");
976
+ console.log(
977
+ failed === 0
978
+ ? " ✔ wk-skills-ui 接入检查通过"
979
+ : " ⚠ wk-skills-ui 接入仍有 " + failed + " 项需处理",
980
+ );
981
+ console.log("");
982
+ if (failed > 0) process.exitCode = 1;
803
983
  }
804
984
 
805
985
  function parseMarkdownTable(content) {
@@ -899,8 +1079,12 @@ switch (command) {
899
1079
  runDiff();
900
1080
  break;
901
1081
  case "validate":
1082
+ case "validate-page":
902
1083
  runValidate();
903
1084
  break;
1085
+ case "doctor-ui":
1086
+ runDoctorUi();
1087
+ break;
904
1088
  case "export":
905
1089
  runExport();
906
1090
  break;