cloudcc-cli 2.2.5 → 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 (130) hide show
  1. package/.cloudcc-cache.json +38 -0
  2. package/README.md +1435 -522
  3. package/bin/cc.js +7 -2
  4. package/bin/index.js +4 -0
  5. package/java/com/cloudcc/core/BaseException.java +100 -0
  6. package/java/com/cloudcc/core/BusiException.java +43 -0
  7. package/java/com/cloudcc/core/CCService.java +3 -1
  8. package/java/com/cloudcc/core/StringUtils.java +7 -0
  9. package/java/com/cloudcc/core/TimeUtil.java +33 -0
  10. package/java/com/cloudcc/core/UserInfo.java +9 -0
  11. package/package.json +7 -1
  12. package/pom.xml +1 -1
  13. package/skill/BACKEND_CODE.md +114 -0
  14. package/skill/CLI_CHEATSHEET.md +90 -0
  15. package/skill/INSTALL_AND_BOOTSTRAP.md +59 -0
  16. package/skill/OBJECTS_AND_FIELDS.md +120 -0
  17. package/skill/REQUIREMENTS_BREAKDOWN.md +98 -0
  18. package/skill/SKILL.md +33 -0
  19. package/skill/VUE_CUSTOM_COMPONENT.md +50 -0
  20. package/src/api/backend-sdk-java.md +427 -0
  21. package/src/api/ccdk-sdk.md +1039 -0
  22. package/src/application/create.js +114 -0
  23. package/src/application/get.js +13 -0
  24. package/src/application/index.js +8 -0
  25. package/src/classes/doc.js +486 -0
  26. package/src/classes/index.js +1 -0
  27. package/src/mcp/cliRunner.js +61 -0
  28. package/src/mcp/index.js +84 -12
  29. package/src/mcp/readme.md +6 -3
  30. package/src/mcp/tools/Application Creator/handler.js +78 -0
  31. package/src/mcp/tools/Approval/handler.js +34 -151
  32. package/src/mcp/tools/Class Creator/handler.js +18 -15
  33. package/src/mcp/tools/Class Detail Retriever/handler.js +8 -9
  34. package/src/mcp/tools/Class Editor Guide/handler.js +5 -19
  35. package/src/mcp/tools/Class List Retriever/handler.js +8 -3
  36. package/src/mcp/tools/Class Publisher/handler.js +7 -9
  37. package/src/mcp/tools/Class Puller/handler.js +6 -65
  38. package/src/mcp/tools/Client Script Detail Retriever/handler.js +12 -18
  39. package/src/mcp/tools/Client Script Editor Guide/handler.js +9 -605
  40. package/src/mcp/tools/Client Script List Retriever/handler.js +30 -33
  41. package/src/mcp/tools/Client Script Publisher/handler.js +12 -11
  42. package/src/mcp/tools/Client Script Puller/handler.js +23 -30
  43. package/src/mcp/tools/CloudCC Development Overview/handler.js +11 -5
  44. package/src/mcp/tools/Component Creator/handler.js +12 -11
  45. package/src/mcp/tools/Component Detail Retriever/handler.js +12 -9
  46. package/src/mcp/tools/Component Editor Guide/handler.js +5 -22
  47. package/src/mcp/tools/Component List Retriever/handler.js +21 -18
  48. package/src/mcp/tools/Component Publisher/handler.js +25 -3
  49. package/src/mcp/tools/Component Puller/handler.js +13 -16
  50. package/src/mcp/tools/Dev Environment Creator/handler.js +5 -72
  51. package/src/mcp/tools/Dev Environment Validator/handler.js +5 -66
  52. package/src/mcp/tools/Developer Key Setup Guide/handler.js +11 -20
  53. package/src/mcp/tools/JSP Migrator/handler.js +842 -0
  54. package/src/mcp/tools/Menu Creator/handler.js +86 -0
  55. package/src/mcp/tools/Object Creator/handler.js +14 -6
  56. package/src/mcp/tools/Object Fields Creator/handler.js +9 -10
  57. package/src/mcp/tools/Object Fields Retriever/handler.js +6 -3
  58. package/src/mcp/tools/Object List Retriever/handler.js +10 -7
  59. package/src/mcp/tools/Scheduled Class Creator/handler.js +12 -16
  60. package/src/mcp/tools/Scheduled Class Detail Retriever/handler.js +7 -9
  61. package/src/mcp/tools/Scheduled Class List Retriever/handler.js +21 -23
  62. package/src/mcp/tools/Scheduled Class Publisher/handler.js +7 -9
  63. package/src/mcp/tools/Scheduled Class Puller/handler.js +6 -70
  64. package/src/mcp/tools/Trigger Creator/handler.js +12 -20
  65. package/src/mcp/tools/Trigger Detail Retriever/handler.js +7 -9
  66. package/src/mcp/tools/Trigger Editor Guide/handler.js +10 -35
  67. package/src/mcp/tools/Trigger List Retriever/handler.js +12 -4
  68. package/src/mcp/tools/Trigger Publisher/handler.js +8 -11
  69. package/src/mcp/tools/Trigger Puller/handler.js +12 -17
  70. package/src/menu/common.js +16 -0
  71. package/src/menu/create-object.js +94 -0
  72. package/src/menu/create-page.js +108 -0
  73. package/src/menu/create-script.js +108 -0
  74. package/src/menu/create-site.js +108 -0
  75. package/src/menu/create.js +54 -0
  76. package/src/menu/index.js +7 -0
  77. package/src/plugin/doc.js +801 -0
  78. package/src/plugin/index.js +1 -0
  79. package/src/plugin/pull.js +3 -0
  80. package/src/project/doc.js +378 -0
  81. package/src/project/index.js +1 -0
  82. package/src/script/doc.js +259 -0
  83. package/src/script/index.js +1 -0
  84. package/src/timer/index.js +1 -0
  85. package/src/triggers/doc.js +342 -0
  86. package/src/triggers/index.js +5 -0
  87. package/target/classes/com/cloudcc/core/BaseException.class +0 -0
  88. package/target/classes/com/cloudcc/core/BusiException.class +0 -0
  89. package/target/classes/com/cloudcc/core/CCService.class +0 -0
  90. package/target/classes/com/cloudcc/core/StringUtils.class +0 -0
  91. package/target/classes/com/cloudcc/core/TimeUtil.class +0 -0
  92. package/target/classes/com/cloudcc/core/UserInfo.class +0 -0
  93. package/template/lib/ccopenapi-0.0.4.jar +0 -0
  94. package/test/application.cli.test.js +30 -0
  95. package/test/classes.cli.test.js +121 -0
  96. package/test/fields.cli.test.js +69 -0
  97. package/test/mcp.cli.test.js +21 -0
  98. package/test/menu.cli.test.js +41 -0
  99. package/test/object.cli.test.js +64 -0
  100. package/test/plugin.cli.test.js +109 -0
  101. package/test/script.cli.test.js +101 -0
  102. package/test/timer.cli.test.js +107 -0
  103. package/test/trigger.cli.test.js +146 -0
  104. package/.vscode/settings.json +0 -3
  105. package/bin/mcp-svc.js +0 -13
  106. package/src/mcp/MCP/345/234/272/346/231/257/346/250/241/346/213/237.md +0 -8
  107. package/src/mcp/index-sse-svc.js +0 -126
  108. package/src/mcp/index-streamable-svc.js +0 -180
  109. package/src/mcp/tools/Class Detail Retriever/prompt.js +0 -37
  110. package/src/mcp/tools/Class Editor Guide/prompt.js +0 -468
  111. package/src/mcp/tools/Class Publisher/prompt.js +0 -40
  112. package/src/mcp/tools/Class Puller/prompt.js +0 -49
  113. package/src/mcp/tools/Client Script Creator/handler.js +0 -179
  114. package/src/mcp/tools/CloudCC Development Overview/prompt.js +0 -870
  115. package/src/mcp/tools/Component Editor Guide/prompt.js +0 -519
  116. package/src/mcp/tools/Component Publisher/prompt.js +0 -659
  117. package/src/mcp/tools/Dev Environment Creator/prompt.js +0 -273
  118. package/src/mcp/tools/Dev Environment Validator/prompt.js +0 -193
  119. package/src/mcp/tools/Developer Key Setup Guide/prompt.js +0 -71
  120. package/src/mcp/tools/Object Fields Retriever/prompt.js +0 -10
  121. package/src/mcp/tools/Object List Retriever/prompt.js +0 -10
  122. package/src/mcp/tools/ccdk/fetcher.js +0 -18
  123. package/src/mcp/tools/ccdk/handler.js +0 -98
  124. package/src/mcp/tools/ccdk/prompt.js +0 -453
  125. package/target/ccopenapi-0.0.3-classes.jar +0 -0
  126. package/target/ccopenapi-0.0.3.jar +0 -0
  127. package/target/maven-archiver/pom.properties +0 -3
  128. package/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +0 -18
  129. package/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +0 -19
  130. package/template/lib/ccopenapi-0.0.3.jar +0 -0
@@ -0,0 +1,842 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ function uniq(items) {
5
+ return Array.from(new Set((items || []).filter(Boolean)));
6
+ }
7
+
8
+ function toPascalCase(value) {
9
+ return String(value || '')
10
+ .replace(/\.[^.]+$/, '')
11
+ .split(/[^a-zA-Z0-9]+/)
12
+ .filter(Boolean)
13
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
14
+ .join('');
15
+ }
16
+
17
+ function toKebabCase(value) {
18
+ const base = String(value || '')
19
+ .replace(/\.[^.]+$/, '')
20
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
21
+ .replace(/[^a-zA-Z0-9]+/g, '-')
22
+ .replace(/^-+|-+$/g, '')
23
+ .toLowerCase();
24
+ return base || 'jsp-migrated-component';
25
+ }
26
+
27
+ function lineNumberAt(source, index) {
28
+ if (index <= 0) return 1;
29
+ let count = 1;
30
+ for (let i = 0; i < index && i < source.length; i += 1) {
31
+ if (source[i] === '\n') count += 1;
32
+ }
33
+ return count;
34
+ }
35
+
36
+ function extractTagBlock(source, tag) {
37
+ const start = source.indexOf(tag);
38
+ if (start === -1) return null;
39
+
40
+ const closeTag = '</cc>';
41
+ const contentStart = start + tag.length;
42
+ const closeStart = source.indexOf(closeTag, contentStart);
43
+ if (closeStart === -1) return null;
44
+
45
+ return {
46
+ tag,
47
+ start,
48
+ end: closeStart + closeTag.length,
49
+ innerStart: contentStart,
50
+ innerEnd: closeStart,
51
+ inner: source.slice(contentStart, closeStart)
52
+ };
53
+ }
54
+
55
+ function extractBraceBlock(source, openBraceIndex) {
56
+ let depth = 0;
57
+ let inSingle = false;
58
+ let inDouble = false;
59
+ let inBacktick = false;
60
+ let inLineComment = false;
61
+ let inBlockComment = false;
62
+ let escaped = false;
63
+
64
+ for (let i = openBraceIndex; i < source.length; i += 1) {
65
+ const ch = source[i];
66
+ const next = source[i + 1];
67
+
68
+ if (inLineComment) {
69
+ if (ch === '\n') inLineComment = false;
70
+ continue;
71
+ }
72
+
73
+ if (inBlockComment) {
74
+ if (ch === '*' && next === '/') {
75
+ inBlockComment = false;
76
+ i += 1;
77
+ }
78
+ continue;
79
+ }
80
+
81
+ if (inSingle) {
82
+ if (!escaped && ch === "'") inSingle = false;
83
+ escaped = ch === '\\' && !escaped;
84
+ continue;
85
+ }
86
+
87
+ if (inDouble) {
88
+ if (!escaped && ch === '"') inDouble = false;
89
+ escaped = ch === '\\' && !escaped;
90
+ continue;
91
+ }
92
+
93
+ if (inBacktick) {
94
+ if (!escaped && ch === '`') inBacktick = false;
95
+ escaped = ch === '\\' && !escaped;
96
+ continue;
97
+ }
98
+
99
+ if (ch === '/' && next === '/') {
100
+ inLineComment = true;
101
+ i += 1;
102
+ continue;
103
+ }
104
+
105
+ if (ch === '/' && next === '*') {
106
+ inBlockComment = true;
107
+ i += 1;
108
+ continue;
109
+ }
110
+
111
+ if (ch === "'") {
112
+ inSingle = true;
113
+ escaped = false;
114
+ continue;
115
+ }
116
+
117
+ if (ch === '"') {
118
+ inDouble = true;
119
+ escaped = false;
120
+ continue;
121
+ }
122
+
123
+ if (ch === '`') {
124
+ inBacktick = true;
125
+ escaped = false;
126
+ continue;
127
+ }
128
+
129
+ if (ch === '{') {
130
+ depth += 1;
131
+ } else if (ch === '}') {
132
+ depth -= 1;
133
+ if (depth === 0) {
134
+ return {
135
+ start: openBraceIndex,
136
+ end: i,
137
+ body: source.slice(openBraceIndex + 1, i)
138
+ };
139
+ }
140
+ }
141
+ }
142
+
143
+ return null;
144
+ }
145
+
146
+ function extractObjectApis(snippet) {
147
+ const objectApis = [];
148
+ const cqueryRegex = /cs\.(?:cquery|cqlQuery)\(\s*"([^"]+)"/g;
149
+ let match = cqueryRegex.exec(snippet);
150
+ while (match) {
151
+ objectApis.push(match[1]);
152
+ match = cqueryRegex.exec(snippet);
153
+ }
154
+ return uniq(objectApis);
155
+ }
156
+
157
+ function extractInvokers(snippet) {
158
+ const invokers = [];
159
+ const invokerRegex = /PageClsInvoker\(userInfo\)\.invoker\(\s*"([^"]+)"\s*,\s*"([^"]+)"/g;
160
+ let match = invokerRegex.exec(snippet);
161
+ while (match) {
162
+ invokers.push(`${match[1]}.${match[2]}`);
163
+ match = invokerRegex.exec(snippet);
164
+ }
165
+ return uniq(invokers);
166
+ }
167
+
168
+ function buildReasonBranches(source) {
169
+ const branches = [];
170
+ const reasonRegex = /if\s*\(\s*"([^"]+)"\.equals\(reason\)\s*\)\s*\{/g;
171
+ let match = reasonRegex.exec(source);
172
+ const methodNameCount = {};
173
+
174
+ while (match) {
175
+ const reason = match[1];
176
+ const matchStart = match.index;
177
+ const openBraceOffset = match[0].lastIndexOf('{');
178
+ const openBraceIndex = matchStart + openBraceOffset;
179
+ const block = extractBraceBlock(source, openBraceIndex);
180
+ if (!block) {
181
+ match = reasonRegex.exec(source);
182
+ continue;
183
+ }
184
+
185
+ const methodBase = `handle${toPascalCase(reason)}`;
186
+ methodNameCount[methodBase] = (methodNameCount[methodBase] || 0) + 1;
187
+ const methodName = methodNameCount[methodBase] > 1
188
+ ? `${methodBase}${methodNameCount[methodBase]}`
189
+ : methodBase;
190
+
191
+ const startLine = lineNumberAt(source, matchStart);
192
+ const endLine = lineNumberAt(source, block.end);
193
+
194
+ branches.push({
195
+ reason,
196
+ methodName,
197
+ start: matchStart,
198
+ end: block.end,
199
+ startLine,
200
+ endLine,
201
+ snippet: source.slice(matchStart, block.end + 1),
202
+ body: block.body,
203
+ objectApis: extractObjectApis(block.body),
204
+ invokers: extractInvokers(block.body)
205
+ });
206
+
207
+ match = reasonRegex.exec(source);
208
+ }
209
+
210
+ return branches;
211
+ }
212
+
213
+ function buildClassSource({ className, jspFileName, branches, hasHelperBlock, sourcePath }) {
214
+ const switchCases = branches.length > 0
215
+ ? branches.map(branch => ` case "${branch.reason}":\n return ${branch.methodName}(oppid);`).join('\n')
216
+ : ' default:\n return fail("未识别到 reason 分支,请手动补充迁移逻辑");';
217
+
218
+ const handlerMethods = branches.length > 0
219
+ ? branches.map(branch => {
220
+ const objectApiText = branch.objectApis.length > 0 ? branch.objectApis.join(', ') : '无';
221
+ const invokerText = branch.invokers.length > 0 ? branch.invokers.join(', ') : '无';
222
+ return `
223
+ /**
224
+ * TODO: 从 JSP 迁移 reason 分支: ${branch.reason}
225
+ * 来源: ${jspFileName}:${branch.startLine}
226
+ * 依赖对象: ${objectApiText}
227
+ * 依赖调用: ${invokerText}
228
+ */
229
+ private Map<String, Object> ${branch.methodName}(String oppid) {
230
+ Map<String, Object> result = new HashMap<>();
231
+ result.put("success", false);
232
+ result.put("oppid", oppid == null ? "" : oppid);
233
+ result.put("reason", "${branch.reason}");
234
+ result.put("message", "TODO: 请将 JSP 分支 '${branch.reason}' 迁移到该方法");
235
+ return result;
236
+ }
237
+ `;
238
+ }).join('\n')
239
+ : `
240
+ private Map<String, Object> handleUnclassified(String oppid) {
241
+ Map<String, Object> result = new HashMap<>();
242
+ result.put("success", false);
243
+ result.put("oppid", oppid == null ? "" : oppid);
244
+ result.put("message", "未识别到 reason 分支,请手动迁移");
245
+ return result;
246
+ }
247
+ `;
248
+
249
+ return `package classes.${className};
250
+
251
+ import com.cloudcc.core.*;
252
+ import java.util.*;
253
+ // @SOURCE_CONTENT_START
254
+ public class ${className} {
255
+ private final UserInfo userInfo;
256
+ private final CCService cs;
257
+
258
+ public ${className}(UserInfo userInfo) {
259
+ this(userInfo, new CCService(userInfo));
260
+ }
261
+
262
+ public ${className}(UserInfo userInfo, CCService cs) {
263
+ this.userInfo = userInfo;
264
+ this.cs = cs;
265
+ }
266
+
267
+ /**
268
+ * 自动生成的迁移入口。
269
+ * JSP来源: ${sourcePath}
270
+ * helper块存在: ${hasHelperBlock ? '是' : '否'}
271
+ */
272
+ public Map<String, Object> execute(Map<String, Object> input) {
273
+ String oppid = getString(input, "oppid");
274
+ String reason = getString(input, "reason");
275
+ return execute(oppid, reason);
276
+ }
277
+
278
+ public Map<String, Object> execute(String oppid, String reason) {
279
+ if ("".equals(oppid)) {
280
+ return fail("缺少参数 oppid");
281
+ }
282
+ if ("".equals(reason)) {
283
+ return fail("缺少参数 reason");
284
+ }
285
+
286
+ switch (reason) {
287
+ ${switchCases}
288
+ default:
289
+ return fail("不支持的 reason: " + reason);
290
+ }
291
+ }
292
+
293
+ ${handlerMethods}
294
+
295
+ private String getString(Map<String, Object> input, String key) {
296
+ if (input == null || key == null) {
297
+ return "";
298
+ }
299
+ Object value = input.get(key);
300
+ return value == null ? "" : String.valueOf(value);
301
+ }
302
+
303
+ private Map<String, Object> fail(String message) {
304
+ Map<String, Object> result = new HashMap<>();
305
+ result.put("success", false);
306
+ result.put("message", message);
307
+ return result;
308
+ }
309
+ }
310
+ // @SOURCE_CONTENT_END
311
+ `;
312
+ }
313
+
314
+ function buildClassTestSource({ className, firstReason }) {
315
+ const reason = firstReason || 'TODO_REASON';
316
+ return `package classes.${className};
317
+
318
+ import com.cloudcc.core.*;
319
+ import java.util.*;
320
+
321
+ public class ${className}Test {
322
+ public static void main(String[] args) {
323
+ ${className} service = new ${className}(new UserInfo());
324
+ Map<String, Object> input = new HashMap<>();
325
+ input.put("oppid", "demo_oppid");
326
+ input.put("reason", "${reason}");
327
+ Map<String, Object> result = service.execute(input);
328
+ System.out.println(result);
329
+ }
330
+ }
331
+ `;
332
+ }
333
+
334
+ function buildComponentSource({ componentName, className, jspFileName, reasons }) {
335
+ const reasonList = JSON.stringify(reasons, null, 2);
336
+ const defaultReason = reasons[0] || '';
337
+
338
+ return `<template>
339
+ <div class="jsp-migrated-wrapper">
340
+ <header class="migrated-header">
341
+ <h3>{{ componentInfo.compName }}</h3>
342
+ <p>Legacy JSP: {{ legacyJsp }}</p>
343
+ </header>
344
+
345
+ <section class="migrated-panel">
346
+ <label>Opportunity ID</label>
347
+ <input v-model.trim="form.oppid" type="text" placeholder="请输入 oppid" />
348
+
349
+ <label>Reason</label>
350
+ <select v-model="form.reason">
351
+ <option v-for="item in reasons" :key="item" :value="item">{{ item }}</option>
352
+ </select>
353
+
354
+ <button type="button" @click="handleExecute">执行迁移入口</button>
355
+ <p class="hint">{{ hint }}</p>
356
+ </section>
357
+
358
+ <section class="migrated-result" v-if="lastPayload">
359
+ <h4>最近触发参数</h4>
360
+ <pre>{{ formattedPayload }}</pre>
361
+ </section>
362
+ </div>
363
+ </template>
364
+
365
+ <script>
366
+ export default {
367
+ data() {
368
+ return {
369
+ componentInfo: {
370
+ component: "component-${componentName}",
371
+ compName: "compName-${componentName}",
372
+ compDesc: "Migrated from JSP ${jspFileName}",
373
+ },
374
+ legacyJsp: "${jspFileName}",
375
+ targetClass: "${className}",
376
+ reasons: ${reasonList},
377
+ form: {
378
+ oppid: "",
379
+ reason: "${defaultReason}",
380
+ },
381
+ hint: "TODO: 在宿主中接入自定义类调用逻辑。",
382
+ lastPayload: null,
383
+ };
384
+ },
385
+ computed: {
386
+ formattedPayload() {
387
+ return this.lastPayload ? JSON.stringify(this.lastPayload, null, 2) : "";
388
+ },
389
+ },
390
+ methods: {
391
+ handleExecute() {
392
+ const payload = {
393
+ oppid: this.form.oppid,
394
+ reason: this.form.reason,
395
+ };
396
+ this.lastPayload = payload;
397
+
398
+ if (!payload.oppid || !payload.reason) {
399
+ this.hint = "请先输入 oppid 并选择 reason。";
400
+ return;
401
+ }
402
+
403
+ this.$emit("execute-migrated-action", {
404
+ className: this.targetClass,
405
+ payload,
406
+ });
407
+ this.hint = \`已触发事件 execute-migrated-action,请在宿主中调用 \${this.targetClass}.execute(payload)\`;
408
+ },
409
+ },
410
+ };
411
+ </script>
412
+
413
+ <style lang="scss" scoped>
414
+ .jsp-migrated-wrapper {
415
+ padding: 16px;
416
+ background: linear-gradient(160deg, #f2f6ff 0%, #ffffff 70%);
417
+ border: 1px solid #dbe4ff;
418
+ border-radius: 10px;
419
+ color: #1d2b53;
420
+ }
421
+
422
+ .migrated-header h3 {
423
+ margin: 0 0 6px;
424
+ font-size: 18px;
425
+ }
426
+
427
+ .migrated-header p {
428
+ margin: 0;
429
+ font-size: 12px;
430
+ color: #5b6b91;
431
+ }
432
+
433
+ .migrated-panel {
434
+ margin-top: 14px;
435
+ display: grid;
436
+ gap: 8px;
437
+ }
438
+
439
+ .migrated-panel label {
440
+ font-size: 12px;
441
+ font-weight: 600;
442
+ }
443
+
444
+ .migrated-panel input,
445
+ .migrated-panel select {
446
+ height: 34px;
447
+ border: 1px solid #c9d5ff;
448
+ border-radius: 6px;
449
+ padding: 0 10px;
450
+ outline: none;
451
+ }
452
+
453
+ .migrated-panel button {
454
+ margin-top: 4px;
455
+ width: 160px;
456
+ height: 34px;
457
+ border: 0;
458
+ border-radius: 6px;
459
+ background: #3451ff;
460
+ color: #fff;
461
+ cursor: pointer;
462
+ }
463
+
464
+ .hint {
465
+ margin: 0;
466
+ font-size: 12px;
467
+ color: #4f5f88;
468
+ }
469
+
470
+ .migrated-result {
471
+ margin-top: 16px;
472
+ background: #0f1530;
473
+ border-radius: 8px;
474
+ padding: 10px;
475
+ }
476
+
477
+ .migrated-result h4 {
478
+ margin: 0 0 8px;
479
+ font-size: 12px;
480
+ color: #d7def7;
481
+ }
482
+
483
+ .migrated-result pre {
484
+ margin: 0;
485
+ white-space: pre-wrap;
486
+ word-break: break-word;
487
+ color: #b8c7ff;
488
+ font-size: 12px;
489
+ }
490
+ </style>
491
+ `;
492
+ }
493
+
494
+ function buildReport({
495
+ sourcePath,
496
+ helpDocPath,
497
+ jspContent,
498
+ helpDocContent,
499
+ pageDirective,
500
+ ccBlock,
501
+ ccBangBlock,
502
+ visualSegment,
503
+ branches,
504
+ objectApis,
505
+ invokers,
506
+ className,
507
+ componentName,
508
+ reportPath,
509
+ mode
510
+ }) {
511
+ const lines = [];
512
+ lines.push('# JSP Migration Report');
513
+ lines.push('');
514
+ lines.push(`- generatedAt: ${new Date().toISOString()}`);
515
+ lines.push(`- mode: ${mode}`);
516
+ lines.push(`- sourceJsp: ${sourcePath}`);
517
+ lines.push(`- helpDoc: ${helpDocPath || '未提供'}`);
518
+ lines.push(`- outputClass: classes/${className}`);
519
+ lines.push(`- outputComponent: plugins/${componentName}`);
520
+ lines.push(`- reportFile: ${reportPath}`);
521
+ lines.push('');
522
+
523
+ lines.push('## Summary');
524
+ lines.push('');
525
+ lines.push(`- jspLines: ${jspContent.split(/\r?\n/).length}`);
526
+ lines.push(`- reasonBranches: ${branches.length}`);
527
+ lines.push(`- helperBlockFound: ${ccBangBlock ? 'yes' : 'no'}`);
528
+ lines.push(`- visualSegmentFound: ${visualSegment ? 'yes' : 'no'}`);
529
+ lines.push(`- distinctObjectApis: ${objectApis.length}`);
530
+ lines.push(`- distinctInvokers: ${invokers.length}`);
531
+ lines.push('');
532
+
533
+ lines.push('## Page Directive');
534
+ lines.push('');
535
+ lines.push(`- cc:page: ${pageDirective || '未检测到'}`);
536
+ lines.push('');
537
+
538
+ lines.push('## Legacy Blocks');
539
+ lines.push('');
540
+ lines.push(`- <cc>: ${ccBlock ? `${lineNumberAt(jspContent, ccBlock.start)}-${lineNumberAt(jspContent, ccBlock.end)}` : 'not found'}`);
541
+ lines.push(`- <cc!>: ${ccBangBlock ? `${lineNumberAt(jspContent, ccBangBlock.start)}-${lineNumberAt(jspContent, ccBangBlock.end)}` : 'not found'}`);
542
+ lines.push(`- visualHtmlSegment: ${visualSegment ? `${lineNumberAt(jspContent, visualSegment.start)}-${lineNumberAt(jspContent, visualSegment.end)}` : 'not found'}`);
543
+ lines.push('');
544
+
545
+ lines.push('## Global Dependencies');
546
+ lines.push('');
547
+ lines.push(`- cquery/cqlQuery objects: ${objectApis.length > 0 ? objectApis.join(', ') : '无'}`);
548
+ lines.push(`- PageClsInvoker calls: ${invokers.length > 0 ? invokers.join(', ') : '无'}`);
549
+ lines.push('');
550
+
551
+ lines.push('## Reason Branches');
552
+ lines.push('');
553
+ if (branches.length === 0) {
554
+ lines.push('- 未识别到 `if("xxx".equals(reason))` 分支,请手工分析。');
555
+ } else {
556
+ branches.forEach(branch => {
557
+ lines.push(`### ${branch.reason}`);
558
+ lines.push(`- lineRange: ${branch.startLine}-${branch.endLine}`);
559
+ lines.push(`- generatedMethod: ${branch.methodName}`);
560
+ lines.push(`- objectApis: ${branch.objectApis.length > 0 ? branch.objectApis.join(', ') : '无'}`);
561
+ lines.push(`- invokers: ${branch.invokers.length > 0 ? branch.invokers.join(', ') : '无'}`);
562
+ lines.push('');
563
+ });
564
+ }
565
+
566
+ lines.push('## Help Doc Snapshot');
567
+ lines.push('');
568
+ if (!helpDocContent) {
569
+ lines.push('- 未提供帮助文档路径,跳过。');
570
+ } else {
571
+ const snapshot = helpDocContent
572
+ .split(/\r?\n/)
573
+ .map(line => line.trim())
574
+ .filter(Boolean)
575
+ .slice(0, 20);
576
+ if (snapshot.length === 0) {
577
+ lines.push('- 帮助文档为空或不可解析。');
578
+ } else {
579
+ snapshot.forEach(line => lines.push(`- ${line}`));
580
+ }
581
+ }
582
+
583
+ lines.push('');
584
+ lines.push('## Next Actions');
585
+ lines.push('');
586
+ lines.push(`- 在 \`classes/${className}/${className}.java\` 完成各 reason 分支迁移。`);
587
+ lines.push(`- 在 \`plugins/${componentName}/${componentName}.vue\` 接入真实触发流程。`);
588
+ lines.push('- 按报告中的 object/invoker 依赖逐项验证。');
589
+
590
+ return `${lines.join('\n')}\n`;
591
+ }
592
+
593
+ function parseMigrationInput({ jspPath, className, componentName }) {
594
+ const jspFileName = path.basename(jspPath);
595
+ const jspBaseName = jspFileName.replace(/\.[^.]+$/, '');
596
+
597
+ const resolvedClassName = className && className.trim()
598
+ ? className.trim()
599
+ : `${toPascalCase(jspBaseName)}MigratedService`;
600
+
601
+ const resolvedComponentName = componentName && componentName.trim()
602
+ ? componentName.trim()
603
+ : `${toKebabCase(jspBaseName)}-migrated`;
604
+
605
+ return {
606
+ jspFileName,
607
+ jspBaseName,
608
+ resolvedClassName,
609
+ resolvedComponentName
610
+ };
611
+ }
612
+
613
+ function prepareAnalysis({ jspContent, sourcePath }) {
614
+ const ccBlock = extractTagBlock(jspContent, '<cc>');
615
+ const ccBangBlock = extractTagBlock(jspContent, '<cc!>');
616
+ const pageMatch = jspContent.match(/<cc:page\s+([^>]*?)\/>/);
617
+ const pageDirective = pageMatch ? pageMatch[0] : '';
618
+
619
+ let visualSegment = null;
620
+ if (ccBlock) {
621
+ const visualStart = ccBlock.end;
622
+ const visualEnd = ccBangBlock ? ccBangBlock.start : jspContent.length;
623
+ const rawVisual = jspContent.slice(visualStart, visualEnd);
624
+ if (/<\s*(div|table|form|section|header|main|script|style|span|input|button|ul|ol|li)\b/i.test(rawVisual)) {
625
+ visualSegment = {
626
+ start: visualStart,
627
+ end: visualEnd,
628
+ content: rawVisual
629
+ };
630
+ }
631
+ }
632
+
633
+ const branches = buildReasonBranches(jspContent);
634
+ const objectApis = extractObjectApis(jspContent);
635
+ const invokers = extractInvokers(jspContent);
636
+
637
+ return {
638
+ sourcePath,
639
+ ccBlock,
640
+ ccBangBlock,
641
+ pageDirective,
642
+ visualSegment,
643
+ branches,
644
+ objectApis,
645
+ invokers
646
+ };
647
+ }
648
+
649
+ function buildResultText({
650
+ mode,
651
+ sourcePath,
652
+ classPath,
653
+ componentPath,
654
+ reportPath,
655
+ className,
656
+ componentName,
657
+ reasonCount,
658
+ objectApiCount,
659
+ invokerCount,
660
+ createdFiles
661
+ }) {
662
+ const lines = [];
663
+ lines.push(`✓ JSP 迁移${mode === 'dry-run' ? '分析' : '生成'}完成`);
664
+ lines.push('');
665
+ lines.push(`source: ${sourcePath}`);
666
+ lines.push(`class: ${className}`);
667
+ lines.push(`component: ${componentName}`);
668
+ lines.push(`reason分支: ${reasonCount}`);
669
+ lines.push(`对象依赖数: ${objectApiCount}`);
670
+ lines.push(`invoker依赖数: ${invokerCount}`);
671
+ lines.push('');
672
+
673
+ if (mode === 'dry-run') {
674
+ lines.push('dry-run 模式未写入任何文件。');
675
+ lines.push(`报告预期路径: ${reportPath}`);
676
+ } else {
677
+ lines.push('已生成文件:');
678
+ createdFiles.forEach(file => lines.push(`- ${file}`));
679
+ lines.push('');
680
+ lines.push(`class目录: ${classPath}`);
681
+ lines.push(`component目录: ${componentPath}`);
682
+ lines.push(`报告路径: ${reportPath}`);
683
+ }
684
+
685
+ return lines.join('\n');
686
+ }
687
+
688
+ async function runMigration({
689
+ jspPath,
690
+ helpDocPath,
691
+ projectPath = process.cwd(),
692
+ outputProjectPath,
693
+ className,
694
+ componentName,
695
+ reportDir = 'migration-report',
696
+ mode = 'apply',
697
+ overwrite = false
698
+ }) {
699
+ try {
700
+ if (!jspPath) {
701
+ return {
702
+ content: [{ type: 'text', text: '✗ 参数缺失: jspPath 必填' }]
703
+ };
704
+ }
705
+
706
+ const resolvedProjectPath = path.resolve(projectPath);
707
+ const resolvedOutputProjectPath = path.resolve(outputProjectPath || projectPath);
708
+ const sourcePath = path.isAbsolute(jspPath)
709
+ ? jspPath
710
+ : path.resolve(resolvedProjectPath, jspPath);
711
+
712
+ if (!fs.existsSync(sourcePath)) {
713
+ return {
714
+ content: [{ type: 'text', text: `✗ JSP 文件不存在: ${sourcePath}` }]
715
+ };
716
+ }
717
+
718
+ const helpPath = helpDocPath
719
+ ? (path.isAbsolute(helpDocPath) ? helpDocPath : path.resolve(resolvedProjectPath, helpDocPath))
720
+ : null;
721
+
722
+ const jspContent = fs.readFileSync(sourcePath, 'utf8');
723
+ const helpDocContent = helpPath && fs.existsSync(helpPath)
724
+ ? fs.readFileSync(helpPath, 'utf8')
725
+ : '';
726
+
727
+ const naming = parseMigrationInput({ jspPath: sourcePath, className, componentName });
728
+ const { jspFileName, jspBaseName, resolvedClassName, resolvedComponentName } = naming;
729
+
730
+ const analysis = prepareAnalysis({ jspContent, sourcePath });
731
+ const classSource = buildClassSource({
732
+ className: resolvedClassName,
733
+ jspFileName,
734
+ branches: analysis.branches,
735
+ hasHelperBlock: Boolean(analysis.ccBangBlock),
736
+ sourcePath
737
+ });
738
+
739
+ const classTestSource = buildClassTestSource({
740
+ className: resolvedClassName,
741
+ firstReason: analysis.branches[0] ? analysis.branches[0].reason : ''
742
+ });
743
+
744
+ const componentSource = buildComponentSource({
745
+ componentName: resolvedComponentName,
746
+ className: resolvedClassName,
747
+ jspFileName,
748
+ reasons: analysis.branches.map(item => item.reason)
749
+ });
750
+
751
+ const reportPath = path.join(resolvedOutputProjectPath, reportDir, `${jspBaseName}.migration.md`);
752
+ const reportContent = buildReport({
753
+ sourcePath,
754
+ helpDocPath: helpPath,
755
+ jspContent,
756
+ helpDocContent,
757
+ pageDirective: analysis.pageDirective,
758
+ ccBlock: analysis.ccBlock,
759
+ ccBangBlock: analysis.ccBangBlock,
760
+ visualSegment: analysis.visualSegment,
761
+ branches: analysis.branches,
762
+ objectApis: analysis.objectApis,
763
+ invokers: analysis.invokers,
764
+ className: resolvedClassName,
765
+ componentName: resolvedComponentName,
766
+ reportPath,
767
+ mode
768
+ });
769
+
770
+ const classDir = path.join(resolvedOutputProjectPath, 'classes', resolvedClassName);
771
+ const componentDir = path.join(resolvedOutputProjectPath, 'plugins', resolvedComponentName);
772
+
773
+ const fileMap = {
774
+ [path.join(classDir, `${resolvedClassName}.java`)]: classSource,
775
+ [path.join(classDir, `${resolvedClassName}Test.java`)]: classTestSource,
776
+ [path.join(classDir, 'config.json')]: JSON.stringify({
777
+ name: resolvedClassName,
778
+ version: '2'
779
+ }, null, 4),
780
+ [path.join(componentDir, `${resolvedComponentName}.vue`)]: componentSource,
781
+ [path.join(componentDir, 'config.json')]: JSON.stringify({
782
+ component: `component-${resolvedComponentName}`,
783
+ compName: `compName-${resolvedComponentName}`,
784
+ compDesc: `Migrated from JSP ${jspFileName}`
785
+ }),
786
+ [reportPath]: reportContent
787
+ };
788
+
789
+ if (mode === 'apply') {
790
+ const existingFiles = Object.keys(fileMap).filter(file => fs.existsSync(file));
791
+ if (!overwrite && existingFiles.length > 0) {
792
+ return {
793
+ content: [{
794
+ type: 'text',
795
+ text: `✗ 目标文件已存在(可设置 overwrite=true 覆盖):\n${existingFiles.map(item => `- ${item}`).join('\n')}`
796
+ }]
797
+ };
798
+ }
799
+
800
+ Object.keys(fileMap).forEach(filePath => {
801
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
802
+ fs.writeFileSync(filePath, fileMap[filePath], 'utf8');
803
+ });
804
+ }
805
+
806
+ const text = buildResultText({
807
+ mode,
808
+ sourcePath,
809
+ classPath: classDir,
810
+ componentPath: componentDir,
811
+ reportPath,
812
+ className: resolvedClassName,
813
+ componentName: resolvedComponentName,
814
+ reasonCount: analysis.branches.length,
815
+ objectApiCount: analysis.objectApis.length,
816
+ invokerCount: analysis.invokers.length,
817
+ createdFiles: Object.keys(fileMap)
818
+ });
819
+
820
+ return {
821
+ content: [{ type: 'text', text }]
822
+ };
823
+ } catch (error) {
824
+ return {
825
+ content: [{ type: 'text', text: `✗ JSP 迁移失败: ${error.message}` }]
826
+ };
827
+ }
828
+ }
829
+
830
+ async function analyzeJspMigration(params) {
831
+ return runMigration({ ...(params || {}), mode: 'dry-run' });
832
+ }
833
+
834
+ async function splitJspToCloudcc(params) {
835
+ return runMigration(params || {});
836
+ }
837
+
838
+ module.exports = {
839
+ analyzeJspMigration,
840
+ splitJspToCloudcc,
841
+ runMigrationForCli: runMigration
842
+ };