@burgan-tech/vnext-workflow-cli 1.0.1 → 1.0.3
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.
- package/README.md +413 -80
- package/bin/workflow.js +47 -13
- package/package.json +5 -5
- package/src/commands/check.js +62 -22
- package/src/commands/config.js +15 -9
- package/src/commands/csx.js +125 -44
- package/src/commands/domain.js +161 -0
- package/src/commands/reset.js +198 -80
- package/src/commands/sync.js +189 -107
- package/src/commands/update.js +217 -99
- package/src/lib/api.js +54 -36
- package/src/lib/config.js +231 -21
- package/src/lib/csx.js +130 -57
- package/src/lib/db.js +10 -10
- package/src/lib/discover.js +131 -29
- package/src/lib/vnextConfig.js +124 -0
- package/src/lib/workflow.js +86 -39
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const config = require('../lib/config');
|
|
3
|
+
|
|
4
|
+
const VALID_KEYS = [
|
|
5
|
+
'API_BASE_URL', 'API_VERSION', 'DB_HOST', 'DB_PORT', 'DB_NAME',
|
|
6
|
+
'DB_USER', 'DB_PASSWORD', 'AUTO_DISCOVER', 'USE_DOCKER',
|
|
7
|
+
'DOCKER_POSTGRES_CONTAINER', 'DEBUG_MODE'
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const NUMERIC_KEYS = ['DB_PORT'];
|
|
11
|
+
const BOOLEAN_KEYS = ['AUTO_DISCOVER', 'USE_DOCKER', 'DEBUG_MODE'];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Coerces a string value to the correct type based on key.
|
|
15
|
+
* Only known numeric keys become numbers; only known boolean keys become booleans.
|
|
16
|
+
* All other values remain as strings.
|
|
17
|
+
*/
|
|
18
|
+
function coerceValue(key, value) {
|
|
19
|
+
if (BOOLEAN_KEYS.includes(key)) {
|
|
20
|
+
return value === 'true';
|
|
21
|
+
}
|
|
22
|
+
if (NUMERIC_KEYS.includes(key)) {
|
|
23
|
+
const num = Number(value);
|
|
24
|
+
return isNaN(num) ? value : num;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extracts and coerces valid domain config options from Commander options object.
|
|
31
|
+
*/
|
|
32
|
+
function extractOptions(options) {
|
|
33
|
+
const parsed = {};
|
|
34
|
+
for (const key of VALID_KEYS) {
|
|
35
|
+
if (options[key] !== undefined) {
|
|
36
|
+
parsed[key] = coerceValue(key, options[key]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main domain command handler.
|
|
44
|
+
* Dispatches to add, use, list, or remove based on action argument.
|
|
45
|
+
*/
|
|
46
|
+
async function domainCommand(action, name, options) {
|
|
47
|
+
// Support wf domain --list
|
|
48
|
+
if (options.list || action === 'list') {
|
|
49
|
+
return listDomains();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (action === 'active') {
|
|
53
|
+
return showActiveDomain();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (action === 'add') {
|
|
57
|
+
return addDomain(name, options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (action === 'use') {
|
|
61
|
+
return useDomain(name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (action === 'remove') {
|
|
65
|
+
return removeDomain(name);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// No valid action - show usage
|
|
69
|
+
console.log(chalk.cyan.bold('\n🌐 Domain Management\n'));
|
|
70
|
+
console.log('Usage:');
|
|
71
|
+
console.log(chalk.white(' wf domain active Show active domain'));
|
|
72
|
+
console.log(chalk.white(' wf domain list List all domains'));
|
|
73
|
+
console.log(chalk.white(' wf domain add <name> [--API_BASE_URL ...] [--DB_NAME ...] Add a domain'));
|
|
74
|
+
console.log(chalk.white(' wf domain use <name> Switch active domain'));
|
|
75
|
+
console.log(chalk.white(' wf domain remove <name> Remove a domain'));
|
|
76
|
+
console.log('');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function showActiveDomain() {
|
|
80
|
+
console.log(config.get('ACTIVE_DOMAIN'));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function addDomain(name, options) {
|
|
84
|
+
if (!name) {
|
|
85
|
+
console.log(chalk.red('Usage: wf domain add <name> [--API_BASE_URL <url>] [--DB_NAME <name>] ...'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const parsed = extractOptions(options);
|
|
91
|
+
const domain = config.addDomain(name, parsed);
|
|
92
|
+
|
|
93
|
+
console.log(chalk.green(`\n✓ Domain "${name}" added successfully.\n`));
|
|
94
|
+
printDomainConfig(domain);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.log(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function useDomain(name) {
|
|
101
|
+
if (!name) {
|
|
102
|
+
console.log(chalk.red('Usage: wf domain use <name>'));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
config.useDomain(name);
|
|
108
|
+
console.log(chalk.green(`\n✓ Active domain switched to "${name}".\n`));
|
|
109
|
+
|
|
110
|
+
// Show key applied config values
|
|
111
|
+
const domainConfig = config.getActiveDomainConfig();
|
|
112
|
+
console.log(chalk.cyan('Applied settings:'));
|
|
113
|
+
console.log(chalk.cyan(' API_BASE_URL:'), chalk.white(domainConfig.API_BASE_URL));
|
|
114
|
+
console.log(chalk.cyan(' DB_NAME: '), chalk.white(domainConfig.DB_NAME));
|
|
115
|
+
console.log('');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.log(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function listDomains() {
|
|
122
|
+
const { activeDomain, domains } = config.listDomains();
|
|
123
|
+
|
|
124
|
+
console.log(chalk.cyan.bold('\n🌐 Domains:\n'));
|
|
125
|
+
|
|
126
|
+
for (const domain of domains) {
|
|
127
|
+
const isActive = domain.DOMAIN_NAME === activeDomain;
|
|
128
|
+
const marker = isActive ? chalk.green('▸ ') : ' ';
|
|
129
|
+
const label = isActive
|
|
130
|
+
? chalk.green.bold(domain.DOMAIN_NAME) + chalk.green(' (active)')
|
|
131
|
+
: chalk.white(domain.DOMAIN_NAME);
|
|
132
|
+
|
|
133
|
+
console.log(`${marker}${label}`);
|
|
134
|
+
console.log(chalk.dim(` API: ${domain.API_BASE_URL} DB: ${domain.DB_NAME}`));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log('');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function removeDomain(name) {
|
|
141
|
+
if (!name) {
|
|
142
|
+
console.log(chalk.red('Usage: wf domain remove <name>'));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
config.removeDomain(name);
|
|
148
|
+
console.log(chalk.green(`\n✓ Domain "${name}" removed.\n`));
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.log(chalk.red(`\n✗ Error: ${error.message}\n`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function printDomainConfig(domain) {
|
|
155
|
+
for (const [key, value] of Object.entries(domain)) {
|
|
156
|
+
console.log(chalk.cyan(` ${key}:`), chalk.white(value));
|
|
157
|
+
}
|
|
158
|
+
console.log('');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = domainCommand;
|
package/src/commands/reset.js
CHANGED
|
@@ -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 {
|
|
7
|
-
const {
|
|
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
|
-
|
|
42
|
+
LOG.header('COMPONENT RESET (Force Update)');
|
|
11
43
|
|
|
12
44
|
const projectRoot = config.get('PROJECT_ROOT');
|
|
13
|
-
|
|
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:
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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: '
|
|
105
|
+
message: 'Which folder to reset?',
|
|
54
106
|
choices: choices
|
|
55
107
|
}]);
|
|
56
108
|
|
|
57
|
-
//
|
|
109
|
+
// Find files
|
|
58
110
|
let jsonFiles = [];
|
|
59
111
|
|
|
60
112
|
if (selected === 'ALL') {
|
|
61
|
-
|
|
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
|
-
|
|
122
|
+
LOG.error(`${selected} folder not found`);
|
|
66
123
|
return;
|
|
67
124
|
}
|
|
68
125
|
|
|
69
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
146
|
+
LOG.warning('No JSON files found');
|
|
147
|
+
console.log();
|
|
80
148
|
return;
|
|
81
149
|
}
|
|
82
150
|
|
|
83
|
-
//
|
|
84
|
-
|
|
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: '
|
|
158
|
+
message: 'Continue?',
|
|
90
159
|
default: false
|
|
91
160
|
}]);
|
|
92
161
|
|
|
93
162
|
if (!confirm) {
|
|
94
|
-
|
|
163
|
+
LOG.warning('Operation cancelled.');
|
|
164
|
+
console.log();
|
|
95
165
|
return;
|
|
96
166
|
}
|
|
97
167
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
168
|
+
// Group by component type
|
|
169
|
+
const componentStats = {};
|
|
170
|
+
const errors = [];
|
|
101
171
|
|
|
102
|
-
console.log();
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
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
|
|
183
|
+
const metadata = await getJsonMetadata(jsonPath);
|
|
109
184
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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('
|
|
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('
|
|
234
|
+
reinitSpinner.succeed(chalk.green(' System re-initialized'));
|
|
139
235
|
} else {
|
|
140
|
-
reinitSpinner.warn(chalk.yellow('
|
|
236
|
+
reinitSpinner.warn(chalk.yellow(' System re-initialization failed (continuing)'));
|
|
141
237
|
}
|
|
142
238
|
}
|
|
143
239
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.log(chalk.
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|