vk-typescript-cli 1.0.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.
Files changed (119) hide show
  1. package/Jenkinsfile +48 -0
  2. package/README.md +300 -0
  3. package/bin/sf.js +438 -0
  4. package/eslint.config.js +90 -0
  5. package/gulpfile.js +157 -0
  6. package/lib/commander.js +3370 -0
  7. package/lib/inquirer-prompts.js +2038 -0
  8. package/license +13 -0
  9. package/package.json +43 -0
  10. package/smoke-test.mjs +38 -0
  11. package/src/commands/add.js +276 -0
  12. package/src/commands/info.js +82 -0
  13. package/src/commands/interactive.js +493 -0
  14. package/src/commands/mcp.js +135 -0
  15. package/src/commands/new.js +264 -0
  16. package/src/commands/skills.js +42 -0
  17. package/src/commands/theme.js +200 -0
  18. package/src/commands/upgrade.js +219 -0
  19. package/src/constants.js +161 -0
  20. package/src/generators/angular.js +878 -0
  21. package/src/generators/nextjs.js +817 -0
  22. package/src/generators/react.js +769 -0
  23. package/src/generators/typescript.js +258 -0
  24. package/src/generators/vue.js +800 -0
  25. package/src/templates/angular/chart.component.html +23 -0
  26. package/src/templates/angular/chart.component.ts +172 -0
  27. package/src/templates/angular/diagram.component.html +13 -0
  28. package/src/templates/angular/diagram.component.ts +224 -0
  29. package/src/templates/angular/docx-editor.component.html +18 -0
  30. package/src/templates/angular/docx-editor.component.ts +37 -0
  31. package/src/templates/angular/filemanager.component.html +4 -0
  32. package/src/templates/angular/filemanager.component.ts +47 -0
  33. package/src/templates/angular/gantt.component.html +32 -0
  34. package/src/templates/angular/gantt.component.ts +95 -0
  35. package/src/templates/angular/grid.component.html +12 -0
  36. package/src/templates/angular/grid.component.ts +58 -0
  37. package/src/templates/angular/pdf-viewer.component.html +9 -0
  38. package/src/templates/angular/pdf-viewer.component.ts +48 -0
  39. package/src/templates/angular/rte.component.html +7 -0
  40. package/src/templates/angular/rte.component.ts +13 -0
  41. package/src/templates/angular/scheduler.component.html +17 -0
  42. package/src/templates/angular/scheduler.component.ts +75 -0
  43. package/src/templates/angular/spreadsheet-editor.component.html +42 -0
  44. package/src/templates/angular/spreadsheet-editor.component.ts +61 -0
  45. package/src/templates/chart-template.js +20 -0
  46. package/src/templates/diagram-template.js +26 -0
  47. package/src/templates/docx-template.js +20 -0
  48. package/src/templates/filemanager-template.js +20 -0
  49. package/src/templates/gantt-template.js +20 -0
  50. package/src/templates/grid-template.js +20 -0
  51. package/src/templates/pdfviewer-template.js +20 -0
  52. package/src/templates/react/Chart.jsx +81 -0
  53. package/src/templates/react/Chart.tsx +81 -0
  54. package/src/templates/react/DOCXEditor.jsx +37 -0
  55. package/src/templates/react/DOCXEditor.tsx +37 -0
  56. package/src/templates/react/Diagram.jsx +192 -0
  57. package/src/templates/react/Diagram.tsx +195 -0
  58. package/src/templates/react/FileManager.jsx +13 -0
  59. package/src/templates/react/FileManager.tsx +13 -0
  60. package/src/templates/react/Gantt.jsx +89 -0
  61. package/src/templates/react/Gantt.tsx +89 -0
  62. package/src/templates/react/Grid.jsx +45 -0
  63. package/src/templates/react/Grid.tsx +45 -0
  64. package/src/templates/react/PDFViewer.jsx +16 -0
  65. package/src/templates/react/PDFViewer.tsx +16 -0
  66. package/src/templates/react/RTE.jsx +14 -0
  67. package/src/templates/react/RTE.tsx +14 -0
  68. package/src/templates/react/Scheduler.jsx +55 -0
  69. package/src/templates/react/Scheduler.tsx +55 -0
  70. package/src/templates/react/SpreadsheetEditor.jsx +96 -0
  71. package/src/templates/react/SpreadsheetEditor.tsx +98 -0
  72. package/src/templates/rte-template.js +26 -0
  73. package/src/templates/scheduler-template.js +20 -0
  74. package/src/templates/spreadheet-template.js +20 -0
  75. package/src/templates/typescript/RTE.ts +33 -0
  76. package/src/templates/typescript/SpreadsheetEditor.ts +39 -0
  77. package/src/templates/typescript/chart.ts +37 -0
  78. package/src/templates/typescript/diagram.ts +30 -0
  79. package/src/templates/typescript/docxeditor.ts +30 -0
  80. package/src/templates/typescript/filemanager.ts +41 -0
  81. package/src/templates/typescript/gantt.ts +88 -0
  82. package/src/templates/typescript/grid.ts +30 -0
  83. package/src/templates/typescript/pdfviewer.ts +8 -0
  84. package/src/templates/typescript/scheduler.ts +39 -0
  85. package/src/templates/vue/Chart.vue +257 -0
  86. package/src/templates/vue/ChartView.vue +7 -0
  87. package/src/templates/vue/DOCXEditor.vue +61 -0
  88. package/src/templates/vue/DOCXEditorView.vue +7 -0
  89. package/src/templates/vue/Diagram.vue +245 -0
  90. package/src/templates/vue/DiagramView.vue +7 -0
  91. package/src/templates/vue/FileManager.vue +58 -0
  92. package/src/templates/vue/FileManagerView.vue +7 -0
  93. package/src/templates/vue/Gantt.vue +129 -0
  94. package/src/templates/vue/GanttView.vue +7 -0
  95. package/src/templates/vue/Grid.vue +141 -0
  96. package/src/templates/vue/GridView.vue +7 -0
  97. package/src/templates/vue/PDFViewer.vue +58 -0
  98. package/src/templates/vue/PDFViewerView.vue +7 -0
  99. package/src/templates/vue/RTE.vue +27 -0
  100. package/src/templates/vue/RTEView.vue +7 -0
  101. package/src/templates/vue/Scheduler.vue +94 -0
  102. package/src/templates/vue/SchedulerView.vue +7 -0
  103. package/src/templates/vue/SpreadsheetEditor.vue +115 -0
  104. package/src/templates/vue/SpreadsheetEditorView.vue +7 -0
  105. package/src/utils/ansi-colors.js +38 -0
  106. package/src/utils/banner.js +179 -0
  107. package/src/utils/common.js +64 -0
  108. package/src/utils/config-generator.js +73 -0
  109. package/src/utils/folder-structure.js +26 -0
  110. package/src/utils/logger.js +19 -0
  111. package/src/utils/mcp-generator.js +122 -0
  112. package/src/utils/package-manager.js +128 -0
  113. package/src/utils/project-bootstrap.js +98 -0
  114. package/src/utils/project-detector.js +195 -0
  115. package/src/utils/prompt-utils.js +29 -0
  116. package/src/utils/run-command.js +110 -0
  117. package/src/utils/skills-installer.js +69 -0
  118. package/src/utils/template-file.js +35 -0
  119. package/src/utils/theme-loader.js +47 -0
@@ -0,0 +1,493 @@
1
+ import readline from 'node:readline';
2
+ import { input } from '../../lib/inquirer-prompts.js';
3
+ import { createNewCommand } from './new.js';
4
+ import { addComponentCommand } from './add.js';
5
+ import { mcpAddCommand } from './mcp.js';
6
+ import { skillsAddCommand } from './skills.js';
7
+ // import { upgradeCommand } from './upgrade.js';
8
+ import { themeSwitchCommand } from './theme.js';
9
+ import { infoCommand } from './info.js';
10
+ import { logger } from '../utils/logger.js';
11
+ import { ansi, rgb, wrap } from '../utils/ansi-colors.js';
12
+ import { safePrompt, selectWithIndentedChoices } from '../utils/prompt-utils.js';
13
+ import { runCommand } from '../utils/run-command.js';
14
+
15
+ const colors = {
16
+ cyan: (text) => wrap(text, ansi.cyan),
17
+ white: (text) => wrap(text, ansi.white),
18
+ dim: (text) => wrap(text, ansi.dim),
19
+ hex: (hex) => (text) => rgb(text, hex),
20
+ boldHex: (hex) => (text) => rgb(text, hex, { bold: true })
21
+ };
22
+
23
+ const MENU_ITEMS = [
24
+ {
25
+ category: 'PROJECT MANAGEMENT',
26
+ icon: '📦',
27
+ items: [
28
+ { id: 'new-project', title: 'Create a new project', command: 'sf new' },
29
+ { id: 'add-component', title: 'Add component to an existing project', command: 'sf add' },
30
+ { id: 'theme', title: 'Add theme to an existing project', command: 'sf theme switch' },
31
+ // { id: 'upgrade-packages', title: 'Upgrade Syncfusion packages', command: 'sf upgrade' },
32
+ { id: 'show-info', title: 'Show environment & project information', command: 'sf info' }
33
+ ]
34
+ },
35
+ {
36
+ category: 'AI & SKILLS',
37
+ icon: '🤖',
38
+ items: [
39
+ { id: 'mcp-add', title: 'Setup Model Context Protocol (MCP)', command: 'sf mcp init' },
40
+ { id: 'skills-add', title: 'Add component skills to AI Agent', command: 'sf skills install' }
41
+ ]
42
+ },
43
+ {
44
+ category: 'HELP & EXIT',
45
+ icon: 'ℹ️',
46
+ items: [
47
+ { id: 'help', title: 'View help', command: 'sf --help' },
48
+ { id: 'exit', title: 'Exit', command: 'Ctrl + C' }
49
+ ]
50
+ }
51
+ ];
52
+
53
+ function buildMenuRows() {
54
+ const rows = [];
55
+
56
+ const allItems = MENU_ITEMS.flatMap(cat => cat.items);
57
+ const maxTitleLength = Math.max(...allItems.map(item => item.title.length));
58
+ const commandGap = 12;
59
+
60
+ MENU_ITEMS.forEach((cat, catIndex) => {
61
+ if (catIndex > 0) {
62
+ rows.push({ type: 'space' });
63
+ }
64
+
65
+ rows.push({
66
+ type: 'header',
67
+ text: `${cat.icon} ${cat.category}`
68
+ });
69
+
70
+ for (const item of cat.items) {
71
+ rows.push({
72
+ type: 'item',
73
+ id: item.id,
74
+ title: item.title,
75
+ command: item.command,
76
+ paddedTitle: item.title.padEnd(maxTitleLength + commandGap, ' ')
77
+ });
78
+ }
79
+ });
80
+
81
+ return rows;
82
+ }
83
+
84
+ function renderMenuRow(row, selected) {
85
+ if (row.type === 'space') {
86
+ return '';
87
+ }
88
+
89
+ if (row.type === 'header') {
90
+ return ` \x1b[0m\x1b[1m\x1b[97m${row.text}\x1b[0m`;
91
+ }
92
+
93
+ const pointer = selected ? colors.cyan('>') : ' ';
94
+
95
+ // Increase this value to move command title right
96
+ const itemIndent = ' ';
97
+
98
+ const title = selected
99
+ ? colors.boldHex('#00AFFF')(row.paddedTitle)
100
+ : colors.white(row.paddedTitle);
101
+
102
+ const command = row.command
103
+ ? colors.dim(row.command)
104
+ : '';
105
+
106
+ return `${pointer}${itemIndent}${title}${command}`;
107
+ }
108
+
109
+ function promptInteractiveCommand() {
110
+ return new Promise((resolve) => {
111
+ const rows = buildMenuRows();
112
+ const selectableRows = rows
113
+ .map((row, index) => ({ row, index }))
114
+ .filter(entry => entry.row.type === 'item');
115
+
116
+ let selectedIndex = 0;
117
+
118
+ const renderAll = () => {
119
+ process.stdout.write('\n');
120
+ process.stdout.write(colors.boldHex('#00AFFF')('? What would you like to do? '));
121
+ process.stdout.write(colors.dim('(Use arrow keys)\n'));
122
+ process.stdout.write('\n');
123
+
124
+ rows.forEach((row, rowIndex) => {
125
+ const isSelected =
126
+ selectableRows[selectedIndex].index === rowIndex;
127
+
128
+ process.stdout.write(renderMenuRow(row, isSelected) + '\n');
129
+ });
130
+ };
131
+
132
+ const updateRow = (rowIndex) => {
133
+ const linesFromBottom = rows.length - rowIndex;
134
+
135
+ readline.moveCursor(process.stdout, 0, -linesFromBottom);
136
+ readline.cursorTo(process.stdout, 0);
137
+ readline.clearLine(process.stdout, 0);
138
+
139
+ const isSelected =
140
+ selectableRows[selectedIndex].index === rowIndex;
141
+
142
+ process.stdout.write(renderMenuRow(rows[rowIndex], isSelected));
143
+
144
+ readline.cursorTo(process.stdout, 0);
145
+ readline.moveCursor(process.stdout, 0, linesFromBottom);
146
+ };
147
+
148
+ const cleanup = () => {
149
+ process.stdin.setRawMode(false);
150
+ process.stdin.pause();
151
+ process.stdin.removeAllListeners('keypress');
152
+ process.stdout.write('\n');
153
+ };
154
+
155
+ readline.emitKeypressEvents(process.stdin);
156
+ process.stdin.setRawMode(true);
157
+ process.stdin.resume();
158
+
159
+ renderAll();
160
+
161
+ process.stdin.on('keypress', (_, key) => {
162
+ if (!key) {
163
+ return;
164
+ }
165
+
166
+ if (key.ctrl && key.name === 'c') {
167
+ cleanup();
168
+ process.exit(0);
169
+ }
170
+
171
+ const previousSelectedRow = selectableRows[selectedIndex].index;
172
+
173
+ if (key.name === 'up') {
174
+ selectedIndex =
175
+ selectedIndex === 0
176
+ ? selectableRows.length - 1
177
+ : selectedIndex - 1;
178
+ } else if (key.name === 'down') {
179
+ selectedIndex =
180
+ selectedIndex === selectableRows.length - 1
181
+ ? 0
182
+ : selectedIndex + 1;
183
+ } else if (key.name === 'return') {
184
+ const selectedCommand = selectableRows[selectedIndex].row.id;
185
+ cleanup();
186
+ resolve(selectedCommand);
187
+ return;
188
+ } else {
189
+ return;
190
+ }
191
+
192
+ const currentSelectedRow = selectableRows[selectedIndex].index;
193
+
194
+ updateRow(previousSelectedRow);
195
+ updateRow(currentSelectedRow);
196
+ });
197
+ });
198
+ }
199
+
200
+ async function runCliHelp() {
201
+ try {
202
+ if (process.platform === 'win32') {
203
+ await runCommand('cmd.exe', ['/d', '/s', '/c', 'sf --help']);
204
+ } else {
205
+ await runCommand('sf', ['--help']);
206
+ }
207
+ } catch (error) {
208
+ logger.warning(`\n⚠️ Unable to show help: ${error.message}\n`);
209
+ logger.info('Run: sf --help\n');
210
+ }
211
+ }
212
+
213
+ async function executeCommand(commandId) {
214
+ switch (commandId) {
215
+ case 'new-project':
216
+ await runNewProjectInteractive();
217
+ break;
218
+ case 'add-component':
219
+ await addComponentCommand();
220
+ break;
221
+ case 'mcp-add':
222
+ await mcpAddCommand({ promptEditor: true });
223
+ break;
224
+ case 'skills-add':
225
+ await skillsAddCommand({ promptInstallMode: true });
226
+ break;
227
+ case 'theme':
228
+ await themeSwitchCommand();
229
+ break;
230
+ // case 'upgrade-packages':
231
+ // await upgradeCommand();
232
+ // break;
233
+ case 'show-info':
234
+ await infoCommand();
235
+ break;
236
+ case 'help':
237
+ await runCliHelp();
238
+ break;
239
+ case 'exit':
240
+ logger.info('\n' + colors.boldHex('#00AFFF')('👋 Thanks for using Syncfusion CLI!\n'));
241
+ process.exit(0);
242
+ break;
243
+ default:
244
+ logger.warning(`Unknown command: ${commandId}`);
245
+ break;
246
+ }
247
+ }
248
+
249
+ function yesNoKeyPrompt(message, defaultValue = false, options = {}) {
250
+ return new Promise((resolve) => {
251
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
252
+ resolve(defaultValue);
253
+ return;
254
+ }
255
+ const suffix = defaultValue ? ' [Y/n] ' : ' [y/N] ';
256
+ const blue = options.blue === true;
257
+ const cleanup = () => {
258
+ process.stdin.setRawMode(false);
259
+ process.stdin.pause();
260
+ process.stdin.removeAllListeners('keypress', onKeypress);
261
+ };
262
+ const renderQuestion = () => {
263
+ if (blue) {
264
+ process.stdout.write(colors.boldHex('#00AFFF')(`? ${message}`));
265
+ } else {
266
+ process.stdout.write(`? ${message}`);
267
+ }
268
+
269
+ process.stdout.write(colors.dim(suffix));
270
+ };
271
+ const finish = (value) => {
272
+ const answer = colors.hex('#00AFFF')(value ? 'yes' : 'no');
273
+ readline.cursorTo(process.stdout, 0);
274
+ readline.clearLine(process.stdout, 0);
275
+ process.stdout.write(
276
+ `${wrap('✔', ansi.green)} ${wrap(message, ansi.bold)} ${answer}\n`
277
+ );
278
+ cleanup();
279
+ resolve(value);
280
+ };
281
+ const onKeypress = (str, key) => {
282
+ if (key?.ctrl && key.name === 'c') {
283
+ cleanup();
284
+ process.stdout.write('\n');
285
+ process.exit(0);
286
+ }
287
+ if (str === 'y' || str === 'Y') {
288
+ finish(true);
289
+ return;
290
+ }
291
+ if (str === 'n' || str === 'N') {
292
+ finish(false);
293
+ return;
294
+ }
295
+ if (key?.name === 'return') {
296
+ finish(defaultValue);
297
+ }
298
+ };
299
+
300
+ readline.emitKeypressEvents(process.stdin);
301
+ renderQuestion();
302
+
303
+ process.stdin.setRawMode(true);
304
+ process.stdin.resume();
305
+ process.stdin.on('keypress', onKeypress);
306
+ });
307
+ }
308
+
309
+ export async function interactiveMode() {
310
+ while (true) {
311
+ const command = await promptInteractiveCommand();
312
+
313
+ await executeCommand(command);
314
+
315
+ const again = await yesNoKeyPrompt('↩️ Return to main menu?', true , { blue: true });
316
+
317
+ if (!again) {
318
+ logger.info('\n' + colors.boldHex('#00AFFF')('👋 Thanks for using Syncfusion CLI!\n'));
319
+ process.exit(0);
320
+ }
321
+ }
322
+ }
323
+
324
+ async function runNewProjectInteractive() {
325
+ logger.info('\n🎯 Syncfusion Project Creator\n');
326
+
327
+ const appName = await safePrompt(input, {
328
+ message: 'Project name?',
329
+ default: 'my-syncfusion-app',
330
+ validate: (value) => {
331
+ if (!value || value.trim() === '') {
332
+ return 'Project name is required';
333
+ }
334
+
335
+ if (!/^[a-zA-Z0-9-_]+$/.test(value)) {
336
+ return 'Project name can only contain letters, numbers, hyphens, and underscores';
337
+ }
338
+
339
+ return true;
340
+ }
341
+ });
342
+
343
+ if (!appName) {
344
+ logger.warning('\n⚠️ Project creation cancelled.\n');
345
+ return;
346
+ }
347
+
348
+ const framework = await selectWithIndentedChoices({
349
+ message: 'Choose Framework:',
350
+ choices: [
351
+ { name: 'React', value: 'react' },
352
+ { name: 'Angular', value: 'angular' },
353
+ { name: 'Vue', value: 'vue' },
354
+ { name: 'TypeScript (vanilla EJ2)', value: 'typescript' }
355
+ ],
356
+ default: 'react'
357
+ });
358
+
359
+ if (!framework) {
360
+ logger.warning('\n⚠️ Project creation cancelled.\n');
361
+ return;
362
+ }
363
+
364
+ let buildTool = null;
365
+
366
+ if (framework === 'react') {
367
+ buildTool = await selectWithIndentedChoices({
368
+ message: 'Choose Build Tool:',
369
+ choices: [
370
+ { name: 'Vite', value: 'vite' },
371
+ { name: 'Next.js', value: 'nextjs' }
372
+ ],
373
+ default: 'vite'
374
+ });
375
+
376
+ if (!buildTool) {
377
+ logger.warning('\n⚠️ Project creation cancelled.\n');
378
+ return;
379
+ }
380
+ }
381
+
382
+ let type = 'typescript';
383
+
384
+ if (framework === 'typescript') {
385
+ // The TypeScript (vanilla EJ2) platform is always TypeScript.
386
+ type = 'ts';
387
+ } else if (framework !== 'angular') {
388
+ type = await selectWithIndentedChoices({
389
+ message: 'Choose Language:',
390
+ choices: [
391
+ { name: 'TypeScript', value: 'ts' },
392
+ { name: 'JavaScript', value: 'js' }
393
+ ],
394
+ default: 'ts'
395
+ });
396
+
397
+ if (!type) {
398
+ logger.warning('\n⚠️ Project creation cancelled.\n');
399
+ return;
400
+ }
401
+ }
402
+
403
+ const template = await selectWithIndentedChoices({
404
+ message: 'Choose Template:',
405
+ choices: [
406
+ { name: 'Empty', value: 'empty' },
407
+ { name: 'Grid', value: 'grid' },
408
+ { name: 'Chart', value: 'chart' },
409
+ { name: 'Scheduler', value: 'scheduler' },
410
+ { name: 'Gantt', value: 'gantt' },
411
+ { name: 'File Manager', value: 'file-manager' },
412
+ { name: 'Diagram', value: 'diagram' },
413
+ { name: 'Rich Text Editor', value: 'rich-text-editor' },
414
+ { name: 'PDF Viewer', value: 'pdf-viewer' },
415
+ { name: 'DOCX Editor', value: 'docx-editor' },
416
+ { name: 'Spreadsheet Editor', value: 'spreadsheet-editor' }
417
+ ],
418
+ default: 'empty'
419
+ });
420
+
421
+ if (!template) {
422
+ logger.warning('\n⚠️ Project creation cancelled.\n');
423
+ return;
424
+ }
425
+
426
+ const theme = await selectWithIndentedChoices({
427
+ message: 'Choose Theme:',
428
+ choices: [
429
+ { name: 'Material3', value: 'material3' },
430
+ { name: 'Tailwind3', value: 'tailwind3' },
431
+ { name: 'Bootstrap5.3', value: 'bootstrap5.3' },
432
+ { name: 'Fluent2', value: 'fluent2' }
433
+ ],
434
+ default: 'material3'
435
+ });
436
+
437
+ if (!theme) {
438
+ logger.warning('\n⚠️ Project creation cancelled.\n');
439
+ return;
440
+ }
441
+
442
+ const styling = await selectWithIndentedChoices({
443
+ message: 'Choose Style Format:',
444
+ choices: [
445
+ { name: 'CSS', value: 'css' },
446
+ { name: 'SCSS', value: 'scss' }
447
+ ],
448
+ default: 'css'
449
+ });
450
+
451
+ if (!styling) {
452
+ logger.warning('\n⚠️ Project creation cancelled.\n');
453
+ return;
454
+ }
455
+
456
+ const wantsMcp = await yesNoKeyPrompt(
457
+ 'Would you like to integrate the Syncfusion MCP Server (AI Assistant) into this project?',
458
+ false
459
+ );
460
+
461
+ const wantsSkills = await yesNoKeyPrompt(
462
+ 'Would you like to install Syncfusion Component Skills for AI-powered development?',
463
+ false
464
+ );
465
+
466
+ let start = false;
467
+ let noStart = false;
468
+
469
+ if (process.stdout.isTTY) {
470
+ const wantsBootstrap = await yesNoKeyPrompt('Install dependencies and start app now?', false);
471
+
472
+ if (wantsBootstrap === true) {
473
+ start = true;
474
+ } else if (wantsBootstrap === false) {
475
+ noStart = true;
476
+ }
477
+ }
478
+
479
+ await createNewCommand(framework, appName, {
480
+ framework: framework,
481
+ buildTool,
482
+ type,
483
+ template: template,
484
+ theme: theme,
485
+ styling: styling,
486
+ mcp: wantsMcp,
487
+ promptMcpEditor: wantsMcp === true,
488
+ skills: wantsSkills,
489
+ promptSkillsInstallMode: wantsSkills === true,
490
+ start,
491
+ noStart
492
+ });
493
+ }
@@ -0,0 +1,135 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { select } from '../../lib/inquirer-prompts.js';
4
+ import { logger } from '../utils/logger.js';
5
+ import { safePrompt } from '../utils/prompt-utils.js';
6
+ import { requireSyncfusionProject } from '../utils/project-detector.js';
7
+ import { updateSyncfusionConfig } from '../utils/config-generator.js';
8
+ import { setupMcpForEditors } from '../utils/mcp-generator.js';
9
+
10
+ const EDITOR_FLAGS = [
11
+ { flag: 'vsCode', value: 'vscode' },
12
+ { flag: 'codeStudio', value: 'codestudio' },
13
+ { flag: 'cursor', value: 'cursor' },
14
+ { flag: 'jetbrains', value: 'jetbrains' }
15
+ ];
16
+
17
+ const EDITOR_VALUE_MAP = {
18
+ all: 'all',
19
+ vscode: 'vscode',
20
+ 'vs-code': 'vscode',
21
+ codestudio: 'codestudio',
22
+ 'code-studio': 'codestudio',
23
+ cursor: 'cursor',
24
+ jetbrains: 'jetbrains'
25
+ };
26
+
27
+ function normalizeEditorValue(editor) {
28
+ if (!editor) return null;
29
+
30
+ const value = String(editor).trim().toLowerCase();
31
+ return EDITOR_VALUE_MAP[value] || null;
32
+ }
33
+
34
+ export function resolveMcpEditors(options = {}, editorArg) {
35
+ const positionalEditor = normalizeEditorValue(editorArg || options.editor);
36
+
37
+ if (positionalEditor) {
38
+ const hasFlagEditor = options.all || EDITOR_FLAGS.some((editor) => options[editor.flag]);
39
+ if (hasFlagEditor) {
40
+ logger.error('Use either a positional editor value or editor flags, not both.');
41
+ logger.info('Example: sf mcp init code-studio');
42
+ process.exit(1);
43
+ }
44
+
45
+ return [positionalEditor];
46
+ }
47
+
48
+ if ((editorArg || options.editor) && !positionalEditor) {
49
+ logger.error(`Invalid editor value: ${editorArg || options.editor}`);
50
+ logger.info('Supported editor values: all, vs-code, code-studio, cursor, jetbrains.');
51
+ process.exit(1);
52
+ }
53
+
54
+ if (options.all) {
55
+ const hasOtherEditor = EDITOR_FLAGS.some((editor) => options[editor.flag]);
56
+ if (hasOtherEditor) {
57
+ logger.error('Use --all alone, or choose one or more specific editor flags.');
58
+ process.exit(1);
59
+ }
60
+ return ['all'];
61
+ }
62
+
63
+ const editors = EDITOR_FLAGS
64
+ .filter((editor) => options[editor.flag])
65
+ .map((editor) => editor.value);
66
+
67
+ return editors.length > 0 ? editors : ['all'];
68
+ }
69
+
70
+ /**
71
+ * `sf mcp` — configure the Syncfusion MCP (AI assistant) server
72
+ * for the current project.
73
+ */
74
+ export async function mcpAddCommand(options = {}, editorArg) {
75
+ const project = await requireSyncfusionProject(process.cwd());
76
+ if (!project) process.exit(1);
77
+
78
+ const configPath = path.join(project.root, 'syncfusion.config.json');
79
+ const config = await readFile(configPath, 'utf8').then(JSON.parse).catch(() => ({}));
80
+
81
+ if (config.mcp?.integrated === true) {
82
+ logger.info('\nℹ️ MCP already added. Skipping.\n');
83
+ return;
84
+ }
85
+
86
+ let editors;
87
+
88
+ if (options.promptEditor && !editorArg) {
89
+ const editorOptions = await promptMcpEditorSelection();
90
+ if (!editorOptions) {
91
+ return;
92
+ }
93
+ editors = resolveMcpEditors(editorOptions);
94
+ } else {
95
+ editors = resolveMcpEditors(options, editorArg);
96
+ }
97
+
98
+ logger.info(`\n🧩 Configuring Syncfusion MCP server for ${project.framework} project...\n`);
99
+
100
+ const written = await setupMcpForEditors(project.root, project.framework, editors);
101
+
102
+ try {
103
+ await updateSyncfusionConfig(project.root, {
104
+ mcp: { integrated: true, editors: written }
105
+ });
106
+ } catch (error) {
107
+ logger.warning(
108
+ `⚠️ Failed to update syncfusion.config.json: ${error.message}`
109
+ );
110
+ }
111
+
112
+ logger.success('\n✅ MCP server configuration added.');
113
+ logger.info(`Update the configuration file with your Syncfusion API key, then open the file(s) and click "Start" to connect to the MCP server. \nFor more information, see: https://ej2.syncfusion.com/${config.framework}/documentation/mcp-server/ai-coding-assistant/getting-started`);
114
+ }
115
+
116
+ export async function promptMcpEditorSelection() {
117
+ const editor = await safePrompt(select, {
118
+ message: 'Which editor do you want to add MCP configuration for?',
119
+ choices: [
120
+ { name: 'All Editors', value: 'all' },
121
+ { name: 'VS Code', value: 'vs-code' },
122
+ { name: 'Code Studio', value: 'code-studio' },
123
+ { name: 'Cursor', value: 'cursor' },
124
+ { name: 'JetBrains', value: 'jetbrains' }
125
+ ],
126
+ default: 'all'
127
+ });
128
+
129
+ if (!editor) {
130
+ logger.warning('\n⚠️ MCP configuration cancelled.\n');
131
+ return null;
132
+ }
133
+
134
+ return { editor };
135
+ }