cloudcc-cli 2.2.6 → 2.2.7

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.
Files changed (117) hide show
  1. package/.cloudcc-cache.json +24 -20
  2. package/README.md +12 -0
  3. package/bin/cc.js +7 -0
  4. package/java/com/cloudcc/core/BaseException.java +100 -0
  5. package/java/com/cloudcc/core/BusiException.java +43 -0
  6. package/java/com/cloudcc/core/CCService.java +3 -1
  7. package/java/com/cloudcc/core/StringUtils.java +7 -0
  8. package/java/com/cloudcc/core/TimeUtil.java +33 -0
  9. package/java/com/cloudcc/core/UserInfo.java +9 -0
  10. package/package.json +7 -1
  11. package/pom.xml +1 -1
  12. package/skill/BACKEND_CODE.md +114 -0
  13. package/skill/CLI_CHEATSHEET.md +90 -0
  14. package/skill/INSTALL_AND_BOOTSTRAP.md +59 -0
  15. package/skill/OBJECTS_AND_FIELDS.md +120 -0
  16. package/skill/REQUIREMENTS_BREAKDOWN.md +98 -0
  17. package/skill/SKILL.md +33 -0
  18. package/skill/VUE_CUSTOM_COMPONENT.md +50 -0
  19. package/src/api/backend-sdk-java.md +427 -0
  20. package/src/api/ccdk-sdk.md +1039 -0
  21. package/src/classes/doc.js +486 -0
  22. package/src/classes/index.js +1 -0
  23. package/src/mcp/cliRunner.js +61 -0
  24. package/src/mcp/index.js +41 -3
  25. package/src/mcp/tools/Application Creator/handler.js +7 -9
  26. package/src/mcp/tools/Approval/handler.js +34 -151
  27. package/src/mcp/tools/Class Creator/handler.js +18 -15
  28. package/src/mcp/tools/Class Detail Retriever/handler.js +8 -9
  29. package/src/mcp/tools/Class Editor Guide/handler.js +5 -19
  30. package/src/mcp/tools/Class List Retriever/handler.js +8 -3
  31. package/src/mcp/tools/Class Publisher/handler.js +7 -9
  32. package/src/mcp/tools/Class Puller/handler.js +6 -65
  33. package/src/mcp/tools/Client Script Detail Retriever/handler.js +12 -18
  34. package/src/mcp/tools/Client Script Editor Guide/handler.js +9 -605
  35. package/src/mcp/tools/Client Script List Retriever/handler.js +30 -33
  36. package/src/mcp/tools/Client Script Publisher/handler.js +12 -11
  37. package/src/mcp/tools/Client Script Puller/handler.js +23 -30
  38. package/src/mcp/tools/CloudCC Development Overview/handler.js +11 -5
  39. package/src/mcp/tools/Component Creator/handler.js +12 -11
  40. package/src/mcp/tools/Component Detail Retriever/handler.js +12 -9
  41. package/src/mcp/tools/Component Editor Guide/handler.js +5 -22
  42. package/src/mcp/tools/Component List Retriever/handler.js +21 -18
  43. package/src/mcp/tools/Component Publisher/handler.js +25 -3
  44. package/src/mcp/tools/Component Puller/handler.js +13 -16
  45. package/src/mcp/tools/Dev Environment Creator/handler.js +5 -72
  46. package/src/mcp/tools/Dev Environment Validator/handler.js +5 -66
  47. package/src/mcp/tools/Developer Key Setup Guide/handler.js +11 -20
  48. package/src/mcp/tools/JSP Migrator/handler.js +842 -0
  49. package/src/mcp/tools/Menu Creator/handler.js +7 -30
  50. package/src/mcp/tools/Object Creator/handler.js +14 -6
  51. package/src/mcp/tools/Object Fields Creator/handler.js +9 -10
  52. package/src/mcp/tools/Object Fields Retriever/handler.js +6 -3
  53. package/src/mcp/tools/Object List Retriever/handler.js +10 -7
  54. package/src/mcp/tools/Scheduled Class Creator/handler.js +12 -16
  55. package/src/mcp/tools/Scheduled Class Detail Retriever/handler.js +7 -9
  56. package/src/mcp/tools/Scheduled Class List Retriever/handler.js +21 -23
  57. package/src/mcp/tools/Scheduled Class Publisher/handler.js +7 -9
  58. package/src/mcp/tools/Scheduled Class Puller/handler.js +6 -70
  59. package/src/mcp/tools/Trigger Creator/handler.js +12 -20
  60. package/src/mcp/tools/Trigger Detail Retriever/handler.js +7 -9
  61. package/src/mcp/tools/Trigger Editor Guide/handler.js +10 -35
  62. package/src/mcp/tools/Trigger List Retriever/handler.js +12 -4
  63. package/src/mcp/tools/Trigger Publisher/handler.js +8 -11
  64. package/src/mcp/tools/Trigger Puller/handler.js +12 -17
  65. package/src/plugin/doc.js +801 -0
  66. package/src/plugin/index.js +1 -0
  67. package/src/project/doc.js +378 -0
  68. package/src/project/index.js +1 -0
  69. package/src/script/doc.js +259 -0
  70. package/src/script/index.js +1 -0
  71. package/src/timer/index.js +1 -0
  72. package/src/triggers/doc.js +342 -0
  73. package/src/triggers/index.js +5 -0
  74. package/target/classes/com/cloudcc/core/BaseException.class +0 -0
  75. package/target/classes/com/cloudcc/core/BusiException.class +0 -0
  76. package/target/classes/com/cloudcc/core/CCService.class +0 -0
  77. package/target/classes/com/cloudcc/core/StringUtils.class +0 -0
  78. package/target/classes/com/cloudcc/core/TimeUtil.class +0 -0
  79. package/target/classes/com/cloudcc/core/UserInfo.class +0 -0
  80. package/template/lib/ccopenapi-0.0.4.jar +0 -0
  81. package/test/application.cli.test.js +30 -0
  82. package/test/classes.cli.test.js +121 -0
  83. package/test/fields.cli.test.js +69 -0
  84. package/test/mcp.cli.test.js +21 -0
  85. package/test/menu.cli.test.js +41 -0
  86. package/test/object.cli.test.js +64 -0
  87. package/test/plugin.cli.test.js +109 -0
  88. package/test/script.cli.test.js +101 -0
  89. package/test/timer.cli.test.js +107 -0
  90. package/test/trigger.cli.test.js +146 -0
  91. package/.vscode/settings.json +0 -3
  92. package/bin/mcp-svc.js +0 -13
  93. package/src/mcp/MCP/345/234/272/346/231/257/346/250/241/346/213/237.md +0 -8
  94. package/src/mcp/index-sse-svc.js +0 -126
  95. package/src/mcp/index-streamable-svc.js +0 -180
  96. package/src/mcp/tools/Class Detail Retriever/prompt.js +0 -37
  97. package/src/mcp/tools/Class Editor Guide/prompt.js +0 -468
  98. package/src/mcp/tools/Class Publisher/prompt.js +0 -40
  99. package/src/mcp/tools/Class Puller/prompt.js +0 -49
  100. package/src/mcp/tools/Client Script Creator/handler.js +0 -179
  101. package/src/mcp/tools/CloudCC Development Overview/prompt.js +0 -871
  102. package/src/mcp/tools/Component Editor Guide/prompt.js +0 -519
  103. package/src/mcp/tools/Component Publisher/prompt.js +0 -659
  104. package/src/mcp/tools/Dev Environment Creator/prompt.js +0 -273
  105. package/src/mcp/tools/Dev Environment Validator/prompt.js +0 -193
  106. package/src/mcp/tools/Developer Key Setup Guide/prompt.js +0 -71
  107. package/src/mcp/tools/Object Fields Retriever/prompt.js +0 -10
  108. package/src/mcp/tools/Object List Retriever/prompt.js +0 -10
  109. package/src/mcp/tools/ccdk/fetcher.js +0 -18
  110. package/src/mcp/tools/ccdk/handler.js +0 -98
  111. package/src/mcp/tools/ccdk/prompt.js +0 -453
  112. package/target/ccopenapi-0.0.3-classes.jar +0 -0
  113. package/target/ccopenapi-0.0.3.jar +0 -0
  114. package/target/maven-archiver/pom.properties +0 -3
  115. package/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +0 -18
  116. package/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +0 -19
  117. package/template/lib/ccopenapi-0.0.3.jar +0 -0
@@ -0,0 +1,342 @@
1
+ /**
2
+ * triggers 文档统一入口(全量一次性输出)
3
+ */
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+
7
+ function generateFullMarkdownDoc() {
8
+ const lines = [
9
+ "# CloudCC 触发器开发指南",
10
+ "",
11
+ "> 本文档参考 CloudCC 官方「触发器」文档整理而成:[触发器](https://help.cloudcc.cn/product03/hong-fa-qi/)。 ",
12
+ "> 目标:帮助开发者正确编写、调试和使用触发器,理解触发时机、可用变量、与自定义类/Email/时间工具的协同,以及避免循环调用等常见坑。",
13
+ "",
14
+ "---",
15
+ "",
16
+ "## 1. 概念与入口",
17
+ "",
18
+ "### 1.1 什么是触发器",
19
+ "",
20
+ "- 触发器是在特定数据操作事件发生**之前或之后**执行的一段 Java 代码。 ",
21
+ "- 典型触发时间:",
22
+ " - 在对象记录插入数据库之前 / 之后",
23
+ " - 在对象记录更新之前 / 之后",
24
+ " - 在对象记录删除之前 / 之后",
25
+ " - 在审批相关场景(`approval`)中 ",
26
+ "- 主要用于:",
27
+ " - 复杂字段校验(必填、多字段联动、一致性约束)",
28
+ " - 自动带值 / 派生字段写入",
29
+ " - 与其他对象联动更新",
30
+ "",
31
+ "### 1.2 触发器开发入口",
32
+ "",
33
+ "两种方式创建触发器:",
34
+ "",
35
+ "1. **通过开发者平台** ",
36
+ " - 登录系统 → 右上角头像 → 「开发者平台」(管理员简档可见) ",
37
+ " - 左侧菜单:`扩展 → 触发器 → 新建`",
38
+ "",
39
+ "2. **在对象后台设置中创建** ",
40
+ " - 进入目标对象的后台设置(对象管理) ",
41
+ " - 将页面滚动到底部,在「触发器」区域点击「新建`",
42
+ "",
43
+ "---",
44
+ "",
45
+ "## 2. 触发器配置项与触发时机",
46
+ "",
47
+ "### 2.1 基本配置",
48
+ "",
49
+ "- **名称**:汉字说明,用于描述触发器完成的功能 ",
50
+ "- **触发对象**:在哪个对象上执行触发器操作 ",
51
+ "- **触发时间(Trigger Event)**:决定在什么时机触发",
52
+ "",
53
+ "常用触发时间包括:",
54
+ "",
55
+ "- `beforeInsert`:新建/复制记录行满足触发条件前执行 ",
56
+ "- `beforeUpdate`:编辑记录行满足触发条件前执行 ",
57
+ "- `beforeUpsert`:新建和编辑/复制统一入口前执行 ",
58
+ "- `beforeDelete`:删除记录前执行 ",
59
+ "- `afterInsert`:新建/复制记录后执行(可获取记录 ID) ",
60
+ "- `afterUpdate`:修改记录后执行 ",
61
+ "- `afterUpsert`:新建或编辑/复制后执行 ",
62
+ "- `afterDelete`:删除记录后执行 ",
63
+ "- `afterInsertCommit`:插入事务提交后执行,**报错不会回滚主事务** ",
64
+ "- `afterUpdateCommit`:更新提交后执行,报错不会回滚 ",
65
+ "- `afterUpsertCommit`:保存提交后执行,报错不会回滚 ",
66
+ "- `afterDeleteCommit`:删除提交后执行,报错不会回滚 ",
67
+ "- `approval`:审批流程相关触发",
68
+ "",
69
+ "**提示**: ",
70
+ "`before*` 阶段通常用于**校验与字段赋值**;`after*` 阶段适合做**关联对象更新、日志记录、异步操作触发**等。",
71
+ "",
72
+ "---",
73
+ "",
74
+ "## 3. 触发器中可用变量与基础语法",
75
+ "",
76
+ "### 3.1 可直接使用的变量",
77
+ "",
78
+ "- `(UserInfo) userInfo`:当前操作用户信息 ",
79
+ "- `(Map) record_new`:当前记录的**最新值**(新建/修改后) ",
80
+ "- `(Map) record_old`:当前记录的**旧值**(修改前),在 `insert` 场景通常为 `null` ",
81
+ "- `trigger`:触发器上下文对象,可用于添加错误提示等",
82
+ "",
83
+ "### 3.2 常用语法与含义",
84
+ "",
85
+ "| 语法 | 返回值 | 含义 |",
86
+ "|----------------------------------------|----------|------------------------------------|",
87
+ "| `record_new.get(\"字段API\")` | String | 获取记录中字段的最新值 |",
88
+ "| `record_new.put(\"字段API\", \"字段值\")` | void | 为记录的某字段赋值 |",
89
+ "| `record_old.get(\"字段API\")` | String | 获取记录中字段的前一个值 |",
90
+ "| `record_old == null` | boolean | 在 `beforeUpsert/afterUpsert` 中识别是否为新建 |",
91
+ "| `trigger.addErrorMessage(\"提示内容\")` | void | 在保存前用于提示并中止保存 |",
92
+ "",
93
+ "**提示**: ",
94
+ "字段名使用**字段 API 名称**(如 `project_manager`、`start_date`),自定义字段一般以 `__c` 结尾。",
95
+ "",
96
+ "---",
97
+ "",
98
+ "## 4. 常见案例",
99
+ "",
100
+ "### 4.1 案例 1:字段联动带值(查询赋值)",
101
+ "",
102
+ "**需求** ",
103
+ "在项目对象中,将当前项目经理的邮箱自动带入本对象的 `email` 字段。 ",
104
+ "",
105
+ "**触发事件**:`beforeUpsert`",
106
+ "",
107
+ "**代码示例**",
108
+ "",
109
+ "```java",
110
+ "String managerId = record_new.get(\"project_manager\") == null ? \"\" : record_new.get(\"project_manager\") + \"\"; // 新值",
111
+ "String managerId_old = record_old == null || record_old.get(\"project_manager\") == null ? \"\" : record_old.get(\"project_manager\") + \"\"; // 旧值",
112
+ "",
113
+ "// 项目经理字段新值和老值不相同时判定为该字段值发生改变,需重新查询用户表获取邮箱带到本记录中",
114
+ "if (!managerId.equals(managerId_old)) {",
115
+ " List<CCObject> userlist = cquery(\"User\", \"id='\" + managerId + \"'\");",
116
+ " if (userlist.size() > 0) {",
117
+ " String email = userlist.get(0).get(\"email\") == null ? \"\" : userlist.get(0).get(\"email\") + \"\";",
118
+ " record_new.put(\"email\", email);",
119
+ " }",
120
+ "}",
121
+ "```",
122
+ "",
123
+ "要点:",
124
+ "- 使用 `record_new` / `record_old` 比较判断字段是否变更,**避免无谓查询与写入**。",
125
+ "- 查询用户对象时使用 `cquery(\"User\", ...)`。",
126
+ "",
127
+ "### 4.2 案例 2:复杂必填校验",
128
+ "",
129
+ "**需求** ",
130
+ "当项目经理不为空时,项目开始日期与结束日期必填,否则不允许保存。",
131
+ "",
132
+ "**触发时间**:`beforeUpsert`",
133
+ "",
134
+ "**代码示例**",
135
+ "",
136
+ "```java",
137
+ "String managerId = record_new.get(\"project_manager\") == null ? \"\" : record_new.get(\"project_manager\") + \"\"; // 项目经理",
138
+ "String start_date = record_new.get(\"start_date\") == null ? \"\" : record_new.get(\"start_date\") + \"\"; // 项目开始日期",
139
+ "String end_date = record_new.get(\"end_date\") == null ? \"\" : record_new.get(\"end_date\") + \"\"; // 项目结束日期",
140
+ "",
141
+ "if (!\"\".equals(managerId) && (\"\".equals(start_date) || \"\".equals(end_date))) {",
142
+ " trigger.addErrorMessage(\"请填写项目开始日期及项目结束日期\");",
143
+ "}",
144
+ "```",
145
+ "",
146
+ "要点:",
147
+ "- 通过 `trigger.addErrorMessage` 阻断保存并提示用户。",
148
+ "- 适合放在 `beforeInsert` / `beforeUpdate` / `beforeUpsert` 等阶段。",
149
+ "",
150
+ "---",
151
+ "",
152
+ "## 5. 进阶用法与注意事项",
153
+ "",
154
+ "### 5.1 触发器可直接引用的参数",
155
+ "",
156
+ "- `UserInfo userInfo`:当前操作用户 ",
157
+ "- `Map record_new` / `Map record_old`",
158
+ "",
159
+ "取字段值时直接使用字段 API 名称:",
160
+ "",
161
+ "```java",
162
+ "String name = record_new.get(\"name\") == null ? \"\" : record_new.get(\"name\") + \"\";",
163
+ "```",
164
+ "",
165
+ "### 5.2 循环调用限制",
166
+ "",
167
+ "触发器之间可以间接导致**循环调用**,例如:",
168
+ "- 在客户对象的 `update` 触发器中更新业务机会 ",
169
+ "- 在业务机会对象的 `update` 触发器中又更新客户(再次触发客户的 `update` 触发器) ",
170
+ "",
171
+ "如果在触发器中不加条件控制,可能形成无限循环。平台限制:",
172
+ "- **最多允许 3 次循环调用** ",
173
+ "- 超过 3 次系统会报错提示并终止执行",
174
+ "",
175
+ "建议:",
176
+ "- 在跨对象更新时增加「状态字段」或「字段变更判断」作为保护条件 ",
177
+ "- 避免在 A 的触发器中无条件更新 B,同时在 B 的触发器中又无条件更新 A ",
178
+ "- 将复杂联动逻辑尽量下沉到自定义类中集中处理",
179
+ "",
180
+ "### 5.3 触发器中发送邮件通知",
181
+ "",
182
+ "触发器可以使用 `SendEmail` 发送系统邮件:",
183
+ "",
184
+ "```java",
185
+ "SendEmail sendEmail = new SendEmail(userInfo);",
186
+ "",
187
+ "// 直接发送邮件",
188
+ "sendEmail.sendMailFromSystem(",
189
+ " new String[]{\"to@example.com\"}, // 收件人",
190
+ " new String[]{\"cc@example.com\"}, // 抄送",
191
+ " new String[]{\"bcc@example.com\"}, // 密送",
192
+ " \"邮件主题\",",
193
+ " \"邮件内容\",",
194
+ " true // 是否文本邮件",
195
+ ");",
196
+ "",
197
+ "// 使用邮件模板发送",
198
+ "sendEmail.sendEmailNew(",
199
+ " \"emailTemplateId\",",
200
+ " new String[]{\"to@example.com\"},",
201
+ " new String[]{\"cc@example.com\"},",
202
+ " new String[]{\"bcc@example.com\"},",
203
+ " record_new.get(\"id\") + \"\" // 关联记录 ID",
204
+ ");",
205
+ "```",
206
+ "",
207
+ "### 5.4 获取邮件模板内容",
208
+ "",
209
+ "```java",
210
+ "/**",
211
+ " * lang:语言",
212
+ " * emailtemplateid:邮件模板ID",
213
+ " * id:记录id(可选)",
214
+ " */",
215
+ "Map<String, String> m = this.getMessageByTemplateId(lang, emailtemplateid, record_new.get(\"id\") + \"\");",
216
+ "",
217
+ "// 邮件标题",
218
+ "String subject = m.get(\"subject\");",
219
+ "// 邮件内容",
220
+ "String content = m.get(\"context\");",
221
+ "// 邮件是否为文本形式",
222
+ "String isText = m.get(\"istext\");",
223
+ "```",
224
+ "",
225
+ "可以先获取模板内容,再根据业务需要调整后发送。",
226
+ "",
227
+ "### 5.5 TimeUtil:多时区时间支持",
228
+ "",
229
+ "与自定义类中用法相同,触发器也应使用 `TimeUtil` 来处理与时区相关的时间:",
230
+ "",
231
+ "- 获取当前时间(按用户时区):",
232
+ "",
233
+ "```java",
234
+ "TpSysTask task = new TpSysTask();",
235
+ "task.setBeginTime(TimeUtil.getNowDate(userInfo));",
236
+ "```",
237
+ "",
238
+ "- 获取带时区的 `SimpleDateFormat`:",
239
+ "",
240
+ "```java",
241
+ "SimpleDateFormat myDateFormat = TimeUtil.getSimpleDateFormat(\"yyyy-MM-dd\", userInfo);",
242
+ "```",
243
+ "",
244
+ "- 获取带时区的 `Calendar`:",
245
+ "",
246
+ "```java",
247
+ "Calendar cal = TimeUtil.getCalendar(userInfo);",
248
+ "```",
249
+ "",
250
+ "---",
251
+ "",
252
+ "## 6. 与 CCService / 自定义类的协同",
253
+ "",
254
+ "### 6.1 在触发器中使用 CCService",
255
+ "",
256
+ "触发器中同样可以实例化并使用 `CCService` 进行增删改查:",
257
+ "",
258
+ "```java",
259
+ "CCService cs = new CCService((UserInfo) userInfo);",
260
+ "",
261
+ "// 示例:根据当前记录字段查询机会",
262
+ "String accountId = record_new.get(\"khmc__c\") == null ? \"\" : record_new.get(\"khmc__c\") + \"\";",
263
+ "if (!\"\".equals(accountId)) {",
264
+ " List<CCObject> opps = cs.cquery(",
265
+ " \"Opportunity\",",
266
+ " \"khmc__c = '\" + accountId + \"'\",",
267
+ " \"jine__c desc\"",
268
+ " );",
269
+ " // TODO: 根据查询结果执行相应逻辑",
270
+ "}",
271
+ "```",
272
+ "",
273
+ "具体 `CCService` 能力详见 `custom-class-dev.md`。",
274
+ "",
275
+ "### 6.2 将复杂逻辑下沉到自定义类",
276
+ "",
277
+ "推荐模式:",
278
+ "",
279
+ "1. 触发器中只负责:",
280
+ " - 判断是否需要执行逻辑(字段是否变化、状态是否满足、避免循环等)",
281
+ " - 准备必需参数(如 `userInfo`、`record_new` / `record_old` 等)",
282
+ "2. 将主要业务逻辑封装到自定义类方法中:",
283
+ "",
284
+ "```java",
285
+ "// 触发器中",
286
+ "if (/* 条件满足 */) {",
287
+ " new ProjectService(userInfo).handleAfterUpdate(record_old, record_new);",
288
+ "}",
289
+ "```",
290
+ "",
291
+ "好处:",
292
+ "- 逻辑集中、可复用、易测试 ",
293
+ "- 触发器代码保持精简,可读性更高",
294
+ "",
295
+ "---",
296
+ "",
297
+ "## 7. 触发器开发 Checklist",
298
+ "",
299
+ "- [ ] 已选择正确的**触发对象**与**触发时间**(before/after/commit/approval) ",
300
+ "- [ ] 校验逻辑集中在 `before*` 阶段,并通过 `trigger.addErrorMessage` 给出清晰提示 ",
301
+ "- [ ] 使用 `record_new` / `record_old` 判断字段是否变化,避免不必要的数据库操作 ",
302
+ "- [ ] 涉及跨对象更新时,已经考虑并避免循环调用(尤其是双向更新) ",
303
+ "- [ ] 发送邮件时使用 `SendEmail` / 模板,且确认模板与收件人配置正确 ",
304
+ "- [ ] 与时间相关逻辑使用 `TimeUtil`,避免跨时区错误 ",
305
+ "- [ ] 复杂业务逻辑拆分到自定义类中,通过触发器调用,触发器本身保持简洁 ",
306
+ "- [ ] 已在测试环境完整验证:新建、编辑、删除、审批等全场景,确认不会出现循环触发或异常回滚问题 ",
307
+ "",
308
+ ];
309
+
310
+ const base = lines.join("\n");
311
+ const sdkPath = path.join(__dirname, "..", "api", "backend-sdk-java.md");
312
+ let sdkContent = "";
313
+ try {
314
+ sdkContent = fs.readFileSync(sdkPath, "utf8");
315
+ } catch (e) {
316
+ sdkContent = `# CloudCC 后端 SDK(Java)使用指南\n\n(未找到文件:${sdkPath})\n`;
317
+ }
318
+
319
+ return [
320
+ base,
321
+ "",
322
+ "---",
323
+ "",
324
+ "## 附录:后端 SDK(Java)速查",
325
+ "",
326
+ "> 来源:`src/api/backend-sdk-java.md`",
327
+ "",
328
+ sdkContent.trim(),
329
+ "",
330
+ ].join("\n");
331
+ }
332
+
333
+ function doc() {
334
+ const content = generateFullMarkdownDoc();
335
+ console.log(content);
336
+ return content;
337
+ }
338
+
339
+ // 兼容旧调用方:统一返回全量文档
340
+ doc.getEditGuide = doc;
341
+
342
+ module.exports = doc;
@@ -5,7 +5,12 @@ cc.pull = require("./pull")
5
5
  cc.get = require("./get")
6
6
  cc.pullList = require("./pullList")
7
7
  cc.detail = require("./detail")
8
+ cc.doc = require("./doc")
8
9
  function Classes(action, argvs) {
10
+ if (action === "detail") {
11
+ cc[action](argvs[2], argvs[3]);
12
+ return;
13
+ }
9
14
  cc[action](argvs)
10
15
  }
11
16
 
Binary file
@@ -0,0 +1,30 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const path = require("node:path");
4
+ const { exec } = require("node:child_process");
5
+ const { promisify } = require("node:util");
6
+
7
+ const execAsync = promisify(exec);
8
+ const repoRoot = path.resolve(__dirname, "..");
9
+
10
+ function quoteArg(value) {
11
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
12
+ }
13
+
14
+ async function runCc(args) {
15
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
16
+ return execAsync(cmd, { cwd: repoRoot });
17
+ }
18
+
19
+ test("应用管理流程:创建 application", async () => {
20
+ const appName = `TestApp${Date.now()}`;
21
+ const appCode = `test_app_${Date.now()}`;
22
+
23
+ const { stdout, stderr } = await runCc(["create", "application", repoRoot, appName, appCode]);
24
+ const out = `${stdout}\n${stderr}`.trim();
25
+
26
+ // src/application/create.js 成功时会输出 `Success!`
27
+ assert.match(out, /Success|成功/i, "create application 应输出成功信息");
28
+ console.error(`\n[application-test] 已创建应用: ${appName} (code=${appCode})`);
29
+ });
30
+
@@ -0,0 +1,121 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const fs = require("node:fs");
4
+ const path = require("node:path");
5
+ const { exec } = require("node:child_process");
6
+ const { promisify } = require("node:util");
7
+
8
+ const execAsync = promisify(exec);
9
+ const repoRoot = path.resolve(__dirname, "..");
10
+
11
+ function quoteArg(value) {
12
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
13
+ }
14
+
15
+ async function runCc(args) {
16
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
17
+ return execAsync(cmd, { cwd: repoRoot });
18
+ }
19
+
20
+ test("类管理流程:创建→doc→发布→拉取→线上详情→列表→批量拉取前2个→清空classes文件夹", async (t) => {
21
+ const className = `CCbbbbbbFlow${Date.now()}`;
22
+ const classDir = path.join(repoRoot, "classes", className);
23
+ const configPath = path.join(classDir, "config.json");
24
+ const javaPath = path.join(classDir, `${className}.java`);
25
+ const classesRoot = path.join(repoRoot, "classes");
26
+
27
+ let publishedId = null;
28
+ let topIds = [];
29
+
30
+ t.test("1) 创建类", async () => {
31
+ await runCc(["create", "classes", className]);
32
+
33
+ const javaTestPath = path.join(classDir, `${className}Test.java`);
34
+ assert.equal(fs.existsSync(classDir), true);
35
+ assert.equal(fs.existsSync(javaPath), true);
36
+ assert.equal(fs.existsSync(javaTestPath), true);
37
+ assert.equal(fs.existsSync(configPath), true);
38
+
39
+ const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
40
+ assert.equal(config.name, className);
41
+ });
42
+
43
+ t.test("2) 获取 doc(classes overview)", async () => {
44
+ const { stdout } = await runCc(["doc", "classes", "overview"]);
45
+ assert.match(stdout, /CloudCC 类编辑知识库/);
46
+ });
47
+
48
+ t.test("3) 发布类(publish,写回 config.id)", async () => {
49
+ await runCc(["publish", "classes", className]);
50
+ const configAfterPublish = JSON.parse(fs.readFileSync(configPath, "utf8"));
51
+ publishedId = configAfterPublish.id || null;
52
+ assert.ok(publishedId, "publish 后应写入 config.id");
53
+ });
54
+
55
+ t.test("4) 拉取类(pull:覆盖 SOURCE 区域)", async () => {
56
+ const localJava = fs.readFileSync(javaPath, "utf8");
57
+ const modifiedJava = localJava.replace(
58
+ /\/\/ @SOURCE_CONTENT_START[\s\S]*?\/\/ @SOURCE_CONTENT_END/,
59
+ "// @SOURCE_CONTENT_START\npublic String localOnly(){ return \"LOCAL_ONLY\"; }\n// @SOURCE_CONTENT_END"
60
+ );
61
+ fs.writeFileSync(javaPath, modifiedJava, "utf8");
62
+
63
+ await runCc(["pull", "classes", className]);
64
+ const javaAfterPull = fs.readFileSync(javaPath, "utf8");
65
+ assert.ok(!javaAfterPull.includes("LOCAL_ONLY"), "pull 后应使用远端源码替换本地标记内容");
66
+ });
67
+
68
+ t.test("5) 查询线上类详情(detail:仅使用 id,不走本地)", async () => {
69
+ // detail(name,id) 内部逻辑:name 为空会跳过本地优先,从服务器查询 id
70
+ assert.ok(publishedId, "发布后应有 publishedId");
71
+ await assert.doesNotReject(() => runCc(["detail", "classes", "", publishedId]));
72
+ });
73
+
74
+ t.test("6) get 获取线上类列表(取前2个类 id)", async () => {
75
+ const listQuery = encodeURI(
76
+ JSON.stringify({
77
+ shownum: "2000",
78
+ showpage: "1",
79
+ fid: "",
80
+ sname: "",
81
+ rptcond: "",
82
+ rptorder: "",
83
+ })
84
+ );
85
+
86
+ const { stdout } = await runCc(["get", "classes", listQuery]);
87
+ const list = JSON.parse(stdout.trim());
88
+ assert.ok(Array.isArray(list), "get classes 应返回数组");
89
+
90
+ // 保存前2个 id
91
+ topIds = list.slice(0, 2).map((x) => x.id).filter(Boolean);
92
+ assert.ok(topIds.length >= 1, "线上类列表至少应包含 1 个 id");
93
+ });
94
+
95
+ t.test("7) 批量拉取前2个类(pullList)", async () => {
96
+ assert.ok(topIds.length >= 1, "pullList 需要至少 1 个线上类 id");
97
+
98
+ const beforeCount = fs.existsSync(classesRoot)
99
+ ? fs.readdirSync(classesRoot).filter((n) => fs.statSync(path.join(classesRoot, n)).isDirectory()).length
100
+ : 0;
101
+
102
+ for (const id of topIds) {
103
+ await runCc(["pullList", "classes", id, repoRoot]);
104
+ }
105
+
106
+ const afterCount = fs.readdirSync(classesRoot).filter((n) =>
107
+ fs.statSync(path.join(classesRoot, n)).isDirectory()
108
+ ).length;
109
+ assert.ok(afterCount >= beforeCount, "pullList 之后 classes 目录应存在/可用");
110
+ });
111
+
112
+ t.test("8) 清空 classes 文件夹", async () => {
113
+ if (fs.existsSync(classesRoot)) {
114
+ fs.rmSync(classesRoot, { recursive: true, force: true });
115
+ }
116
+ fs.mkdirSync(classesRoot, { recursive: true });
117
+ const remain = fs.readdirSync(classesRoot);
118
+ assert.equal(remain.length, 0);
119
+ console.error("");
120
+ });
121
+ });
@@ -0,0 +1,69 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const path = require("node:path");
4
+ const { exec } = require("node:child_process");
5
+ const { promisify } = require("node:util");
6
+
7
+ const execAsync = promisify(exec);
8
+ const repoRoot = path.resolve(__dirname, "..");
9
+
10
+ function quoteArg(value) {
11
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
12
+ }
13
+
14
+ async function runCc(args) {
15
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
16
+ return execAsync(cmd, { cwd: repoRoot });
17
+ }
18
+
19
+ test("字段管理流程:获取对象列表→create 字段→get 字段列表", async (t) => {
20
+ let objPrefix = null;
21
+ let objId = null;
22
+ let objName = null;
23
+ const fieldLabel = `AutoField_${Date.now()}`;
24
+
25
+ t.test("1) 获取对象列表(取第一个对象的 id 和 objprefix)", async () => {
26
+ // 只取自定义对象,避免标准对象在部分环境下字段接口异常
27
+ const { stdout } = await runCc(["get", "object", repoRoot, "custom"]);
28
+ const list = JSON.parse(stdout.trim());
29
+ assert.ok(Array.isArray(list) || (list && typeof list === "object"), "get object 应返回列表或对象");
30
+ const arr = Array.isArray(list) ? list : (list.objList || list.data || []);
31
+ assert.ok(arr.length > 0, "自定义对象列表应非空");
32
+
33
+ const first = arr[0];
34
+ objPrefix = first.objprefix || first.prefix || "account";
35
+ objId = first.id || first.objid || null;
36
+ objName = first.label || first.schemetableName || "UnknownObject";
37
+
38
+ assert.ok(objPrefix, "应有可用的 objprefix");
39
+ assert.ok(objId, "应有可用的对象 id 用于创建字段");
40
+
41
+ console.error(`\n[fields-test] 目标对象: ${objName} (prefix=${objPrefix}, id=${objId})`);
42
+ });
43
+
44
+ t.test("2) create 创建一个文本字段(S)", async () => {
45
+ assert.ok(objId, "需要对象 id");
46
+ // create fields 调用约定:cc create fields <path> <fieldType> <objid> <nameLabel> [...]
47
+ const { stdout, stderr } = await runCc(["create", "fields", repoRoot, "S", objId, fieldLabel]);
48
+ // create fields 会通过 console.error 输出创建结果(见 src/fields/create.js)
49
+ assert.match(
50
+ `${stdout}\n${stderr}`,
51
+ /Field created successfully|✓\s*Field created successfully/i,
52
+ "create fields 应输出创建成功信息"
53
+ );
54
+ console.error(`[fields-test] 已创建字段: ${fieldLabel}`);
55
+ });
56
+
57
+ t.test("3) get 获取指定对象的字段列表,并尽量校验新字段", async () => {
58
+ assert.ok(objPrefix, "需要 objprefix");
59
+ const { stdout } = await runCc(["get", "fields", repoRoot, objPrefix]);
60
+ const res = JSON.parse(stdout.trim());
61
+ assert.ok(res && (res.stdFields !== undefined || res.cusFields !== undefined), "get fields 应返回 stdFields 或 cusFields");
62
+ if (res.stdFields) assert.ok(Array.isArray(res.stdFields), "stdFields 应为数组");
63
+ if (res.cusFields) assert.ok(Array.isArray(res.cusFields), "cusFields 应为数组");
64
+
65
+ // 尽量校验新字段是否已出现(不强制)
66
+ const allCus = Array.isArray(res.cusFields) ? res.cusFields : [];
67
+ const hit = allCus.some((f) => f.fieldname === fieldLabel || f.apiname?.includes("_custom_s_field"));
68
+ });
69
+ });
@@ -0,0 +1,21 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const path = require("node:path");
4
+ const fs = require("node:fs");
5
+
6
+ const repoRoot = path.resolve(__dirname, "..");
7
+
8
+ test("MCP 模块:服务可加载、bin 存在", async (t) => {
9
+ t.test("1) 加载 MCP 服务模块", async () => {
10
+ const mcpServer = require(path.join(repoRoot, "src/mcp/index.js"));
11
+ assert.ok(mcpServer, "MCP 模块应导出服务实例");
12
+ assert.ok(typeof mcpServer === "object", "导出应为对象");
13
+ });
14
+
15
+ t.test("2) bin/mcp.js 存在且可读", async () => {
16
+ const binPath = path.join(repoRoot, "bin/mcp.js");
17
+ assert.ok(fs.existsSync(binPath), "bin/mcp.js 应存在");
18
+ const content = fs.readFileSync(binPath, "utf8");
19
+ assert.match(content, /mcp|MCP|StdioServerTransport/, "bin/mcp.js 应引用 MCP 相关逻辑");
20
+ });
21
+ });
@@ -0,0 +1,41 @@
1
+ const test = require("node:test");
2
+ const assert = require("node:assert/strict");
3
+ const path = require("node:path");
4
+ const { exec } = require("node:child_process");
5
+ const { promisify } = require("node:util");
6
+
7
+ const execAsync = promisify(exec);
8
+ const repoRoot = path.resolve(__dirname, "..");
9
+
10
+ function quoteArg(value) {
11
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
12
+ }
13
+
14
+ async function runCc(args) {
15
+ const cmd = `npx --prefix "${repoRoot}" cc ${args.map(quoteArg).join(" ")}`;
16
+ return execAsync(cmd, { cwd: repoRoot });
17
+ }
18
+
19
+ test("菜单管理流程:获取对象列表→create menu object", async (t) => {
20
+ let objectId = null;
21
+ let objectName = null;
22
+
23
+ t.test("1) 获取自定义对象列表(取第一个自定义对象 id 用于创建菜单)", async () => {
24
+ const { stdout } = await runCc(["get", "object", repoRoot, "custom"]);
25
+ const list = JSON.parse(stdout.trim());
26
+ assert.ok(Array.isArray(list), "get object custom 应返回数组");
27
+ if (list.length > 0 && list[0].id) {
28
+ objectId = list[0].id;
29
+ objectName = list[0].label || list[0].schemetableName || list[0].objname || null;
30
+ }
31
+ assert.ok(objectId, "线上至少应有一个自定义对象 id 用于创建菜单");
32
+ console.error(`\n[menu-test] 目标自定义对象: ${objectName || "Unknown"} (id=${objectId})`);
33
+ });
34
+
35
+ t.test("2) 创建对象类型菜单(create menu object)", async () => {
36
+ assert.ok(objectId, "需要 objectId");
37
+ await assert.doesNotReject(() =>
38
+ runCc(["create", "menu", "object", repoRoot, objectId, "CC测试菜单Tab"])
39
+ );
40
+ });
41
+ });