@burgan-tech/vnext-workflow-cli 1.0.0 → 1.0.2

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.
@@ -2,83 +2,164 @@ const chalk = require('chalk');
2
2
  const ora = require('ora');
3
3
  const path = require('path');
4
4
  const config = require('../lib/config');
5
- const {
6
- processCsxFile,
7
- getGitChangedCsx,
8
- findAllCsx
9
- } = require('../lib/csx');
5
+ const { getDomain } = require('../lib/vnextConfig');
6
+ const { processCsxFile, getGitChangedCsx, findAllCsx } = require('../lib/csx');
7
+
8
+ // Logging helpers
9
+ const LOG = {
10
+ separator: () => console.log(chalk.cyan('═'.repeat(60))),
11
+ subSeparator: () => console.log(chalk.cyan('─'.repeat(60))),
12
+ header: (text) => {
13
+ console.log();
14
+ LOG.separator();
15
+ console.log(chalk.cyan.bold(` ${text}`));
16
+ LOG.separator();
17
+ },
18
+ success: (text) => console.log(chalk.green(` ✓ ${text}`)),
19
+ error: (text) => console.log(chalk.red(` ✗ ${text}`)),
20
+ warning: (text) => console.log(chalk.yellow(` ⚠ ${text}`)),
21
+ info: (text) => console.log(chalk.dim(` ○ ${text}`)),
22
+ component: (type, name, status, detail = '') => {
23
+ const typeLabel = chalk.cyan(`[${type}]`);
24
+ const nameLabel = chalk.white(name);
25
+ if (status === 'success') {
26
+ console.log(` ${typeLabel} ${chalk.green('✓')} ${nameLabel} ${chalk.dim(detail)}`);
27
+ } else if (status === 'error') {
28
+ console.log(` ${typeLabel} ${chalk.red('✗')} ${nameLabel}`);
29
+ if (detail) console.log(chalk.red(` └─ ${detail}`));
30
+ } else if (status === 'skip') {
31
+ console.log(` ${typeLabel} ${chalk.dim('○')} ${nameLabel} ${chalk.dim(detail)}`);
32
+ }
33
+ }
34
+ };
10
35
 
11
36
  async function csxCommand(options) {
12
- console.log(chalk.cyan.bold('\n🔄 CSX Güncelleme\n'));
37
+ LOG.header('CSX UPDATE');
13
38
 
14
39
  const projectRoot = config.get('PROJECT_ROOT');
40
+
41
+ // Check domain
42
+ try {
43
+ const domain = getDomain(projectRoot);
44
+ console.log(chalk.dim(` Domain: ${domain}`));
45
+ console.log();
46
+ } catch (error) {
47
+ LOG.error(`Failed to read vnext.config.json: ${error.message}`);
48
+ return;
49
+ }
50
+
15
51
  let csxFiles = [];
16
52
 
17
- // Hangi CSX dosyalarını işleyeceğiz?
53
+ // Which CSX files to process?
18
54
  if (options.file) {
19
- // Belirli dosya
55
+ // Specific file
20
56
  const filePath = path.isAbsolute(options.file)
21
57
  ? options.file
22
58
  : path.join(projectRoot, options.file);
23
59
  csxFiles = [filePath];
24
- console.log(chalk.blue(`Dosya: ${path.basename(filePath)}\n`));
60
+ console.log(chalk.blue(` File: ${path.basename(filePath)}\n`));
25
61
  } else if (options.all) {
26
- // Tüm CSX dosyaları
27
- const spinner = ora('Tüm CSX dosyaları bulunuyor...').start();
28
- csxFiles = await findAllCsx(projectRoot);
29
- spinner.succeed(chalk.green(`${csxFiles.length} CSX dosyası bulundu`));
62
+ // All CSX files
63
+ const spinner = ora(' Finding all CSX files...').start();
64
+ try {
65
+ csxFiles = await findAllCsx(projectRoot);
66
+ spinner.succeed(chalk.green(` ${csxFiles.length} CSX files found`));
67
+ } catch (error) {
68
+ spinner.fail(chalk.red(` CSX scan error: ${error.message}`));
69
+ return;
70
+ }
30
71
  } else {
31
- // Git'te değişenler (default)
32
- const spinner = ora('Git\'te değişen CSX dosyaları aranıyor...').start();
33
- csxFiles = await getGitChangedCsx(projectRoot);
34
-
35
- if (csxFiles.length === 0) {
36
- spinner.info(chalk.yellow('Git\'te değişen CSX dosyası bulunamadı'));
37
- console.log(chalk.green('\n✓ Tüm CSX dosyaları güncel\n'));
72
+ // Changed files in Git (default)
73
+ const spinner = ora(' Finding changed CSX files in Git...').start();
74
+ try {
75
+ csxFiles = await getGitChangedCsx(projectRoot);
76
+
77
+ if (csxFiles.length === 0) {
78
+ spinner.info(chalk.yellow(' No changed CSX files in Git'));
79
+ console.log(chalk.green('\n ✓ All CSX files up to date\n'));
80
+ return;
81
+ }
82
+
83
+ spinner.succeed(chalk.green(` ${csxFiles.length} changed CSX files found`));
84
+ } catch (error) {
85
+ spinner.fail(chalk.red(` CSX scan error: ${error.message}`));
38
86
  return;
39
87
  }
40
-
41
- spinner.succeed(chalk.green(`${csxFiles.length} değişen CSX dosyası bulundu`));
42
88
  }
43
89
 
44
- // Her CSX dosyasını işle
45
- let successCount = 0;
46
- let failCount = 0;
90
+ // Process each CSX file
91
+ const results = { success: 0, failed: 0, errors: [] };
92
+ const updatedFiles = [];
93
+
94
+ console.log(chalk.blue('\n Writing CSX files to JSONs...\n'));
47
95
 
48
- console.log();
49
96
  for (const csxFile of csxFiles) {
50
97
  const fileName = path.basename(csxFile);
51
- const spinner = ora(`İşleniyor: ${fileName}`).start();
52
98
 
53
99
  try {
54
100
  const result = await processCsxFile(csxFile, projectRoot);
55
101
 
56
102
  if (result.success) {
57
- spinner.succeed(chalk.green(`✓ ${fileName} ${result.updatedCount} JSON güncellendi`));
58
- successCount++;
103
+ LOG.component('CSX', fileName, 'success', `→ ${result.updatedJsonCount} JSON, ${result.totalUpdates} refs`);
104
+ results.success++;
105
+ updatedFiles.push({
106
+ file: fileName,
107
+ jsonCount: result.updatedJsonCount,
108
+ totalUpdates: result.totalUpdates,
109
+ jsonFiles: result.jsonFiles
110
+ });
59
111
  } else {
60
- spinner.fail(chalk.red(`✗ ${fileName} ${result.message}`));
61
- failCount++;
112
+ LOG.component('CSX', fileName, 'skip', result.message);
62
113
  }
63
114
  } catch (error) {
64
- spinner.fail(chalk.red(`✗ ${fileName} Hata: ${error.message}`));
65
- failCount++;
115
+ LOG.component('CSX', fileName, 'error', error.message);
116
+ results.failed++;
117
+ results.errors.push({ file: fileName, error: error.message });
118
+ }
119
+ }
120
+
121
+ // SUMMARY REPORT
122
+ LOG.header('CSX UPDATE SUMMARY');
123
+
124
+ // Results
125
+ console.log(chalk.white.bold('\n Results:\n'));
126
+
127
+ const successLabel = results.success > 0 ? chalk.green(`${results.success} success`) : chalk.dim('0 success');
128
+ const failedLabel = results.failed > 0 ? chalk.red(`, ${results.failed} failed`) : '';
129
+ console.log(` ${chalk.cyan('CSX Files'.padEnd(16))} : ${successLabel}${failedLabel}`);
130
+
131
+ // Updated JSON details
132
+ if (updatedFiles.length > 0) {
133
+ console.log();
134
+ LOG.subSeparator();
135
+ console.log(chalk.white.bold('\n Updated JSON Files:\n'));
136
+
137
+ for (const item of updatedFiles) {
138
+ console.log(chalk.green(` ${item.file}:`));
139
+ for (const json of item.jsonFiles) {
140
+ console.log(chalk.dim(` └─ ${json.file} (${json.updates} refs)`));
141
+ }
66
142
  }
67
143
  }
68
144
 
69
- // Özet
70
- console.log();
71
- console.log(chalk.cyan('─'.repeat(50)));
72
- console.log(chalk.white(`Toplam: ${csxFiles.length} dosya`));
73
- console.log(chalk.green(`✓ Başarılı: ${successCount}`));
74
- if (failCount > 0) {
75
- console.log(chalk.red(`✗ Başarısız: ${failCount}`));
145
+ // Errors
146
+ if (results.errors.length > 0) {
147
+ console.log();
148
+ LOG.subSeparator();
149
+ console.log(chalk.red.bold('\n ERRORS:\n'));
150
+
151
+ for (const err of results.errors) {
152
+ console.log(chalk.red(` [CSX] ${err.file}`));
153
+ console.log(chalk.dim(` └─ ${err.error}`));
154
+ }
76
155
  }
77
- console.log(chalk.cyan('─'.repeat(50)));
78
- console.log();
79
156
 
80
- if (successCount > 0) {
81
- console.log(chalk.green.bold('✓ CSX güncelleme tamamlandı\n'));
157
+ LOG.separator();
158
+
159
+ if (results.success > 0 && results.failed === 0) {
160
+ console.log(chalk.green.bold('\n ✓ CSX update completed\n'));
161
+ } else if (results.failed > 0) {
162
+ console.log(chalk.yellow.bold(`\n ⚠ CSX update completed (${results.failed} errors)\n`));
82
163
  }
83
164
  }
84
165
 
@@ -1,161 +1,279 @@
1
1
  const chalk = require('chalk');
2
2
  const ora = require('ora');
3
3
  const inquirer = require('inquirer');
4
+ const path = require('path');
5
+ const { glob } = require('glob');
4
6
  const config = require('../lib/config');
5
7
  const { discoverComponents } = require('../lib/discover');
6
- const { processWorkflow, findAllJson } = require('../lib/workflow');
7
- const { reinitializeSystem } = require('../lib/api');
8
+ const { getDomain, getComponentTypes } = require('../lib/vnextConfig');
9
+ const { getJsonMetadata, findAllJson, detectComponentType } = require('../lib/workflow');
10
+ const { publishComponent, reinitializeSystem } = require('../lib/api');
11
+ const { getInstanceId, deleteWorkflow } = require('../lib/db');
12
+
13
+ // Logging helpers
14
+ const LOG = {
15
+ separator: () => console.log(chalk.cyan('═'.repeat(60))),
16
+ subSeparator: () => console.log(chalk.cyan('─'.repeat(60))),
17
+ header: (text) => {
18
+ console.log();
19
+ LOG.separator();
20
+ console.log(chalk.cyan.bold(` ${text}`));
21
+ LOG.separator();
22
+ },
23
+ success: (text) => console.log(chalk.green(` ✓ ${text}`)),
24
+ error: (text) => console.log(chalk.red(` ✗ ${text}`)),
25
+ warning: (text) => console.log(chalk.yellow(` ⚠ ${text}`)),
26
+ info: (text) => console.log(chalk.dim(` ○ ${text}`)),
27
+ component: (type, name, status, detail = '') => {
28
+ const typeLabel = chalk.cyan(`[${type}]`);
29
+ const nameLabel = chalk.white(name);
30
+ if (status === 'success') {
31
+ console.log(` ${typeLabel} ${chalk.green('✓')} ${nameLabel} ${chalk.dim(detail)}`);
32
+ } else if (status === 'error') {
33
+ console.log(` ${typeLabel} ${chalk.red('✗')} ${nameLabel}`);
34
+ if (detail) console.log(chalk.red(` └─ ${detail}`));
35
+ } else if (status === 'skip') {
36
+ console.log(` ${typeLabel} ${chalk.dim('○')} ${nameLabel} ${chalk.dim(detail)}`);
37
+ }
38
+ }
39
+ };
8
40
 
9
41
  async function resetCommand(options) {
10
- console.log(chalk.cyan.bold('\n🔄 Workflow Reset (Force Update)\n'));
42
+ LOG.header('COMPONENT RESET (Force Update)');
11
43
 
12
44
  const projectRoot = config.get('PROJECT_ROOT');
13
- const autoDiscover = config.get('AUTO_DISCOVER');
45
+
46
+ // Get domain from vnext.config.json
47
+ let domain, componentTypes;
48
+ try {
49
+ domain = getDomain(projectRoot);
50
+ componentTypes = getComponentTypes(projectRoot);
51
+ } catch (error) {
52
+ LOG.error(`Failed to read vnext.config.json: ${error.message}`);
53
+ return;
54
+ }
14
55
 
15
56
  // DB Config
57
+ const useDockerValue = config.get('USE_DOCKER');
16
58
  const dbConfig = {
17
59
  host: config.get('DB_HOST'),
18
60
  port: config.get('DB_PORT'),
19
61
  database: config.get('DB_NAME'),
20
62
  user: config.get('DB_USER'),
21
63
  password: config.get('DB_PASSWORD'),
22
- useDocker: config.get('USE_DOCKER'),
64
+ useDocker: useDockerValue === true || useDockerValue === 'true',
23
65
  dockerContainer: config.get('DOCKER_POSTGRES_CONTAINER')
24
66
  };
25
67
 
26
68
  // API Config
27
69
  const apiConfig = {
28
70
  baseUrl: config.get('API_BASE_URL'),
29
- version: config.get('API_VERSION')
71
+ version: config.get('API_VERSION'),
72
+ domain: domain
30
73
  };
31
74
 
32
- // Klasörleri keşfet
33
- const spinner = ora('Klasörler taranıyor...').start();
34
- const discovered = await discoverComponents(projectRoot);
35
- spinner.succeed(chalk.green('Klasörler bulundu'));
36
-
37
- // Seçenekler
38
- const choices = [
39
- { name: '🔵 Workflows (sys-flows)', value: 'Workflows' },
40
- { name: '📋 Tasks (sys-tasks)', value: 'Tasks' },
41
- { name: '📊 Schemas (sys-schemas)', value: 'Schemas' },
42
- { name: '👁️ Views (sys-views)', value: 'Views' },
43
- { name: '⚙️ Functions (sys-functions)', value: 'Functions' },
44
- { name: '🔌 Extensions (sys-extensions)', value: 'Extensions' },
45
- new inquirer.Separator(),
46
- { name: '🔴 TÜMÜ (Tüm klasörler)', value: 'ALL' }
47
- ];
48
-
49
- // Kullanıcıdan seç
75
+ console.log(chalk.dim(` Domain: ${domain}`));
76
+ console.log(chalk.dim(` API: ${apiConfig.baseUrl}`));
77
+ console.log();
78
+
79
+ // Discover folders
80
+ const spinner = ora(' Scanning folders...').start();
81
+ let discovered;
82
+ try {
83
+ discovered = await discoverComponents(projectRoot);
84
+ spinner.succeed(chalk.green(' Folders discovered'));
85
+ } catch (error) {
86
+ spinner.fail(chalk.red(` Folder scan error: ${error.message}`));
87
+ return;
88
+ }
89
+
90
+ // Build choices dynamically
91
+ const choices = [];
92
+ for (const [type, folderName] of Object.entries(componentTypes)) {
93
+ if (discovered[type]) {
94
+ choices.push({ name: `${type} (${folderName}/)`, value: type });
95
+ }
96
+ }
97
+
98
+ choices.push(new inquirer.Separator());
99
+ choices.push({ name: 'ALL (All folders)', value: 'ALL' });
100
+
101
+ // User selection
50
102
  const { selected } = await inquirer.prompt([{
51
103
  type: 'list',
52
104
  name: 'selected',
53
- message: 'Hangi klasör resetlensin?',
105
+ message: 'Which folder to reset?',
54
106
  choices: choices
55
107
  }]);
56
108
 
57
- // Dosyaları bul
109
+ // Find files
58
110
  let jsonFiles = [];
59
111
 
60
112
  if (selected === 'ALL') {
61
- jsonFiles = await findAllJson(discovered);
113
+ const files = await findAllJson(discovered);
114
+ jsonFiles = files.map(f => ({
115
+ path: f,
116
+ type: detectComponentType(f, projectRoot),
117
+ fileName: path.basename(f)
118
+ }));
62
119
  } else {
63
120
  const dir = discovered[selected];
64
121
  if (!dir) {
65
- console.log(chalk.red(`\n✗ ${selected} klasörü bulunamadı\n`));
122
+ LOG.error(`${selected} folder not found`);
66
123
  return;
67
124
  }
68
125
 
69
- // Sadece bu klasördeki JSON'ları bul
70
- const fs = require('fs').promises;
71
- const path = require('path');
72
- const { glob } = require('glob');
73
-
126
+ // Find JSONs in this folder only
74
127
  const pattern = path.join(dir, '**/*.json');
75
- jsonFiles = await glob(pattern);
128
+ const files = await glob(pattern, {
129
+ ignore: [
130
+ '**/.meta/**',
131
+ '**/.meta',
132
+ '**/*.diagram.json',
133
+ '**/package*.json',
134
+ '**/*config*.json'
135
+ ]
136
+ });
137
+
138
+ jsonFiles = files.map(f => ({
139
+ path: f,
140
+ type: selected,
141
+ fileName: path.basename(f)
142
+ }));
76
143
  }
77
144
 
78
145
  if (jsonFiles.length === 0) {
79
- console.log(chalk.yellow('\n⚠ JSON dosyası bulunamadı\n'));
146
+ LOG.warning('No JSON files found');
147
+ console.log();
80
148
  return;
81
149
  }
82
150
 
83
- // Son onay
84
- console.log(chalk.yellow(`\n⚠️ ${jsonFiles.length} workflow resetlenecek (DB'den silinip tekrar eklenecek)!\n`));
151
+ // Final confirmation
152
+ LOG.warning(`${jsonFiles.length} components will be reset!`);
153
+ console.log();
85
154
 
86
155
  const { confirm } = await inquirer.prompt([{
87
156
  type: 'confirm',
88
157
  name: 'confirm',
89
- message: 'Devam edilsin mi?',
158
+ message: 'Continue?',
90
159
  default: false
91
160
  }]);
92
161
 
93
162
  if (!confirm) {
94
- console.log(chalk.yellow('\nİşlem iptal edildi.\n'));
163
+ LOG.warning('Operation cancelled.');
164
+ console.log();
95
165
  return;
96
166
  }
97
167
 
98
- // İşle
99
- let successCount = 0;
100
- let failCount = 0;
168
+ // Group by component type
169
+ const componentStats = {};
170
+ const errors = [];
101
171
 
102
- console.log();
103
- for (const jsonFile of jsonFiles) {
104
- const fileName = require('path').basename(jsonFile);
105
- const spinner = ora(`İşleniyor: ${fileName}`).start();
172
+ console.log(chalk.blue('\n Resetting components...\n'));
173
+
174
+ for (const jsonInfo of jsonFiles) {
175
+ const { path: jsonPath, type, fileName } = jsonInfo;
176
+
177
+ // Initialize stats
178
+ if (!componentStats[type]) {
179
+ componentStats[type] = { success: 0, failed: 0, skipped: 0, deleted: 0 };
180
+ }
106
181
 
107
182
  try {
108
- const result = await processWorkflow(jsonFile, dbConfig, apiConfig);
183
+ const metadata = await getJsonMetadata(jsonPath);
109
184
 
110
- const status = result.wasDeleted ? 'resetlendi' : 'oluşturuldu';
111
- spinner.succeed(chalk.green(`✓ ${fileName} ${status}`));
112
- successCount++;
113
- } catch (error) {
114
- let errorMsg = error.message;
115
- if (error.response?.data) {
116
- if (typeof error.response.data === 'string') {
117
- errorMsg = error.response.data;
118
- } else if (error.response.data.error?.message) {
119
- errorMsg = error.response.data.error.message;
120
- } else if (error.response.data.message) {
121
- errorMsg = error.response.data.message;
122
- } else {
123
- errorMsg = JSON.stringify(error.response.data);
124
- }
185
+ if (!metadata.key || !metadata.version) {
186
+ LOG.component(type, fileName, 'skip', 'no key/version');
187
+ componentStats[type].skipped++;
188
+ continue;
189
+ }
190
+
191
+ // Detect flow type
192
+ const flow = metadata.flow || detectComponentType(jsonPath, projectRoot);
193
+
194
+ // Check if exists in DB
195
+ const existingId = await getInstanceId(dbConfig, flow, metadata.key, metadata.version);
196
+
197
+ // If exists, delete first (force reset)
198
+ let wasDeleted = false;
199
+ if (existingId) {
200
+ await deleteWorkflow(dbConfig, flow, existingId);
201
+ wasDeleted = true;
202
+ componentStats[type].deleted++;
125
203
  }
126
- spinner.fail(chalk.red(`✗ ${fileName} → ${errorMsg}`));
127
- failCount++;
204
+
205
+ // Publish to API
206
+ const result = await publishComponent(apiConfig.baseUrl, metadata.data);
207
+
208
+ if (result.success) {
209
+ const action = wasDeleted ? 'reset' : 'created';
210
+ LOG.component(type, fileName, 'success', `→ ${action}`);
211
+ componentStats[type].success++;
212
+ } else {
213
+ LOG.component(type, fileName, 'error', result.error);
214
+ componentStats[type].failed++;
215
+ errors.push({ type, file: fileName, error: result.error });
216
+ }
217
+ } catch (error) {
218
+ const errorMsg = error.message || 'Unknown error';
219
+ LOG.component(type, fileName, 'error', errorMsg);
220
+ componentStats[type].failed++;
221
+ errors.push({ type, file: fileName, error: errorMsg });
128
222
  }
129
223
  }
130
224
 
131
225
  // Re-initialize
132
- if (successCount > 0) {
226
+ const totalSuccess = Object.values(componentStats).reduce((sum, s) => sum + s.success, 0);
227
+
228
+ if (totalSuccess > 0) {
133
229
  console.log();
134
- const reinitSpinner = ora('Sistem yeniden başlatılıyor...').start();
230
+ const reinitSpinner = ora(' Re-initializing system...').start();
135
231
  const reinitSuccess = await reinitializeSystem(apiConfig.baseUrl, apiConfig.version);
136
232
 
137
233
  if (reinitSuccess) {
138
- reinitSpinner.succeed(chalk.green(' Sistem yenilendi'));
234
+ reinitSpinner.succeed(chalk.green(' System re-initialized'));
139
235
  } else {
140
- reinitSpinner.warn(chalk.yellow(' Sistem yenilenemedi (devam edildi)'));
236
+ reinitSpinner.warn(chalk.yellow(' System re-initialization failed (continuing)'));
141
237
  }
142
238
  }
143
239
 
144
- // Özet
145
- console.log();
146
- console.log(chalk.cyan('═'.repeat(50)));
147
- console.log(chalk.white(`Toplam: ${jsonFiles.length} dosya`));
148
- console.log(chalk.green(`✓ Başarılı: ${successCount}`));
149
- if (failCount > 0) {
150
- console.log(chalk.red(`✗ Başarısız: ${failCount}`));
240
+ // SUMMARY REPORT
241
+ LOG.header('RESET SUMMARY');
242
+
243
+ // Component statistics
244
+ console.log(chalk.white.bold('\n Component Reset Results:\n'));
245
+
246
+ for (const [type, stats] of Object.entries(componentStats)) {
247
+ const successLabel = stats.success > 0 ? chalk.green(`${stats.success} reset`) : '';
248
+ const deletedLabel = stats.deleted > 0 ? chalk.yellow(`${stats.deleted} deleted`) : '';
249
+ const failedLabel = stats.failed > 0 ? chalk.red(`${stats.failed} failed`) : '';
250
+ const skippedLabel = stats.skipped > 0 ? chalk.dim(`${stats.skipped} skipped`) : '';
251
+
252
+ const parts = [successLabel, deletedLabel, failedLabel, skippedLabel].filter(Boolean);
253
+ console.log(` ${chalk.cyan(type.padEnd(12))} : ${parts.join(', ') || chalk.dim('0')}`);
254
+ }
255
+
256
+ // Errors
257
+ if (errors.length > 0) {
258
+ console.log();
259
+ LOG.subSeparator();
260
+ console.log(chalk.red.bold('\n ERRORS:\n'));
261
+
262
+ for (const err of errors) {
263
+ console.log(chalk.red(` [${err.type}] ${err.file}`));
264
+ console.log(chalk.dim(` └─ ${err.error}`));
265
+ }
151
266
  }
152
- console.log(chalk.cyan('═'.repeat(50)));
153
- console.log();
154
267
 
155
- if (successCount > 0) {
156
- console.log(chalk.green.bold('✓ Reset tamamlandı\n'));
268
+ LOG.separator();
269
+
270
+ const totalFailed = Object.values(componentStats).reduce((sum, s) => sum + s.failed, 0);
271
+
272
+ if (totalSuccess > 0 && totalFailed === 0) {
273
+ console.log(chalk.green.bold('\n ✓ Reset completed\n'));
274
+ } else if (totalFailed > 0) {
275
+ console.log(chalk.yellow.bold(`\n ⚠ Reset completed (${totalFailed} errors)\n`));
157
276
  }
158
277
  }
159
278
 
160
279
  module.exports = resetCommand;
161
-