@burgan-tech/vnext-workflow-cli 1.0.2 → 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 +175 -4
- package/bin/workflow.js +47 -13
- package/package.json +1 -1
- package/src/commands/config.js +13 -6
- package/src/commands/domain.js +161 -0
- package/src/lib/api.js +7 -7
- package/src/lib/config.js +203 -31
- package/src/lib/db.js +10 -10
package/README.md
CHANGED
|
@@ -254,18 +254,85 @@ wf csx --file x.csx # Process a single file
|
|
|
254
254
|
**Purpose**: Configuration management
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
|
-
wf config get # Show all settings
|
|
257
|
+
wf config get # Show all settings (active domain)
|
|
258
258
|
wf config get PROJECT_ROOT # Show a specific setting
|
|
259
|
-
wf config set DB_PASSWORD pass # Change a setting
|
|
259
|
+
wf config set DB_PASSWORD pass # Change a setting (on active domain)
|
|
260
260
|
```
|
|
261
261
|
|
|
262
|
+
**Note:** `config get` and `config set` always operate on the **active domain**. Use `wf domain use <name>` to switch domains.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### `wf domain [action] [name] [options]`
|
|
267
|
+
|
|
268
|
+
**Purpose**: Multidomain management
|
|
269
|
+
|
|
270
|
+
Manage multiple domain configurations. Switch between domains with a single command. All CLI commands automatically use the active domain's settings.
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Show active domain name
|
|
274
|
+
wf domain active
|
|
275
|
+
|
|
276
|
+
# List all domains
|
|
277
|
+
wf domain list
|
|
278
|
+
wf domain --list
|
|
279
|
+
|
|
280
|
+
# Add a new domain
|
|
281
|
+
wf domain add staging --API_BASE_URL http://staging.example.com:4201 --DB_NAME vNext_StagingDb
|
|
282
|
+
|
|
283
|
+
# Add a domain with multiple settings
|
|
284
|
+
wf domain add production \
|
|
285
|
+
--API_BASE_URL http://prod.example.com:4201 \
|
|
286
|
+
--DB_NAME vNext_ProdDb \
|
|
287
|
+
--DB_HOST prod-db.example.com \
|
|
288
|
+
--DB_USER prod_user \
|
|
289
|
+
--DB_PASSWORD prod_pass
|
|
290
|
+
|
|
291
|
+
# Switch active domain
|
|
292
|
+
wf domain use staging
|
|
293
|
+
|
|
294
|
+
# Remove a domain
|
|
295
|
+
wf domain remove staging
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Notes:**
|
|
299
|
+
- When adding a domain, any unspecified settings are inherited from the `default` domain.
|
|
300
|
+
- The `default` domain cannot be removed.
|
|
301
|
+
- If the active domain is removed, the CLI automatically switches to `default`.
|
|
302
|
+
|
|
262
303
|
---
|
|
263
304
|
|
|
264
305
|
## ⚙️ Configuration Variables
|
|
265
306
|
|
|
266
307
|
Config file location: `~/.config/vnext-workflow-cli/config.json`
|
|
267
308
|
|
|
268
|
-
###
|
|
309
|
+
### Config File Format
|
|
310
|
+
|
|
311
|
+
The config file uses a domain-aware structure. Each domain has its own set of configuration values:
|
|
312
|
+
|
|
313
|
+
```json
|
|
314
|
+
{
|
|
315
|
+
"ACTIVE_DOMAIN": "default",
|
|
316
|
+
"DOMAINS": [
|
|
317
|
+
{
|
|
318
|
+
"DOMAIN_NAME": "default",
|
|
319
|
+
"AUTO_DISCOVER": true,
|
|
320
|
+
"API_BASE_URL": "http://localhost:4201",
|
|
321
|
+
"API_VERSION": "v1",
|
|
322
|
+
"DB_HOST": "localhost",
|
|
323
|
+
"DB_PORT": 5432,
|
|
324
|
+
"DB_NAME": "vNext_WorkflowDb",
|
|
325
|
+
"DB_USER": "postgres",
|
|
326
|
+
"DB_PASSWORD": "postgres",
|
|
327
|
+
"USE_DOCKER": false,
|
|
328
|
+
"DOCKER_POSTGRES_CONTAINER": "vnext-postgres",
|
|
329
|
+
"DEBUG_MODE": false
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### All Available Settings (Per Domain)
|
|
269
336
|
|
|
270
337
|
| Variable | Default | Description |
|
|
271
338
|
|----------|---------|-------------|
|
|
@@ -287,7 +354,7 @@ Config file location: `~/.config/vnext-workflow-cli/config.json`
|
|
|
287
354
|
### Quick Setup Examples
|
|
288
355
|
|
|
289
356
|
```bash
|
|
290
|
-
# API settings
|
|
357
|
+
# API settings (applied to active domain)
|
|
291
358
|
wf config set API_BASE_URL http://localhost:4201
|
|
292
359
|
wf config set API_VERSION v1
|
|
293
360
|
|
|
@@ -351,6 +418,26 @@ wf reset
|
|
|
351
418
|
wf csx
|
|
352
419
|
```
|
|
353
420
|
|
|
421
|
+
### 6. Multidomain Workflow
|
|
422
|
+
```bash
|
|
423
|
+
# Add domains
|
|
424
|
+
wf domain add domain-a --API_BASE_URL http://localhost:4201 --DB_NAME vNext_DomainA
|
|
425
|
+
wf domain add domain-b --API_BASE_URL http://localhost:4221 --DB_NAME vNext_DomainB
|
|
426
|
+
|
|
427
|
+
# Work on Domain A
|
|
428
|
+
wf domain use domain-a
|
|
429
|
+
wf check
|
|
430
|
+
wf update
|
|
431
|
+
|
|
432
|
+
# Switch to Domain B - config is applied automatically
|
|
433
|
+
wf domain use domain-b
|
|
434
|
+
wf check
|
|
435
|
+
wf update
|
|
436
|
+
|
|
437
|
+
# See all domains
|
|
438
|
+
wf domain list
|
|
439
|
+
```
|
|
440
|
+
|
|
354
441
|
---
|
|
355
442
|
|
|
356
443
|
## 🔄 Command Comparison
|
|
@@ -364,6 +451,89 @@ wf csx
|
|
|
364
451
|
|
|
365
452
|
---
|
|
366
453
|
|
|
454
|
+
## 🔀 Multidomain Support
|
|
455
|
+
|
|
456
|
+
### Overview
|
|
457
|
+
|
|
458
|
+
The CLI supports managing multiple domain configurations. Each domain has its own `API_BASE_URL`, `DB_NAME`, and other settings. Switch between domains with a single command.
|
|
459
|
+
|
|
460
|
+
### Backward Compatibility
|
|
461
|
+
|
|
462
|
+
- Existing single-domain configurations are automatically migrated to the new format.
|
|
463
|
+
- A `default` domain is created with your existing settings.
|
|
464
|
+
- If you don't use multidomain features, everything works exactly as before.
|
|
465
|
+
- All `wf config get/set` commands continue to work (they operate on the active domain).
|
|
466
|
+
|
|
467
|
+
### Migration
|
|
468
|
+
|
|
469
|
+
When upgrading from an older version, the CLI automatically migrates the config file:
|
|
470
|
+
|
|
471
|
+
**Before (old flat format):**
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"API_BASE_URL": "http://localhost:4201",
|
|
475
|
+
"DB_NAME": "vNext_WorkflowDb"
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**After (new domain-aware format):**
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"ACTIVE_DOMAIN": "default",
|
|
483
|
+
"DOMAINS": [
|
|
484
|
+
{
|
|
485
|
+
"DOMAIN_NAME": "default",
|
|
486
|
+
"AUTO_DISCOVER": true,
|
|
487
|
+
"API_BASE_URL": "http://localhost:4201",
|
|
488
|
+
"API_VERSION": "v1",
|
|
489
|
+
"DB_HOST": "localhost",
|
|
490
|
+
"DB_PORT": 5432,
|
|
491
|
+
"DB_NAME": "vNext_WorkflowDb",
|
|
492
|
+
"DB_USER": "postgres",
|
|
493
|
+
"DB_PASSWORD": "postgres",
|
|
494
|
+
"USE_DOCKER": false,
|
|
495
|
+
"DOCKER_POSTGRES_CONTAINER": "vnext-postgres",
|
|
496
|
+
"DEBUG_MODE": false
|
|
497
|
+
}
|
|
498
|
+
]
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Your existing values are preserved. Any missing keys are filled in from defaults (11 keys total).
|
|
503
|
+
|
|
504
|
+
No manual action is required. The migration happens automatically on first run.
|
|
505
|
+
|
|
506
|
+
### Domain Commands
|
|
507
|
+
|
|
508
|
+
| Command | Description |
|
|
509
|
+
|---------|-------------|
|
|
510
|
+
| `wf domain active` | Show active domain name |
|
|
511
|
+
| `wf domain list` | List all domains with active indicator |
|
|
512
|
+
| `wf domain --list` | List all domains (shorthand) |
|
|
513
|
+
| `wf domain add <name> [options]` | Add a new domain |
|
|
514
|
+
| `wf domain use <name>` | Switch active domain |
|
|
515
|
+
| `wf domain remove <name>` | Remove a domain |
|
|
516
|
+
|
|
517
|
+
### Available Options for `wf domain add`
|
|
518
|
+
|
|
519
|
+
| Option | Description |
|
|
520
|
+
|--------|-------------|
|
|
521
|
+
| `--API_BASE_URL <url>` | API base URL |
|
|
522
|
+
| `--API_VERSION <version>` | API version |
|
|
523
|
+
| `--DB_HOST <host>` | Database host |
|
|
524
|
+
| `--DB_PORT <port>` | Database port |
|
|
525
|
+
| `--DB_NAME <name>` | Database name |
|
|
526
|
+
| `--DB_USER <user>` | Database user |
|
|
527
|
+
| `--DB_PASSWORD <password>` | Database password |
|
|
528
|
+
| `--AUTO_DISCOVER <true/false>` | Auto discover components |
|
|
529
|
+
| `--USE_DOCKER <true/false>` | Use Docker for DB |
|
|
530
|
+
| `--DOCKER_POSTGRES_CONTAINER <name>` | Docker container name |
|
|
531
|
+
| `--DEBUG_MODE <true/false>` | Debug mode |
|
|
532
|
+
|
|
533
|
+
Unspecified options inherit from the `default` domain.
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
367
537
|
## 🆘 Troubleshooting
|
|
368
538
|
|
|
369
539
|
### "vnext.config.json not found"
|
|
@@ -501,6 +671,7 @@ vnext-workflow-cli/
|
|
|
501
671
|
│ │ ├── check.js
|
|
502
672
|
│ │ ├── config.js
|
|
503
673
|
│ │ ├── csx.js
|
|
674
|
+
│ │ ├── domain.js # Multidomain management
|
|
504
675
|
│ │ ├── reset.js
|
|
505
676
|
│ │ ├── sync.js
|
|
506
677
|
│ │ └── update.js
|
package/bin/workflow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { program } = require('commander');
|
|
3
|
+
const { program, Argument } = require('commander');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const pkg = require('../package.json');
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ const updateCommand = require('../src/commands/update');
|
|
|
11
11
|
const syncCommand = require('../src/commands/sync');
|
|
12
12
|
const resetCommand = require('../src/commands/reset');
|
|
13
13
|
const configCommand = require('../src/commands/config');
|
|
14
|
+
const domainCommand = require('../src/commands/domain');
|
|
14
15
|
|
|
15
16
|
program
|
|
16
17
|
.name('workflow')
|
|
@@ -20,46 +21,80 @@ program
|
|
|
20
21
|
// Check command
|
|
21
22
|
program
|
|
22
23
|
.command('check')
|
|
23
|
-
.description('
|
|
24
|
+
.description('System check (API, DB, directories)')
|
|
24
25
|
.action(checkCommand);
|
|
25
26
|
|
|
26
27
|
// CSX command
|
|
27
28
|
program
|
|
28
29
|
.command('csx')
|
|
29
|
-
.description('CSX
|
|
30
|
-
.option('-a, --all', '
|
|
31
|
-
.option('-f, --file <path>', '
|
|
30
|
+
.description('Update CSX files')
|
|
31
|
+
.option('-a, --all', 'Update all CSX files')
|
|
32
|
+
.option('-f, --file <path>', 'Update a specific CSX file')
|
|
32
33
|
.action(csxCommand);
|
|
33
34
|
|
|
34
35
|
// Update command
|
|
35
36
|
program
|
|
36
37
|
.command('update')
|
|
37
|
-
.description('
|
|
38
|
-
.option('-a, --all', '
|
|
39
|
-
.option('-f, --file <path>', '
|
|
38
|
+
.description('Update workflows')
|
|
39
|
+
.option('-a, --all', 'Update all workflows')
|
|
40
|
+
.option('-f, --file <path>', 'Update a specific workflow')
|
|
40
41
|
.action(updateCommand);
|
|
41
42
|
|
|
42
43
|
// Sync command
|
|
43
44
|
program
|
|
44
45
|
.command('sync')
|
|
45
|
-
.description('
|
|
46
|
+
.description('Add missing entries to DB')
|
|
46
47
|
.action(syncCommand);
|
|
47
48
|
|
|
48
49
|
// Reset command
|
|
49
50
|
program
|
|
50
51
|
.command('reset')
|
|
51
|
-
.description('
|
|
52
|
+
.description('Reset workflows (force update)')
|
|
52
53
|
.action(resetCommand);
|
|
53
54
|
|
|
54
55
|
// Config command
|
|
55
56
|
program
|
|
56
57
|
.command('config')
|
|
57
|
-
.description('
|
|
58
|
-
.argument('<action>', 'set
|
|
58
|
+
.description('Configuration management')
|
|
59
|
+
.argument('<action>', 'set or get')
|
|
59
60
|
.argument('[key]', 'Config key')
|
|
60
61
|
.argument('[value]', 'Config value')
|
|
61
62
|
.action(configCommand);
|
|
62
63
|
|
|
64
|
+
// Domain command
|
|
65
|
+
program
|
|
66
|
+
.command('domain')
|
|
67
|
+
.description('Domain management (multidomain support)')
|
|
68
|
+
.addArgument(new Argument('[action]', 'Action to perform').choices(['active', 'add', 'use', 'list', 'remove']))
|
|
69
|
+
.argument('[name]', 'Domain name')
|
|
70
|
+
.option('-l, --list', 'List domains')
|
|
71
|
+
.option('--API_BASE_URL <url>', 'API base URL')
|
|
72
|
+
.option('--API_VERSION <version>', 'API version')
|
|
73
|
+
.option('--DB_HOST <host>', 'Database host')
|
|
74
|
+
.option('--DB_PORT <port>', 'Database port')
|
|
75
|
+
.option('--DB_NAME <dbname>', 'Database name')
|
|
76
|
+
.option('--DB_USER <user>', 'Database username')
|
|
77
|
+
.option('--DB_PASSWORD <password>', 'Database password')
|
|
78
|
+
.option('--AUTO_DISCOVER <value>', 'Auto discover (true/false)')
|
|
79
|
+
.option('--USE_DOCKER <value>', 'Use Docker (true/false)')
|
|
80
|
+
.option('--DOCKER_POSTGRES_CONTAINER <container>', 'Docker PostgreSQL container name')
|
|
81
|
+
.option('--DEBUG_MODE <value>', 'Debug mode (true/false)')
|
|
82
|
+
.addHelpText('after', `
|
|
83
|
+
Examples:
|
|
84
|
+
wf domain active Show active domain name
|
|
85
|
+
wf domain list List domains
|
|
86
|
+
wf domain --list List domains
|
|
87
|
+
wf domain add domain-a --API_BASE_URL http://localhost:4201 --DB_NAME myDb Add a new domain
|
|
88
|
+
wf domain use domain-a Switch active domain
|
|
89
|
+
wf domain remove domain-a Remove a domain
|
|
90
|
+
|
|
91
|
+
Notes:
|
|
92
|
+
- When adding a domain, unspecified settings are inherited from the default domain.
|
|
93
|
+
- The default domain cannot be deleted.
|
|
94
|
+
- If the active domain is deleted, it automatically switches back to default.
|
|
95
|
+
`)
|
|
96
|
+
.action(domainCommand);
|
|
97
|
+
|
|
63
98
|
// Parse arguments
|
|
64
99
|
program.parse(process.argv);
|
|
65
100
|
|
|
@@ -67,4 +102,3 @@ program.parse(process.argv);
|
|
|
67
102
|
if (!process.argv.slice(2).length) {
|
|
68
103
|
program.outputHelp();
|
|
69
104
|
}
|
|
70
|
-
|
package/package.json
CHANGED
package/src/commands/config.js
CHANGED
|
@@ -7,21 +7,28 @@ async function configCommand(action, key, value) {
|
|
|
7
7
|
const val = config.get(key);
|
|
8
8
|
console.log(chalk.cyan(`${key}:`), val);
|
|
9
9
|
} else {
|
|
10
|
-
// Show all config
|
|
11
|
-
console.log(chalk.cyan.bold('\n📝 Current Configuration:\n'));
|
|
10
|
+
// Show all config with active domain info
|
|
12
11
|
const all = config.getAll();
|
|
12
|
+
console.log(chalk.cyan.bold('\n📝 Current Configuration:\n'));
|
|
13
|
+
console.log(chalk.yellow(` Active Domain: ${all.ACTIVE_DOMAIN}\n`));
|
|
13
14
|
for (const [k, v] of Object.entries(all)) {
|
|
14
|
-
|
|
15
|
+
if (k === 'ACTIVE_DOMAIN' || k === 'DOMAIN_NAME') continue;
|
|
16
|
+
console.log(chalk.cyan(` ${k}:`), chalk.white(v));
|
|
15
17
|
}
|
|
16
|
-
console.log(chalk.dim(`\
|
|
18
|
+
console.log(chalk.dim(`\n Config file: ${config.path}`));
|
|
19
|
+
console.log(chalk.dim(` Tip: Use "wf domain list" to see all domains.\n`));
|
|
17
20
|
}
|
|
18
21
|
} else if (action === 'set') {
|
|
19
22
|
if (!key || value === undefined) {
|
|
20
23
|
console.log(chalk.red('Usage: workflow config set <key> <value>'));
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
try {
|
|
27
|
+
config.set(key, value);
|
|
28
|
+
console.log(chalk.green(`✓ ${key} = ${value}`));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.log(chalk.red(`✗ ${error.message}`));
|
|
31
|
+
}
|
|
25
32
|
} else {
|
|
26
33
|
console.log(chalk.red('Invalid action. Use: get or set'));
|
|
27
34
|
}
|
|
@@ -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/lib/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Tests the API connection
|
|
5
5
|
* @param {string} baseUrl - API base URL
|
|
6
|
-
* @returns {Promise<boolean>}
|
|
6
|
+
* @returns {Promise<boolean>} Connection status
|
|
7
7
|
*/
|
|
8
8
|
async function testApiConnection(baseUrl) {
|
|
9
9
|
try {
|
|
@@ -17,9 +17,9 @@ async function testApiConnection(baseUrl) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Publishes a component to the API
|
|
21
21
|
* @param {string} baseUrl - API base URL
|
|
22
|
-
* @param {Object} componentData -
|
|
22
|
+
* @param {Object} componentData - Component JSON data
|
|
23
23
|
* @returns {Promise<Object>} API response
|
|
24
24
|
*/
|
|
25
25
|
async function publishComponent(baseUrl, componentData) {
|
|
@@ -39,7 +39,7 @@ async function publishComponent(baseUrl, componentData) {
|
|
|
39
39
|
data: response.data
|
|
40
40
|
};
|
|
41
41
|
} catch (error) {
|
|
42
|
-
// API
|
|
42
|
+
// Extract API error details
|
|
43
43
|
let errorMessage = error.message;
|
|
44
44
|
let errorDetails = null;
|
|
45
45
|
|
|
@@ -68,10 +68,10 @@ async function publishComponent(baseUrl, componentData) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
71
|
+
* Reinitializes the system
|
|
72
72
|
* @param {string} baseUrl - API base URL
|
|
73
73
|
* @param {string} version - API version
|
|
74
|
-
* @returns {Promise<boolean>}
|
|
74
|
+
* @returns {Promise<boolean>} Success status
|
|
75
75
|
*/
|
|
76
76
|
async function reinitializeSystem(baseUrl, version) {
|
|
77
77
|
const url = `${baseUrl}/api/${version}/definitions/re-initialize`;
|
package/src/lib/config.js
CHANGED
|
@@ -1,67 +1,239 @@
|
|
|
1
1
|
const Conf = require('conf');
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
// Default config values for a domain
|
|
4
|
+
const DEFAULT_DOMAIN_CONFIG = {
|
|
5
|
+
AUTO_DISCOVER: true,
|
|
6
|
+
API_BASE_URL: 'http://localhost:4201',
|
|
7
|
+
API_VERSION: 'v1',
|
|
8
|
+
DB_HOST: 'localhost',
|
|
9
|
+
DB_PORT: 5432,
|
|
10
|
+
DB_NAME: 'vNext_WorkflowDb',
|
|
11
|
+
DB_USER: 'postgres',
|
|
12
|
+
DB_PASSWORD: 'postgres',
|
|
13
|
+
USE_DOCKER: false,
|
|
14
|
+
DOCKER_POSTGRES_CONTAINER: 'vnext-postgres',
|
|
15
|
+
DEBUG_MODE: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DOMAIN_CONFIG_KEYS = Object.keys(DEFAULT_DOMAIN_CONFIG);
|
|
3
19
|
|
|
4
20
|
const config = new Conf({
|
|
5
|
-
projectName: 'vnext-workflow-cli'
|
|
6
|
-
defaults: {
|
|
7
|
-
AUTO_DISCOVER: true,
|
|
8
|
-
API_BASE_URL: 'http://localhost:4201',
|
|
9
|
-
API_VERSION: 'v1',
|
|
10
|
-
DB_HOST: 'localhost',
|
|
11
|
-
DB_PORT: 5432,
|
|
12
|
-
DB_NAME: 'vNext_WorkflowDb',
|
|
13
|
-
DB_USER: 'postgres',
|
|
14
|
-
DB_PASSWORD: 'postgres',
|
|
15
|
-
USE_DOCKER: false,
|
|
16
|
-
DOCKER_POSTGRES_CONTAINER: 'vnext-postgres',
|
|
17
|
-
DEBUG_MODE: false
|
|
18
|
-
}
|
|
21
|
+
projectName: 'vnext-workflow-cli'
|
|
19
22
|
});
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
25
|
+
* Migrates old flat config to new domain-aware format.
|
|
26
|
+
* Old: { API_BASE_URL: "...", DB_NAME: "..." }
|
|
27
|
+
* New: { ACTIVE_DOMAIN: "default", DOMAINS: [{ DOMAIN_NAME: "default", ... }] }
|
|
28
|
+
*/
|
|
29
|
+
function migrateConfig() {
|
|
30
|
+
const store = config.store;
|
|
31
|
+
|
|
32
|
+
// Already in new format - skip
|
|
33
|
+
if (store.ACTIVE_DOMAIN !== undefined && Array.isArray(store.DOMAINS)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Collect existing values from old flat config
|
|
38
|
+
const existingValues = {};
|
|
39
|
+
for (const key of DOMAIN_CONFIG_KEYS) {
|
|
40
|
+
if (store[key] !== undefined) {
|
|
41
|
+
existingValues[key] = store[key];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Create default domain: defaults + any existing overrides
|
|
46
|
+
const defaultDomain = {
|
|
47
|
+
DOMAIN_NAME: 'default',
|
|
48
|
+
...DEFAULT_DOMAIN_CONFIG,
|
|
49
|
+
...existingValues
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Clear old keys and set new format via conf API
|
|
53
|
+
for (const key of DOMAIN_CONFIG_KEYS) {
|
|
54
|
+
config.delete(key);
|
|
55
|
+
}
|
|
56
|
+
config.set('ACTIVE_DOMAIN', 'default');
|
|
57
|
+
config.set('DOMAINS', [defaultDomain]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Run migration on module load
|
|
61
|
+
migrateConfig();
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns the active domain config object.
|
|
65
|
+
* @returns {Object} Active domain config
|
|
66
|
+
*/
|
|
67
|
+
function getActiveDomainConfig() {
|
|
68
|
+
const activeDomain = config.get('ACTIVE_DOMAIN');
|
|
69
|
+
const domains = config.get('DOMAINS') || [];
|
|
70
|
+
const domain = domains.find(d => d.DOMAIN_NAME === activeDomain);
|
|
71
|
+
|
|
72
|
+
if (!domain) {
|
|
73
|
+
throw new Error(`Active domain "${activeDomain}" not found in config.`);
|
|
74
|
+
}
|
|
75
|
+
return domain;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Gets a config value from the active domain.
|
|
80
|
+
* PROJECT_ROOT always returns process.cwd().
|
|
24
81
|
* @param {string} key - Config key
|
|
25
82
|
* @returns {any} Config value
|
|
26
83
|
*/
|
|
27
84
|
function get(key) {
|
|
28
|
-
// PROJECT_ROOT is always the current working directory
|
|
29
85
|
if (key === 'PROJECT_ROOT') {
|
|
30
86
|
return process.cwd();
|
|
31
87
|
}
|
|
32
|
-
|
|
88
|
+
if (key === 'ACTIVE_DOMAIN') {
|
|
89
|
+
return config.get('ACTIVE_DOMAIN');
|
|
90
|
+
}
|
|
91
|
+
const domainConfig = getActiveDomainConfig();
|
|
92
|
+
return domainConfig[key];
|
|
33
93
|
}
|
|
34
94
|
|
|
35
95
|
/**
|
|
36
|
-
* Sets a config value
|
|
37
|
-
* PROJECT_ROOT cannot be set (
|
|
96
|
+
* Sets a config value on the active domain.
|
|
97
|
+
* PROJECT_ROOT cannot be set (always uses cwd).
|
|
38
98
|
* @param {string} key - Config key
|
|
39
99
|
* @param {any} value - Config value
|
|
40
100
|
*/
|
|
41
101
|
function set(key, value) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
const RESERVED_KEYS = {
|
|
103
|
+
PROJECT_ROOT: 'PROJECT_ROOT is always the current working directory and cannot be changed.',
|
|
104
|
+
DOMAIN_NAME: 'DOMAIN_NAME cannot be changed directly. Use "wf domain add/remove" instead.',
|
|
105
|
+
ACTIVE_DOMAIN: 'ACTIVE_DOMAIN cannot be changed directly. Use "wf domain use <name>" instead.'
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (RESERVED_KEYS[key]) {
|
|
109
|
+
throw new Error(RESERVED_KEYS[key]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const activeDomainName = config.get('ACTIVE_DOMAIN');
|
|
113
|
+
const domains = config.get('DOMAINS') || [];
|
|
114
|
+
const idx = domains.findIndex(d => d.DOMAIN_NAME === activeDomainName);
|
|
115
|
+
|
|
116
|
+
if (idx === -1) {
|
|
117
|
+
throw new Error(`Active domain "${activeDomainName}" not found.`);
|
|
46
118
|
}
|
|
47
|
-
|
|
119
|
+
|
|
120
|
+
domains[idx][key] = value;
|
|
121
|
+
config.set('DOMAINS', domains);
|
|
48
122
|
}
|
|
49
123
|
|
|
50
124
|
/**
|
|
51
|
-
* Gets all config values
|
|
52
|
-
* @returns {Object} All config values
|
|
125
|
+
* Gets all config values from the active domain.
|
|
126
|
+
* @returns {Object} All config values including PROJECT_ROOT and ACTIVE_DOMAIN
|
|
53
127
|
*/
|
|
54
128
|
function getAll() {
|
|
129
|
+
const domainConfig = getActiveDomainConfig();
|
|
55
130
|
return {
|
|
56
131
|
PROJECT_ROOT: process.cwd(),
|
|
57
|
-
|
|
132
|
+
ACTIVE_DOMAIN: config.get('ACTIVE_DOMAIN'),
|
|
133
|
+
...domainConfig
|
|
58
134
|
};
|
|
59
135
|
}
|
|
60
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Resets config to default state.
|
|
139
|
+
*/
|
|
140
|
+
function clear() {
|
|
141
|
+
config.clear();
|
|
142
|
+
config.set('ACTIVE_DOMAIN', 'default');
|
|
143
|
+
config.set('DOMAINS', [{
|
|
144
|
+
DOMAIN_NAME: 'default',
|
|
145
|
+
...DEFAULT_DOMAIN_CONFIG
|
|
146
|
+
}]);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Adds a new domain. Missing values are inherited from the default domain.
|
|
151
|
+
* @param {string} name - Domain name
|
|
152
|
+
* @param {Object} options - Domain config overrides
|
|
153
|
+
* @returns {Object} Created domain config
|
|
154
|
+
*/
|
|
155
|
+
function addDomain(name, options) {
|
|
156
|
+
const domains = config.get('DOMAINS') || [];
|
|
157
|
+
|
|
158
|
+
if (domains.find(d => d.DOMAIN_NAME === name)) {
|
|
159
|
+
throw new Error(`Domain "${name}" already exists.`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Use default domain as base, fall back to DEFAULT_DOMAIN_CONFIG
|
|
163
|
+
const defaultDomain = domains.find(d => d.DOMAIN_NAME === 'default');
|
|
164
|
+
const base = defaultDomain
|
|
165
|
+
? { ...defaultDomain }
|
|
166
|
+
: { ...DEFAULT_DOMAIN_CONFIG };
|
|
167
|
+
delete base.DOMAIN_NAME;
|
|
168
|
+
|
|
169
|
+
const newDomain = {
|
|
170
|
+
DOMAIN_NAME: name,
|
|
171
|
+
...base,
|
|
172
|
+
...options
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
domains.push(newDomain);
|
|
176
|
+
config.set('DOMAINS', domains);
|
|
177
|
+
return newDomain;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Switches the active domain.
|
|
182
|
+
* @param {string} name - Domain name to activate
|
|
183
|
+
*/
|
|
184
|
+
function useDomain(name) {
|
|
185
|
+
const domains = config.get('DOMAINS') || [];
|
|
186
|
+
if (!domains.find(d => d.DOMAIN_NAME === name)) {
|
|
187
|
+
throw new Error(`Domain "${name}" not found.`);
|
|
188
|
+
}
|
|
189
|
+
config.set('ACTIVE_DOMAIN', name);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Lists all domains with active domain info.
|
|
194
|
+
* @returns {Object} { activeDomain, domains }
|
|
195
|
+
*/
|
|
196
|
+
function listDomains() {
|
|
197
|
+
return {
|
|
198
|
+
activeDomain: config.get('ACTIVE_DOMAIN'),
|
|
199
|
+
domains: config.get('DOMAINS') || []
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Removes a domain. Cannot remove the default domain.
|
|
205
|
+
* If the removed domain was active, switches to default.
|
|
206
|
+
* @param {string} name - Domain name to remove
|
|
207
|
+
*/
|
|
208
|
+
function removeDomain(name) {
|
|
209
|
+
if (name === 'default') {
|
|
210
|
+
throw new Error('Cannot remove the default domain.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const domains = config.get('DOMAINS') || [];
|
|
214
|
+
const filtered = domains.filter(d => d.DOMAIN_NAME !== name);
|
|
215
|
+
|
|
216
|
+
if (filtered.length === domains.length) {
|
|
217
|
+
throw new Error(`Domain "${name}" not found.`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
config.set('DOMAINS', filtered);
|
|
221
|
+
|
|
222
|
+
if (config.get('ACTIVE_DOMAIN') === name) {
|
|
223
|
+
config.set('ACTIVE_DOMAIN', 'default');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
61
227
|
module.exports = {
|
|
62
228
|
get,
|
|
63
229
|
set,
|
|
64
230
|
getAll,
|
|
65
|
-
clear
|
|
66
|
-
path: config.path
|
|
231
|
+
clear,
|
|
232
|
+
path: config.path,
|
|
233
|
+
addDomain,
|
|
234
|
+
useDomain,
|
|
235
|
+
listDomains,
|
|
236
|
+
removeDomain,
|
|
237
|
+
getActiveDomainConfig,
|
|
238
|
+
DEFAULT_DOMAIN_CONFIG
|
|
67
239
|
};
|
package/src/lib/db.js
CHANGED
|
@@ -5,11 +5,11 @@ const util = require('util');
|
|
|
5
5
|
const execPromise = util.promisify(exec);
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Tests the DB connection
|
|
9
9
|
*/
|
|
10
10
|
async function testDbConnection(dbConfig) {
|
|
11
11
|
if (dbConfig.useDocker) {
|
|
12
|
-
//
|
|
12
|
+
// Test via Docker
|
|
13
13
|
try {
|
|
14
14
|
const cmd = `docker exec ${dbConfig.dockerContainer} psql -U ${dbConfig.user} -d ${dbConfig.database} -c "SELECT 1;"`;
|
|
15
15
|
await execPromise(cmd);
|
|
@@ -18,7 +18,7 @@ async function testDbConnection(dbConfig) {
|
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
20
|
} else {
|
|
21
|
-
//
|
|
21
|
+
// Direct connection
|
|
22
22
|
const client = new Client({
|
|
23
23
|
host: dbConfig.host,
|
|
24
24
|
port: dbConfig.port,
|
|
@@ -39,15 +39,15 @@ async function testDbConnection(dbConfig) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Finds the workflow's ID in the DB
|
|
43
|
+
* NOTE: We don't check version, only the Key (like the bash script)
|
|
44
44
|
*/
|
|
45
45
|
async function getInstanceId(dbConfig, schema, key, version) {
|
|
46
46
|
const dbSchema = schema.replace(/-/g, '_');
|
|
47
47
|
const query = `SELECT "Id" FROM "${dbSchema}"."Instances" WHERE "Key" = $1 ORDER BY "CreatedAt" DESC LIMIT 1`;
|
|
48
48
|
|
|
49
49
|
if (dbConfig.useDocker) {
|
|
50
|
-
// Docker
|
|
50
|
+
// Via Docker - escape double quotes in SQL with backslash
|
|
51
51
|
const cmd = `docker exec ${dbConfig.dockerContainer} psql -U ${dbConfig.user} -d ${dbConfig.database} -t -c "SELECT \\"Id\\" FROM \\"${dbSchema}\\".\\"Instances\\" WHERE \\"Key\\" = '${key}' ORDER BY \\"CreatedAt\\" DESC LIMIT 1"`;
|
|
52
52
|
try {
|
|
53
53
|
const { stdout } = await execPromise(cmd);
|
|
@@ -57,7 +57,7 @@ async function getInstanceId(dbConfig, schema, key, version) {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
} else {
|
|
60
|
-
//
|
|
60
|
+
// Direct connection
|
|
61
61
|
const client = new Client({
|
|
62
62
|
host: dbConfig.host,
|
|
63
63
|
port: dbConfig.port,
|
|
@@ -78,13 +78,13 @@ async function getInstanceId(dbConfig, schema, key, version) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
*
|
|
81
|
+
* Deletes a workflow from the DB
|
|
82
82
|
*/
|
|
83
83
|
async function deleteWorkflow(dbConfig, schema, instanceId) {
|
|
84
84
|
const dbSchema = schema.replace(/-/g, '_');
|
|
85
85
|
|
|
86
86
|
if (dbConfig.useDocker) {
|
|
87
|
-
// Docker
|
|
87
|
+
// Via Docker - escape double quotes
|
|
88
88
|
const cmd = `docker exec ${dbConfig.dockerContainer} psql -U ${dbConfig.user} -d ${dbConfig.database} -c "DELETE FROM \\"${dbSchema}\\".\\"Instances\\" WHERE \\"Id\\" = '${instanceId}'"`;
|
|
89
89
|
try {
|
|
90
90
|
await execPromise(cmd);
|
|
@@ -93,7 +93,7 @@ async function deleteWorkflow(dbConfig, schema, instanceId) {
|
|
|
93
93
|
return false;
|
|
94
94
|
}
|
|
95
95
|
} else {
|
|
96
|
-
//
|
|
96
|
+
// Direct connection
|
|
97
97
|
const query = `DELETE FROM "${dbSchema}"."Instances" WHERE "Id" = $1`;
|
|
98
98
|
const client = new Client({
|
|
99
99
|
host: dbConfig.host,
|