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
package/license ADDED
@@ -0,0 +1,13 @@
1
+ SyncfusionĀ® License
2
+
3
+ Copyright (c) SyncfusionĀ®, Inc. All rights reserved.
4
+
5
+ EssentialĀ® JS 2 library is available through the SyncfusionĀ® Essential StudioĀ® program and can be licensed under either the SyncfusionĀ® Community License Program or the SyncfusionĀ® Commercial License.
6
+
7
+ To qualify for the SyncfusionĀ® Community License Program, your organization must have annual gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and fewer than five (5) developers. Additionally, you must agree to Syncfusion®’s terms and conditions.
8
+
9
+ If you do not meet the requirements for the community license, please reach out to sales@syncfusion.com for commercial licensing options.
10
+
11
+ You are required to obtain either a Community License or a Commercial License before using this product and must agree to comply with SyncfusionĀ®'s terms and conditions.
12
+
13
+ The full SyncfusionĀ® license, including terms and conditions, can be found at: https://www.syncfusion.com/content/downloads/syncfusion_license.pdf
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "vk-typescript-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI for creating Syncfusion starter projects",
5
+ "author": "Syncfusion Inc.",
6
+ "license": "SEE LICENSE IN license",
7
+ "type": "module",
8
+ "readme": "README.md",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/syncfusion/ej2-react-ui-components.git"
12
+ },
13
+ "homepage": "https://www.syncfusion.com/react-components",
14
+ "bin": {
15
+ "sf": "./bin/sf.js"
16
+ },
17
+ "scripts": {
18
+ "start": "node ./bin/sf.js",
19
+ "dev": "node ./bin/sf.js",
20
+ "publish": "gulp syncfusion-cli-publish",
21
+ "ci-report": "gulp ci-report",
22
+ "test": "gulp test"
23
+ },
24
+ "keywords": [
25
+ "syncfusion",
26
+ "ej2",
27
+ "cli",
28
+ "react",
29
+ "vite",
30
+ "angular",
31
+ "vue"
32
+ ],
33
+ "devDependencies": {
34
+ "@babel/eslint-parser": "7.29.7",
35
+ "@eslint/js": "9.39.5",
36
+ "eslint": "9.39.5",
37
+ "gulp": "5.0.1",
38
+ "shelljs": "0.10.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=22.12.0"
42
+ }
43
+ }
package/smoke-test.mjs ADDED
@@ -0,0 +1,38 @@
1
+ // Smoke test - verify the typescript framework integration is wired up correctly.
2
+ import { TEMPLATES, TEMPLATE_PACKAGES } from './src/constants.js';
3
+
4
+ const components = [
5
+ 'grid', 'chart', 'scheduler', 'gantt', 'file-manager',
6
+ 'diagram', 'rich-text-editor', 'pdf-viewer',
7
+ 'docx-editor', 'spreadsheet-editor'
8
+ ];
9
+
10
+ let pass = 0, fail = 0;
11
+ const errors = [];
12
+
13
+ for (const c of components) {
14
+ const ts = TEMPLATE_PACKAGES[c]?.typescript;
15
+ if (!ts || !Array.isArray(ts) || ts.length === 0) {
16
+ fail++;
17
+ errors.push(`āœ— ${c}: no typescript package mapping`);
18
+ } else {
19
+ pass++;
20
+ console.log(`āœ“ ${c}: ${ts.join(', ')}`);
21
+ }
22
+ }
23
+
24
+ // Verify empty template
25
+ const empty = TEMPLATE_PACKAGES.empty?.typescript;
26
+ if (empty) {
27
+ console.log(`āœ“ empty: ${empty.join(', ')}`);
28
+ pass++;
29
+ } else {
30
+ fail++;
31
+ errors.push('āœ— empty: no typescript package mapping');
32
+ }
33
+
34
+ console.log(`\nResults: ${pass} pass, ${fail} fail`);
35
+ if (fail) {
36
+ console.error(errors.join('\n'));
37
+ process.exit(1);
38
+ }
@@ -0,0 +1,276 @@
1
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { runCommand } from '../utils/run-command.js';
4
+ import { select } from '../../lib/inquirer-prompts.js';
5
+ import { logger } from '../utils/logger.js';
6
+ import { safePrompt } from '../utils/prompt-utils.js';
7
+ import { requireSyncfusionProject } from '../utils/project-detector.js';
8
+ import { updateSyncfusionConfig } from '../utils/config-generator.js';
9
+ import { addPackagesToJson } from '../utils/package-manager.js';
10
+ import { createProjectFolderStructure } from '../utils/folder-structure.js';
11
+ import { ADDABLE_COMPONENTS, TEMPLATE_PACKAGES, TEMPLATE_ROUTES } from '../constants.js';
12
+ import { updateVersions } from '../utils/common.js';
13
+ import { generateGridTemplate } from '../templates/grid-template.js';
14
+ import { generateChartTemplate } from '../templates/chart-template.js';
15
+ import { generateSchedulerTemplate } from '../templates/scheduler-template.js';
16
+ import { generateGanttTemplate } from '../templates/gantt-template.js';
17
+ import { generateFileManagerTemplate } from '../templates/filemanager-template.js';
18
+ import { generateDiagramTemplate } from '../templates/diagram-template.js';
19
+ import { generateRteTemplate } from '../templates/rte-template.js';
20
+ import { generatePDFTemplate } from '../templates/pdfviewer-template.js';
21
+ import { generateDOCXTemplate } from '../templates/docx-template.js';
22
+ import { generateSpreadsheetTemplate } from '../templates/spreadheet-template.js';
23
+
24
+ import {
25
+ generateHomePage as generateReactHomePage,
26
+ generateAppWithRouting as generateReactAppWithRouting
27
+ } from '../generators/react.js';
28
+
29
+ import {
30
+ generateAppComponentForRouting as generateAngularAppComponentForRouting,
31
+ generateAppConfig as generateAngularAppConfig,
32
+ generateAppRoutes as generateAngularAppRoutes,
33
+ generateHomeComponentStandalone as generateAngularHomeComponent,
34
+ generateGlobalStyles as generateAngularGlobalStyles
35
+ } from '../generators/angular.js';
36
+
37
+ import {
38
+ generateHomePage as generateVueHomePage,
39
+ generateAppWithRouting as generateVueAppWithRouting,
40
+ generateVueRouter
41
+ } from '../generators/vue.js';
42
+
43
+ import {
44
+ generateNextJsHomePage,
45
+ generateNextJsLayout,
46
+ generateNextJsRoutes,
47
+ generateNextJsAppPage
48
+ } from '../generators/nextjs.js';
49
+
50
+ import { addToTypeScript } from '../generators/typescript.js';
51
+
52
+ const TEMPLATE_GENERATORS = {
53
+ grid: generateGridTemplate,
54
+ chart: generateChartTemplate,
55
+ scheduler: generateSchedulerTemplate,
56
+ gantt: generateGanttTemplate,
57
+ 'file-manager': generateFileManagerTemplate,
58
+ diagram: generateDiagramTemplate,
59
+ 'rich-text-editor': generateRteTemplate,
60
+ 'pdf-viewer': generatePDFTemplate,
61
+ 'docx-editor': generateDOCXTemplate,
62
+ 'spreadsheet-editor': generateSpreadsheetTemplate
63
+ };
64
+
65
+ function componentLabel(key) {
66
+ return TEMPLATE_ROUTES[key]?.label || key;
67
+ }
68
+
69
+ async function promptForSyncfusionCleanInstall() {
70
+ const answer = await safePrompt(select, {
71
+ message: 'This project already has Syncfusion components. Do you want to delete node_modules/@syncfusion and package-lock.json, then install packages again?',
72
+ choices: [
73
+ { name: 'Yes', value: true },
74
+ { name: 'No', value: false }
75
+ ]
76
+ });
77
+
78
+ return answer === true;
79
+ }
80
+
81
+ async function cleanSyncfusionPackages(root) {
82
+ logger.info('\n🧹 Cleaning node_modules/@syncfusion and package-lock.json...');
83
+ await rm(path.join(root, 'node_modules', '@syncfusion'), { recursive: true, force: true });
84
+ await rm(path.join(root, 'package-lock.json'), { force: true });
85
+ }
86
+
87
+ /**
88
+ * `sf add <component>` — adds a Grid/Chart/Scheduler/Gantt/File Manager/Diagram/Rich Text Editor/PDF Viewer/DOCX Editor/Spreadsheet Editor
89
+ * sample into the current project, converting an "empty" project into a
90
+ * routed one on first use, or extending the existing routing on subsequent
91
+ * calls. Auto-detects framework/type/styling/theme from syncfusion.config.json
92
+ * (falling back to package.json / tsconfig.json heuristics).
93
+ */
94
+ export async function addComponentCommand(componentArg) {
95
+ let component = (componentArg || '').toLowerCase();
96
+
97
+ const project = await requireSyncfusionProject(process.cwd());
98
+ if (!project) {
99
+ process.exit(1);
100
+ }
101
+
102
+ if (!component) {
103
+ const selected = await safePrompt(select, {
104
+ message: 'Which component do you want to add?',
105
+ choices: ADDABLE_COMPONENTS.map((c) => ({ name: componentLabel(c), value: c }))
106
+ });
107
+ if (!selected) {
108
+ logger.warning('\nāš ļø Cancelled.\n');
109
+ return;
110
+ }
111
+ component = selected;
112
+ }
113
+
114
+ if (!ADDABLE_COMPONENTS.includes(component)) {
115
+ logger.error(`āŒ Unknown component: ${component}`);
116
+ logger.info(` Supported components: ${ADDABLE_COMPONENTS.join(', ')}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ const { root, framework, type, styling = 'css', theme = 'material3', buildTool } = project;
121
+ const isTypeScript = type === 'ts' || framework === 'typescript';
122
+ const existingComponents = project.components || [];
123
+
124
+ if (existingComponents.includes(component)) {
125
+ logger.warning(`\nāš ļø "${componentLabel(component)}" is already part of this project (src/components).`);
126
+ logger.info(' Nothing to do.\n');
127
+ return;
128
+ }
129
+
130
+ const wasEmpty = existingComponents.length === 0;
131
+ const updatedComponents = [...existingComponents, component];
132
+
133
+ logger.info(`šŸš€ Adding ${componentLabel(component)} to the project...\n`);
134
+
135
+ // Install packages required for this component (Syncfusion packages + router if applicable).
136
+ const packageFrameworkKey = framework === 'typescript'
137
+ ? 'typescript'
138
+ : (framework === 'react' ? (buildTool === 'nextjs' ? 'nextjs' : 'react') : framework);
139
+ const packages = TEMPLATE_PACKAGES[component]?.[packageFrameworkKey] || [];
140
+ if (packages.length) {
141
+ const versionedPackages = await updateVersions(root, packages);
142
+ if (wasEmpty) {
143
+ logger.info(`šŸ“¦ Installing ${packages.join(', ')}...`);
144
+ await installPackages(root, versionedPackages);
145
+ } else {
146
+ const shouldCleanInstall = await promptForSyncfusionCleanInstall();
147
+ if (shouldCleanInstall) {
148
+ await cleanSyncfusionPackages(root);
149
+ logger.info(`šŸ“¦ Installing ${versionedPackages}...`);
150
+ await installPackages(root, versionedPackages);
151
+ } else {
152
+ await addPackagesToJson(root, versionedPackages);
153
+ logger.warning(`\nāš ļø ${componentLabel(component)} component and dependencies added to package.json.`);
154
+ logger.warning(' Please delete node_modules and run npm install to update the installed packages.\n');
155
+ }
156
+ }
157
+ }
158
+
159
+ try {
160
+ if (framework === 'angular') {
161
+ await addToAngular(root, styling, updatedComponents, wasEmpty, theme);
162
+ } else if (framework === 'vue') {
163
+ await addToVue(root, isTypeScript, styling, theme, updatedComponents, wasEmpty);
164
+ } else if (framework === 'typescript') {
165
+ await addToTypeScript(root, isTypeScript, theme, updatedComponents, wasEmpty);
166
+ } else if (buildTool === 'nextjs') {
167
+ await addToNextJs(root, isTypeScript, styling, theme, updatedComponents, wasEmpty);
168
+ } else {
169
+ await addToReact(root, isTypeScript, styling, theme, updatedComponents, wasEmpty);
170
+ }
171
+
172
+ // Generate the component's own template files (Grid.tsx, grid.component.ts, etc.)
173
+ const generatorFrameworkKey = framework === 'angular'
174
+ ? 'angular'
175
+ : framework === 'vue'
176
+ ? 'vue'
177
+ : framework === 'typescript'
178
+ ? 'typescript'
179
+ : 'react';
180
+ await TEMPLATE_GENERATORS[component](root, generatorFrameworkKey, isTypeScript);
181
+
182
+ try {
183
+ await updateSyncfusionConfig(root, {
184
+ template: updatedComponents.length === 1 ? updatedComponents[0] : 'multi',
185
+ components: updatedComponents
186
+ });
187
+ } catch (error) {
188
+ logger.warning(
189
+ `āš ļø Failed to update syncfusion.config.json: ${error.message}`
190
+ );
191
+ }
192
+
193
+ logger.success(`\nāœ… ${componentLabel(component)} added successfully!`);
194
+ } catch (error) {
195
+ logger.error(`āŒ Failed to add ${component}: ${error.message}`);
196
+ console.error(error);
197
+ process.exit(1);
198
+ }
199
+ }
200
+
201
+ async function addToReact(root, isTypeScript, styling, theme, updatedComponents, wasEmpty) {
202
+
203
+ if (wasEmpty) {
204
+ await createProjectFolderStructure(root, 'with-components');
205
+ await addPackagesToJson(root, ['react-router@8.3.0']);
206
+ await generateReactHomePage(root, isTypeScript, theme, styling);
207
+ }
208
+ await generateReactHomePage(root, isTypeScript, theme, styling, updatedComponents);
209
+ await generateReactAppWithRouting(root, isTypeScript, updatedComponents, styling);
210
+ }
211
+
212
+ async function addToVue(root, isTypeScript, styling, theme, updatedComponents, wasEmpty) {
213
+ if (wasEmpty) {
214
+ await addPackagesToJson(root, ['vue-router@^4.3.0']);
215
+ await mkdir(path.join(root, 'src', 'views'), { recursive: true });
216
+ await mkdir(path.join(root, 'src', 'components'), { recursive: true });
217
+ await mkdir(path.join(root, 'src', 'router'), { recursive: true });
218
+ await rm(path.join(root, 'src', 'Home.vue'), { force: true });
219
+ // Rewrite main.js/ts to use the router.
220
+ const mainFileName = isTypeScript ? 'main.ts' : 'main.js';
221
+ const mainFilePath = path.join(root, 'src', mainFileName);
222
+ const styleFileName = styling === 'scss' ? 'style.scss' : 'style.css';
223
+ const mainContent = `import './${styleFileName}'
224
+ import { createApp } from 'vue'
225
+ import App from './App.vue'
226
+ import router from './router/index'
227
+
228
+ const app = createApp(App)
229
+ app.use(router)
230
+ app.mount('#app')
231
+ `;
232
+ await writeFile(mainFilePath, mainContent);
233
+ }
234
+
235
+ await generateVueHomePage(root, isTypeScript, theme, styling, updatedComponents);
236
+ await generateVueAppWithRouting(root, isTypeScript, updatedComponents, styling);
237
+ await generateVueRouter(root, isTypeScript, updatedComponents);
238
+ }
239
+
240
+ async function addToAngular(root, styling, updatedComponents, wasEmpty, theme) {
241
+ if (wasEmpty) {
242
+ await createProjectFolderStructure(root, 'with-components');
243
+ await generateAngularAppConfig(root);
244
+ }
245
+
246
+ await generateAngularHomeComponent(root, styling, updatedComponents);
247
+ await generateAngularAppComponentForRouting(root, styling, updatedComponents);
248
+ await generateAngularAppRoutes(root, updatedComponents);
249
+
250
+ if (wasEmpty) {
251
+ await generateAngularGlobalStyles(root, theme, styling);
252
+ }
253
+ }
254
+
255
+ async function addToNextJs(root, isTypeScript, styling, theme, updatedComponents, wasEmpty) {
256
+ const ext = isTypeScript ? '.tsx' : '.js';
257
+
258
+ if (wasEmpty) {
259
+ await createProjectFolderStructure(root, 'with-components');
260
+ await rm(path.join(root, 'src', 'app', `Home${ext}`), { force: true });
261
+ await generateNextJsHomePage(root, isTypeScript, theme, styling);
262
+ await generateNextJsAppPage(root, isTypeScript);
263
+ }
264
+
265
+ await generateNextJsHomePage(root, isTypeScript, theme, styling, updatedComponents);
266
+ await generateNextJsLayout(root, isTypeScript, theme, updatedComponents, styling);
267
+ await generateNextJsRoutes(root, isTypeScript, updatedComponents);
268
+ }
269
+
270
+ async function installPackages(root, packages) {
271
+ if (!packages.length) {
272
+ return;
273
+ }
274
+
275
+ await runCommand('npm', ['install', ...packages], { cwd: root });
276
+ }
@@ -0,0 +1,82 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { execFile } from 'node:child_process';
5
+ import { promisify } from 'node:util';
6
+ import { detectProject } from '../utils/project-detector.js';
7
+ import { getSyncfusionPackages } from '../utils/package-manager.js';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const execFileAsync = promisify(execFile);
11
+
12
+ async function getCliVersion() {
13
+ try {
14
+ const pkg = JSON.parse(await readFile(path.join(__dirname, '..', '..', 'package.json'), 'utf8'));
15
+ return pkg.version;
16
+ } catch {
17
+ return 'unknown';
18
+ }
19
+ }
20
+
21
+ async function getNpmVersion() {
22
+ const commands = process.platform === 'win32'
23
+ ? [
24
+ ['cmd.exe', ['/d', '/s', '/c', 'npm --version']]
25
+ ]
26
+ : [
27
+ ['npm', ['--version']]
28
+ ];
29
+
30
+ for (const [command, args] of commands) {
31
+ try {
32
+ const { stdout } = await execFileAsync(command, args);
33
+ const version = stdout.trim();
34
+ if (version) {
35
+ return version;
36
+ }
37
+ } catch (error) {
38
+ console.warn(`Failed to run command "${command}":`, error.message);
39
+ }
40
+ }
41
+
42
+ return 'not found';
43
+ }
44
+
45
+ /**
46
+ * `sf info` — print environment + current project information.
47
+ */
48
+ export async function infoCommand() {
49
+ const cliVersion = await getCliVersion();
50
+ const npmVersion = await getNpmVersion();
51
+
52
+ console.log('\nšŸ’» Environment\n');
53
+ console.log(` Syncfusion CLI : v${cliVersion}`);
54
+ console.log(` Node.js : ${process.version}`);
55
+ console.log(` npm : v${npmVersion}`);
56
+
57
+ const project = await detectProject(process.cwd());
58
+
59
+ console.log('\nšŸ“ Project\n');
60
+ if (!project || !project.framework) {
61
+ console.log(' No Syncfusion project detected in the current directory.\n');
62
+ return;
63
+ }
64
+
65
+ console.log(` Name : ${project.appName}`);
66
+ console.log(` Framework : ${project.framework}${project.buildTool ? ` (${project.buildTool})` : ''}`);
67
+ console.log(` Type : ${project.type}`);
68
+ console.log(` Theme : ${project.theme}`);
69
+ console.log(` Styling : ${project.styling}`);
70
+ console.log(` Template : ${project.template}`);
71
+ console.log(` Components : ${(project.components || []).join(', ') || 'none (empty template)'}`);
72
+ console.log(` Config source : ${project.fromConfig ? 'syncfusion.config.json' : 'auto-detected (no config file)'}`);
73
+
74
+ const syncfusionPackages = await getSyncfusionPackages(project.root);
75
+ const pkgEntries = Object.entries(syncfusionPackages);
76
+ console.log(`\nšŸ“¦ @syncfusion packages (${pkgEntries.length})\n`);
77
+ pkgEntries.slice(0, 15).forEach(([name, version]) => console.log(` ${name.padEnd(45)} ${version}`));
78
+ if (pkgEntries.length > 15) {
79
+ console.log(` ...and ${pkgEntries.length - 15} more`);
80
+ }
81
+ console.log('');
82
+ }