@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.
@@ -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;
@@ -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
-