@qelos/plugins-cli 0.0.8 → 0.0.10
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 +136 -3
- package/commands/pull.mjs +3 -2
- package/commands/push.mjs +4 -3
- package/controllers/pull.mjs +38 -27
- package/controllers/push.mjs +51 -37
- package/package.json +2 -1
- package/services/blueprints.mjs +157 -0
- package/services/components.mjs +120 -0
- package/services/configurations.mjs +142 -0
- package/services/logger.mjs +112 -0
- package/services/sdk.mjs +57 -0
package/README.md
CHANGED
|
@@ -3,11 +3,144 @@
|
|
|
3
3
|
A command-line interface to help you create and manage your Qelos plugins.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
### Global Installation
|
|
8
|
+
|
|
9
|
+
Install the CLI globally using npm:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @qelos/plugins-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
After installation, the CLI will be available as both `qelos` and `qplay` commands:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
qelos --version
|
|
19
|
+
qplay --version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Environment Variables
|
|
23
|
+
|
|
24
|
+
The CLI requires the following environment variables to connect to your Qelos instance:
|
|
25
|
+
|
|
26
|
+
- `QELOS_URL` - Your Qelos instance URL (default: `http://localhost:3000`)
|
|
27
|
+
- `QELOS_USERNAME` - Your Qelos username (default: `test@test.com`)
|
|
28
|
+
- `QELOS_PASSWORD` - Your Qelos password (default: `admin`)
|
|
29
|
+
|
|
30
|
+
You can set these in your shell profile or use a `.env` file:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export QELOS_URL=https://your-qelos-instance.com
|
|
34
|
+
export QELOS_USERNAME=your-username
|
|
35
|
+
export QELOS_PASSWORD=your-password
|
|
36
|
+
```
|
|
7
37
|
|
|
8
38
|
## Commands
|
|
9
39
|
|
|
10
40
|
### Create a new plugin
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
42
|
+
Create a new plugin project:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
qplay create my-app
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Pull
|
|
49
|
+
|
|
50
|
+
Pull resources from your Qelos instance to your local filesystem. This allows you to work on components, plugins, integrations, and blueprints locally.
|
|
51
|
+
|
|
52
|
+
**Syntax:**
|
|
53
|
+
```bash
|
|
54
|
+
qelos pull <type> <path>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Arguments:**
|
|
58
|
+
- `type` - Type of resource to pull (e.g., `components`, `plugins`, `integrations`, `blueprints`)
|
|
59
|
+
- `path` - Local directory path where resources will be saved
|
|
60
|
+
|
|
61
|
+
**Example - Pull Components:**
|
|
62
|
+
```bash
|
|
63
|
+
qelos pull components ./my-components
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This command will:
|
|
67
|
+
1. Connect to your Qelos instance using the configured credentials
|
|
68
|
+
2. Fetch all components from the instance
|
|
69
|
+
3. Create the target directory if it doesn't exist
|
|
70
|
+
4. Save each component as a `.vue` file using its identifier as the filename
|
|
71
|
+
5. Display progress for each component pulled
|
|
72
|
+
|
|
73
|
+
**Output:**
|
|
74
|
+
```
|
|
75
|
+
Created directory: ./my-components
|
|
76
|
+
Found 5 components to pull
|
|
77
|
+
Pulled component: header-component
|
|
78
|
+
Pulled component: footer-component
|
|
79
|
+
Pulled component: sidebar-component
|
|
80
|
+
All 5 components pulled to ./my-components
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Push
|
|
84
|
+
|
|
85
|
+
Push local resources to your Qelos instance. This allows you to update or create components, plugins, integrations, and blueprints from your local filesystem.
|
|
86
|
+
|
|
87
|
+
**Syntax:**
|
|
88
|
+
```bash
|
|
89
|
+
qelos push <type> <path>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Arguments:**
|
|
93
|
+
- `type` - Type of resource to push (e.g., `components`, `plugins`, `integrations`, `blueprints`)
|
|
94
|
+
- `path` - Local directory path containing the resources to push
|
|
95
|
+
|
|
96
|
+
**Example - Push Components:**
|
|
97
|
+
```bash
|
|
98
|
+
qelos push components ./my-components
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
This command will:
|
|
102
|
+
1. Connect to your Qelos instance using the configured credentials
|
|
103
|
+
2. Read all `.vue` files from the specified directory
|
|
104
|
+
3. For each file:
|
|
105
|
+
- Check if a component with the same identifier exists
|
|
106
|
+
- Update the existing component or create a new one
|
|
107
|
+
- Display progress for each component
|
|
108
|
+
|
|
109
|
+
**Output:**
|
|
110
|
+
```
|
|
111
|
+
Pushing component: header-component
|
|
112
|
+
Component updated: header-component
|
|
113
|
+
Pushing component: new-component
|
|
114
|
+
Component pushed: new-component
|
|
115
|
+
All components pushed
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Workflow Example
|
|
119
|
+
|
|
120
|
+
A typical workflow for working with components:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Pull components from Qelos to work on them locally
|
|
124
|
+
qelos pull components ./local-components
|
|
125
|
+
|
|
126
|
+
# Make changes to the .vue files in ./local-components
|
|
127
|
+
|
|
128
|
+
# Push the updated components back to Qelos
|
|
129
|
+
qelos push components ./local-components
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Help
|
|
133
|
+
|
|
134
|
+
View all available commands and options:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
qelos --help
|
|
138
|
+
qplay --help
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
View help for a specific command:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
qelos pull --help
|
|
145
|
+
qelos push --help
|
|
146
|
+
```
|
package/commands/pull.mjs
CHANGED
|
@@ -2,12 +2,13 @@ import pullController from "../controllers/pull.mjs";
|
|
|
2
2
|
|
|
3
3
|
export default function pullCommand(program) {
|
|
4
4
|
program
|
|
5
|
-
.command('pull [type] [path]', 'pull from qelos app. Ability to pull components,
|
|
5
|
+
.command('pull [type] [path]', 'pull from qelos app. Ability to pull components, blueprints, configurations, and more.',
|
|
6
6
|
(yargs) => {
|
|
7
7
|
return yargs
|
|
8
8
|
.positional('type', {
|
|
9
|
-
describe: 'Type of the resource to pull. Can be components,
|
|
9
|
+
describe: 'Type of the resource to pull. Can be components, blueprints, configurations, or more.',
|
|
10
10
|
type: 'string',
|
|
11
|
+
choices: ['components', 'blueprints', 'configs'],
|
|
11
12
|
required: true
|
|
12
13
|
})
|
|
13
14
|
.positional('path', {
|
package/commands/push.mjs
CHANGED
|
@@ -2,16 +2,17 @@ import pushController from "../controllers/push.mjs";
|
|
|
2
2
|
|
|
3
3
|
export default function createCommand(program) {
|
|
4
4
|
program
|
|
5
|
-
.command('push [type] [path]', 'push to qelos app. Ability to push components,
|
|
5
|
+
.command('push [type] [path]', 'push to qelos app. Ability to push components, blueprints, configurations, and more.',
|
|
6
6
|
(yargs) => {
|
|
7
7
|
return yargs
|
|
8
8
|
.positional('type', {
|
|
9
|
-
describe: 'Type of the
|
|
9
|
+
describe: 'Type of the resource to push. Can be components, blueprints, configurations, or more.',
|
|
10
10
|
type: 'string',
|
|
11
|
+
choices: ['components', 'blueprints', 'configs'],
|
|
11
12
|
required: true
|
|
12
13
|
})
|
|
13
14
|
.positional('path', {
|
|
14
|
-
describe: 'Path to the
|
|
15
|
+
describe: 'Path to the resource to push.',
|
|
15
16
|
type: 'string',
|
|
16
17
|
required: true
|
|
17
18
|
})
|
package/controllers/pull.mjs
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { initializeSdk } from '../services/sdk.mjs';
|
|
2
|
+
import { pullComponents } from '../services/components.mjs';
|
|
3
|
+
import { pullBlueprints } from '../services/blueprints.mjs';
|
|
4
|
+
import { pullConfigurations } from '../services/configurations.mjs';
|
|
5
|
+
import { logger } from '../services/logger.mjs';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
4
8
|
|
|
5
9
|
export default async function pullController({ type, path: targetPath }) {
|
|
10
|
+
try {
|
|
11
|
+
// Validate parent directory exists
|
|
12
|
+
const parentDir = path.dirname(targetPath);
|
|
13
|
+
if (!fs.existsSync(parentDir)) {
|
|
14
|
+
logger.error(`Parent directory does not exist: ${parentDir}`);
|
|
15
|
+
logger.info('Please ensure the parent directory exists');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Warn if target path exists and is not a directory
|
|
20
|
+
if (fs.existsSync(targetPath) && !fs.statSync(targetPath).isDirectory()) {
|
|
21
|
+
logger.error(`Path exists but is not a directory: ${targetPath}`);
|
|
22
|
+
logger.info('Please provide a directory path, not a file');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
6
25
|
|
|
7
|
-
|
|
8
|
-
appUrl: process.env.QELOS_URL || "http://localhost:3000",
|
|
9
|
-
})
|
|
26
|
+
const sdk = await initializeSdk();
|
|
10
27
|
|
|
11
|
-
|
|
12
|
-
username: process.env.QELOS_USERNAME || 'test@test.com',
|
|
13
|
-
password: process.env.QELOS_PASSWORD || 'admin',
|
|
14
|
-
})
|
|
28
|
+
logger.section(`Pulling ${type} to ${targetPath}`);
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
30
|
+
if (type === 'components') {
|
|
31
|
+
await pullComponents(sdk, targetPath);
|
|
32
|
+
} else if (type === 'blueprints') {
|
|
33
|
+
await pullBlueprints(sdk, targetPath);
|
|
34
|
+
} else if (type === 'config' || type === 'configs' || type === 'configuration') {
|
|
35
|
+
await pullConfigurations(sdk, targetPath);
|
|
36
|
+
} else {
|
|
37
|
+
logger.error(`Unknown type: ${type}`);
|
|
38
|
+
logger.info('Supported types: components, blueprints, config, configs, configuration');
|
|
39
|
+
process.exit(1);
|
|
22
40
|
}
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
console.log(`Found ${components.length} components to pull`)
|
|
26
|
-
|
|
27
|
-
await Promise.all(components.map(async (component) => {
|
|
28
|
-
const fileName = `${component.identifier}.vue`
|
|
29
|
-
const filePath = path.join(targetPath, fileName)
|
|
30
|
-
|
|
31
|
-
fs.writeFileSync(filePath, component.content, 'utf-8')
|
|
32
|
-
console.log('Pulled component:', component.identifier)
|
|
33
|
-
}))
|
|
42
|
+
logger.success(`Successfully pulled ${type} to ${targetPath}`);
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.error(`Failed to pull ${type}`, error);
|
|
46
|
+
process.exit(1);
|
|
36
47
|
}
|
|
37
48
|
}
|
package/controllers/push.mjs
CHANGED
|
@@ -1,43 +1,57 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { initializeSdk } from '../services/sdk.mjs';
|
|
2
|
+
import { pushComponents } from '../services/components.mjs';
|
|
3
|
+
import { pushBlueprints } from '../services/blueprints.mjs';
|
|
4
|
+
import { pushConfigurations } from '../services/configurations.mjs';
|
|
5
|
+
import { logger } from '../services/logger.mjs';
|
|
6
|
+
import fs from 'node:fs';
|
|
3
7
|
|
|
4
|
-
export default async
|
|
8
|
+
export default async function pushController({ type, path }) {
|
|
9
|
+
try {
|
|
10
|
+
// Validate path exists
|
|
11
|
+
if (!fs.existsSync(path)) {
|
|
12
|
+
logger.error(`Path does not exist: ${path}`);
|
|
13
|
+
logger.info('Please provide a valid directory path');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
5
16
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
// Validate path is a directory
|
|
18
|
+
if (!fs.statSync(path).isDirectory()) {
|
|
19
|
+
logger.error(`Path is not a directory: ${path}`);
|
|
20
|
+
logger.info('Please provide a directory path, not a file');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
9
23
|
|
|
10
|
-
|
|
11
|
-
username: process.env.QELOS_USERNAME || 'test@test.com',
|
|
12
|
-
password: process.env.QELOS_PASSWORD || 'admin',
|
|
13
|
-
})
|
|
24
|
+
const sdk = await initializeSdk();
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
26
|
+
logger.section(`Pushing ${type} from ${path}`);
|
|
27
|
+
|
|
28
|
+
if (type === 'components') {
|
|
29
|
+
await pushComponents(sdk, path);
|
|
30
|
+
} else if (type === 'blueprints') {
|
|
31
|
+
await pushBlueprints(sdk, path);
|
|
32
|
+
} else if (type === 'config' || type === 'configs' || type === 'configuration') {
|
|
33
|
+
await pushConfigurations(sdk, path);
|
|
34
|
+
} else {
|
|
35
|
+
logger.error(`Unknown type: ${type}`);
|
|
36
|
+
logger.info('Supported types: components, blueprints, config, configs, configuration');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
logger.success(`Successfully pushed ${type}`);
|
|
41
|
+
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Don't log the error again if it's already been logged by the service
|
|
44
|
+
if (!error.message?.includes('Failed to push')) {
|
|
45
|
+
logger.error(`Failed to push ${type}`, error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.env.VERBOSE) {
|
|
49
|
+
console.error('\nStack trace:');
|
|
50
|
+
console.error(error.stack);
|
|
51
|
+
} else {
|
|
52
|
+
console.error('\nRun with VERBOSE=true for full stack trace');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(1);
|
|
42
56
|
}
|
|
43
57
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qelos/plugins-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "CLI to manage QELOS plugins",
|
|
5
5
|
"main": "cli.mjs",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"cli-select": "^1.1.2",
|
|
20
20
|
"decompress-zip": "^0.3.3",
|
|
21
21
|
"follow-redirects": "^1.15.11",
|
|
22
|
+
"jiti": "^2.6.1",
|
|
22
23
|
"rimraf": "^6.0.1",
|
|
23
24
|
"yargs": "^18.0.0",
|
|
24
25
|
"zx": "^8.8.5"
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { logger } from './logger.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push blueprints from local directory to remote
|
|
7
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
8
|
+
* @param {string} path - Path to blueprints directory
|
|
9
|
+
*/
|
|
10
|
+
export async function pushBlueprints(sdk, path) {
|
|
11
|
+
const files = fs.readdirSync(path);
|
|
12
|
+
const blueprintFiles = files.filter(f => f.endsWith('.blueprint.json'));
|
|
13
|
+
|
|
14
|
+
if (blueprintFiles.length === 0) {
|
|
15
|
+
logger.warning(`No blueprint files (*.blueprint.json) found in ${path}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logger.info(`Found ${blueprintFiles.length} blueprint(s) to push`);
|
|
20
|
+
const existingBlueprints = await sdk.manageBlueprints.getList();
|
|
21
|
+
|
|
22
|
+
const results = await Promise.allSettled(blueprintFiles.map(async (file) => {
|
|
23
|
+
if (file.endsWith('.blueprint.json')) {
|
|
24
|
+
let blueprintData;
|
|
25
|
+
try {
|
|
26
|
+
blueprintData = JSON.parse(fs.readFileSync(join(path, file), 'utf-8'));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error(`Failed to parse ${file}`, error);
|
|
29
|
+
throw new Error(`Parse error in ${file}: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const identifier = blueprintData.identifier;
|
|
33
|
+
|
|
34
|
+
if (!identifier) {
|
|
35
|
+
logger.warning(`Skipping ${file}: missing identifier field`);
|
|
36
|
+
return { skipped: true, file };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logger.step(`Pushing blueprint: ${identifier}`);
|
|
40
|
+
|
|
41
|
+
const existingBlueprint = existingBlueprints.find(
|
|
42
|
+
blueprint => blueprint.identifier === identifier
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (existingBlueprint) {
|
|
47
|
+
await sdk.manageBlueprints.update(identifier, blueprintData);
|
|
48
|
+
logger.success(`Updated: ${identifier}`);
|
|
49
|
+
} else {
|
|
50
|
+
await sdk.manageBlueprints.create(blueprintData);
|
|
51
|
+
logger.success(`Created: ${identifier}`);
|
|
52
|
+
}
|
|
53
|
+
return { success: true, identifier };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Extract detailed error information
|
|
56
|
+
let errorMessage = error.message || 'Unknown error';
|
|
57
|
+
let errorDetails = null;
|
|
58
|
+
|
|
59
|
+
// The SDK throws the response body as the error
|
|
60
|
+
if (typeof error === 'object' && error !== null) {
|
|
61
|
+
if (error.message) {
|
|
62
|
+
errorMessage = error.message;
|
|
63
|
+
}
|
|
64
|
+
if (error.error) {
|
|
65
|
+
errorDetails = error.error;
|
|
66
|
+
}
|
|
67
|
+
if (error.errors) {
|
|
68
|
+
errorDetails = error.errors;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.error(`Failed to push ${identifier}: ${errorMessage}`);
|
|
73
|
+
|
|
74
|
+
if (errorDetails) {
|
|
75
|
+
if (typeof errorDetails === 'string') {
|
|
76
|
+
logger.error(` Details: ${errorDetails}`);
|
|
77
|
+
} else {
|
|
78
|
+
logger.error(` Details: ${JSON.stringify(errorDetails, null, 2)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (process.env.VERBOSE && error.stack) {
|
|
83
|
+
logger.debug(`Stack: ${error.stack}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error(`Failed to push ${identifier}: ${errorMessage}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Check for failures
|
|
92
|
+
const failures = results.filter(r => r.status === 'rejected');
|
|
93
|
+
const successes = results.filter(r => r.status === 'fulfilled' && r.value?.success);
|
|
94
|
+
|
|
95
|
+
if (failures.length > 0) {
|
|
96
|
+
logger.error(`\n${failures.length} blueprint(s) failed to push:`);
|
|
97
|
+
failures.forEach(f => {
|
|
98
|
+
logger.error(` • ${f.reason.message}`);
|
|
99
|
+
});
|
|
100
|
+
throw new Error(`Failed to push ${failures.length} blueprint(s)`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.info(`Pushed ${blueprintFiles.length} blueprint(s)`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pull blueprints from remote to local directory
|
|
108
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
109
|
+
* @param {string} targetPath - Path to save blueprints
|
|
110
|
+
*/
|
|
111
|
+
export async function pullBlueprints(sdk, targetPath) {
|
|
112
|
+
// Create directory if it doesn't exist
|
|
113
|
+
if (!fs.existsSync(targetPath)) {
|
|
114
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
115
|
+
logger.info(`Created directory: ${targetPath}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const blueprints = await sdk.manageBlueprints.getList();
|
|
119
|
+
|
|
120
|
+
if (blueprints.length === 0) {
|
|
121
|
+
logger.warning('No blueprints found to pull');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
logger.info(`Found ${blueprints.length} blueprint(s) to pull`);
|
|
126
|
+
|
|
127
|
+
await Promise.all(blueprints.map(async (blueprint) => {
|
|
128
|
+
const fileName = `${blueprint.identifier}.blueprint.json`;
|
|
129
|
+
const filePath = join(targetPath, fileName);
|
|
130
|
+
|
|
131
|
+
// Fetch full blueprint details
|
|
132
|
+
const fullBlueprint = await sdk.manageBlueprints.getBlueprint(blueprint.identifier);
|
|
133
|
+
|
|
134
|
+
function removeIdFromObject(obj) {
|
|
135
|
+
const { _id, ...rest } = obj;
|
|
136
|
+
return rest;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const relevantFields = {
|
|
140
|
+
identifier: fullBlueprint.identifier,
|
|
141
|
+
name: fullBlueprint.name,
|
|
142
|
+
description: fullBlueprint.description,
|
|
143
|
+
properties: fullBlueprint.properties,
|
|
144
|
+
relations: fullBlueprint.relations,
|
|
145
|
+
dispatchers: fullBlueprint.dispatchers,
|
|
146
|
+
permissions: (fullBlueprint.permissions || []).map(removeIdFromObject),
|
|
147
|
+
permissionScope: fullBlueprint.permissionScope,
|
|
148
|
+
entityIdentifierMechanism: fullBlueprint.entityIdentifierMechanism,
|
|
149
|
+
limitations: (fullBlueprint.limitations || []).map(removeIdFromObject),
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fs.writeFileSync(filePath, JSON.stringify(relevantFields, null, 2), 'utf-8');
|
|
153
|
+
logger.step(`Pulled: ${blueprint.identifier}`);
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
logger.info(`Pulled ${blueprints.length} blueprint(s)`);
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { logger } from './logger.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push components from local directory to remote
|
|
7
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
8
|
+
* @param {string} path - Path to components directory
|
|
9
|
+
*/
|
|
10
|
+
export async function pushComponents(sdk, path) {
|
|
11
|
+
const files = fs.readdirSync(path);
|
|
12
|
+
const vueFiles = files.filter(f => f.endsWith('.vue'));
|
|
13
|
+
|
|
14
|
+
if (vueFiles.length === 0) {
|
|
15
|
+
logger.warning(`No .vue files found in ${path}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logger.info(`Found ${vueFiles.length} component(s) to push`);
|
|
20
|
+
let componentsJson = {};
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const jsonPath = join(path, 'components.json');
|
|
24
|
+
if (fs.existsSync(jsonPath)) {
|
|
25
|
+
componentsJson = JSON.parse(fs.readFileSync(jsonPath));
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.debug('No components.json found or invalid format');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const existingComponents = await sdk.components.getList();
|
|
32
|
+
|
|
33
|
+
await Promise.all(files.map(async (file) => {
|
|
34
|
+
if (file.endsWith('.vue')) {
|
|
35
|
+
const componentName = file.replace('.vue', '');
|
|
36
|
+
const info = componentsJson[componentName] || {};
|
|
37
|
+
const content = fs.readFileSync(join(path, file), 'utf-8');
|
|
38
|
+
|
|
39
|
+
logger.step(`Pushing component: ${componentName}`);
|
|
40
|
+
|
|
41
|
+
const existingComponent = existingComponents.find(
|
|
42
|
+
component => component.identifier === componentName
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (existingComponent) {
|
|
46
|
+
await sdk.components.update(existingComponent._id, {
|
|
47
|
+
identifier: info.identifier || existingComponent.identifier || componentName,
|
|
48
|
+
componentName: componentName,
|
|
49
|
+
content,
|
|
50
|
+
description: info.description || existingComponent.description || 'Component description'
|
|
51
|
+
});
|
|
52
|
+
logger.success(`Updated: ${componentName}`);
|
|
53
|
+
} else {
|
|
54
|
+
await sdk.components.create({
|
|
55
|
+
identifier: info.identifier || componentName,
|
|
56
|
+
componentName: componentName,
|
|
57
|
+
content,
|
|
58
|
+
description: info.description || 'Component description'
|
|
59
|
+
});
|
|
60
|
+
logger.success(`Created: ${componentName}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
logger.info(`Pushed ${vueFiles.length} component(s)`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Pull components from remote to local directory
|
|
70
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
71
|
+
* @param {string} targetPath - Path to save components
|
|
72
|
+
*/
|
|
73
|
+
export async function pullComponents(sdk, targetPath) {
|
|
74
|
+
// Create directory if it doesn't exist
|
|
75
|
+
if (!fs.existsSync(targetPath)) {
|
|
76
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
77
|
+
logger.info(`Created directory: ${targetPath}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const components = await sdk.components.getList();
|
|
81
|
+
|
|
82
|
+
if (components.length === 0) {
|
|
83
|
+
logger.warning('No components found to pull');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
logger.info(`Found ${components.length} component(s) to pull`);
|
|
88
|
+
|
|
89
|
+
const componentsInformation = await Promise.all(components.map(async (component) => {
|
|
90
|
+
const fileName = `${component.componentName}.vue`;
|
|
91
|
+
const filePath = join(targetPath, fileName);
|
|
92
|
+
|
|
93
|
+
const { content, description } = await sdk.components.getComponent(component._id);
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
96
|
+
logger.step(`Pulled: ${component.identifier}`);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
_id: component._id,
|
|
100
|
+
componentName: component.componentName,
|
|
101
|
+
identifier: component.identifier,
|
|
102
|
+
description,
|
|
103
|
+
};
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(
|
|
107
|
+
join(targetPath, 'components.json'),
|
|
108
|
+
JSON.stringify(
|
|
109
|
+
componentsInformation.reduce((obj, current) => {
|
|
110
|
+
obj[current.componentName] = current;
|
|
111
|
+
return obj;
|
|
112
|
+
}, {}),
|
|
113
|
+
null,
|
|
114
|
+
2
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
logger.info(`Saved components.json with metadata`);
|
|
119
|
+
logger.info(`Pulled ${components.length} component(s)`);
|
|
120
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { logger } from './logger.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Push configurations from local directory to remote
|
|
7
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
8
|
+
* @param {string} path - Path to configurations directory
|
|
9
|
+
*/
|
|
10
|
+
export async function pushConfigurations(sdk, path) {
|
|
11
|
+
const files = fs.readdirSync(path);
|
|
12
|
+
const configFiles = files.filter(f => f.endsWith('.config.json'));
|
|
13
|
+
|
|
14
|
+
if (configFiles.length === 0) {
|
|
15
|
+
logger.warning(`No configuration files (*.config.json) found in ${path}`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
logger.info(`Found ${configFiles.length} configuration(s) to push`);
|
|
20
|
+
const existingConfigurations = await sdk.manageConfigurations.getList();
|
|
21
|
+
|
|
22
|
+
const results = await Promise.allSettled(configFiles.map(async (file) => {
|
|
23
|
+
if (file.endsWith('.config.json')) {
|
|
24
|
+
let configData;
|
|
25
|
+
try {
|
|
26
|
+
configData = JSON.parse(fs.readFileSync(join(path, file), 'utf-8'));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
logger.error(`Failed to parse ${file}`, error);
|
|
29
|
+
throw new Error(`Parse error in ${file}: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = configData.key;
|
|
33
|
+
|
|
34
|
+
if (!key) {
|
|
35
|
+
logger.warning(`Skipping ${file}: missing key field`);
|
|
36
|
+
return { skipped: true, file };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logger.step(`Pushing configuration: ${key}`);
|
|
40
|
+
|
|
41
|
+
const existingConfig = existingConfigurations.find(
|
|
42
|
+
config => config.key === key
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
if (existingConfig) {
|
|
47
|
+
await sdk.manageConfigurations.update(key, configData);
|
|
48
|
+
logger.success(`Updated: ${key}`);
|
|
49
|
+
} else {
|
|
50
|
+
await sdk.manageConfigurations.create(configData);
|
|
51
|
+
logger.success(`Created: ${key}`);
|
|
52
|
+
}
|
|
53
|
+
return { success: true, key };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Extract detailed error information
|
|
56
|
+
let errorMessage = error.message || 'Unknown error';
|
|
57
|
+
let errorDetails = null;
|
|
58
|
+
|
|
59
|
+
// The SDK throws the response body as the error
|
|
60
|
+
if (typeof error === 'object' && error !== null) {
|
|
61
|
+
if (error.message) {
|
|
62
|
+
errorMessage = error.message;
|
|
63
|
+
}
|
|
64
|
+
if (error.error) {
|
|
65
|
+
errorDetails = error.error;
|
|
66
|
+
}
|
|
67
|
+
if (error.errors) {
|
|
68
|
+
errorDetails = error.errors;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.error(`Failed to push ${key}: ${errorMessage}`);
|
|
73
|
+
|
|
74
|
+
if (errorDetails) {
|
|
75
|
+
if (typeof errorDetails === 'string') {
|
|
76
|
+
logger.error(` Details: ${errorDetails}`);
|
|
77
|
+
} else {
|
|
78
|
+
logger.error(` Details: ${JSON.stringify(errorDetails, null, 2)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (process.env.VERBOSE && error.stack) {
|
|
83
|
+
logger.debug(`Stack: ${error.stack}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error(`Failed to push ${key}: ${errorMessage}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
|
|
91
|
+
// Check for failures
|
|
92
|
+
const failures = results.filter(r => r.status === 'rejected');
|
|
93
|
+
const successes = results.filter(r => r.status === 'fulfilled' && r.value?.success);
|
|
94
|
+
|
|
95
|
+
if (failures.length > 0) {
|
|
96
|
+
logger.error(`\n${failures.length} configuration(s) failed to push:`);
|
|
97
|
+
failures.forEach(f => {
|
|
98
|
+
logger.error(` • ${f.reason.message}`);
|
|
99
|
+
});
|
|
100
|
+
throw new Error(`Failed to push ${failures.length} configuration(s)`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
logger.info(`Pushed ${configFiles.length} configuration(s)`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Pull configurations from remote to local directory
|
|
108
|
+
* @param {Object} sdk - Initialized SDK instance
|
|
109
|
+
* @param {string} targetPath - Path to save configurations
|
|
110
|
+
*/
|
|
111
|
+
export async function pullConfigurations(sdk, targetPath) {
|
|
112
|
+
// Create directory if it doesn't exist
|
|
113
|
+
if (!fs.existsSync(targetPath)) {
|
|
114
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
115
|
+
logger.info(`Created directory: ${targetPath}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const configurations = await sdk.manageConfigurations.getList();
|
|
119
|
+
|
|
120
|
+
if (configurations.length === 0) {
|
|
121
|
+
logger.warning('No configurations found to pull');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
logger.info(`Found ${configurations.length} configuration(s) to pull`);
|
|
126
|
+
|
|
127
|
+
await Promise.all(configurations.map(async (config) => {
|
|
128
|
+
const fileName = `${config.key}.config.json`;
|
|
129
|
+
const filePath = join(targetPath, fileName);
|
|
130
|
+
|
|
131
|
+
// Fetch full configuration details
|
|
132
|
+
const fullConfig = await sdk.manageConfigurations.getConfiguration(config.key);
|
|
133
|
+
|
|
134
|
+
// Remove fields that shouldn't be in the file
|
|
135
|
+
const { _id, tenant, created, updated, ...relevantFields } = fullConfig;
|
|
136
|
+
|
|
137
|
+
fs.writeFileSync(filePath, JSON.stringify(relevantFields, null, 2), 'utf-8');
|
|
138
|
+
logger.step(`Pulled: ${config.key}`);
|
|
139
|
+
}));
|
|
140
|
+
|
|
141
|
+
logger.info(`Pulled ${configurations.length} configuration(s)`);
|
|
142
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger utility for CLI with colored output and consistent formatting
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const colors = {
|
|
6
|
+
reset: '\x1b[0m',
|
|
7
|
+
bright: '\x1b[1m',
|
|
8
|
+
dim: '\x1b[2m',
|
|
9
|
+
red: '\x1b[31m',
|
|
10
|
+
green: '\x1b[32m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
12
|
+
blue: '\x1b[34m',
|
|
13
|
+
cyan: '\x1b[36m',
|
|
14
|
+
gray: '\x1b[90m',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const logger = {
|
|
18
|
+
success(message) {
|
|
19
|
+
console.log(`${colors.green}✓${colors.reset} ${message}`);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
error(message, error = null) {
|
|
23
|
+
console.error(`${colors.red}✗ Error:${colors.reset} ${message}`);
|
|
24
|
+
if (error && process.env.VERBOSE) {
|
|
25
|
+
console.error(`${colors.gray}${error.stack || error}${colors.reset}`);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
warning(message) {
|
|
30
|
+
console.warn(`${colors.yellow}⚠ Warning:${colors.reset} ${message}`);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
info(message) {
|
|
34
|
+
console.log(`${colors.blue}ℹ${colors.reset} ${message}`);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
debug(message) {
|
|
38
|
+
if (process.env.VERBOSE) {
|
|
39
|
+
console.log(`${colors.gray}[DEBUG]${colors.reset} ${message}`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
step(message) {
|
|
44
|
+
console.log(`${colors.cyan}→${colors.reset} ${message}`);
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
section(title) {
|
|
48
|
+
console.log(`\n${colors.bright}${title}${colors.reset}`);
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Format and display a connection error with helpful context
|
|
53
|
+
*/
|
|
54
|
+
connectionError(url, error) {
|
|
55
|
+
console.error(`\n${colors.red}✗ Connection Failed${colors.reset}`);
|
|
56
|
+
console.error(`${colors.dim}Unable to connect to: ${colors.reset}${url}`);
|
|
57
|
+
|
|
58
|
+
if (error.code === 'UND_ERR_CONNECT_TIMEOUT') {
|
|
59
|
+
console.error(`${colors.yellow}Reason:${colors.reset} Connection timeout (10s)`);
|
|
60
|
+
console.error(`\n${colors.cyan}Suggestions:${colors.reset}`);
|
|
61
|
+
console.error(` • Check if the server is running and accessible`);
|
|
62
|
+
console.error(` • Verify the QELOS_URL is correct: ${url}`);
|
|
63
|
+
console.error(` • Check your network connection`);
|
|
64
|
+
console.error(` • Ensure there are no firewall rules blocking the connection`);
|
|
65
|
+
} else if (error.code === 'ENOTFOUND') {
|
|
66
|
+
console.error(`${colors.yellow}Reason:${colors.reset} Domain not found`);
|
|
67
|
+
console.error(`\n${colors.cyan}Suggestions:${colors.reset}`);
|
|
68
|
+
console.error(` • Verify the QELOS_URL is correct: ${url}`);
|
|
69
|
+
console.error(` • Check if the domain exists and is accessible`);
|
|
70
|
+
} else if (error.code === 'ECONNREFUSED') {
|
|
71
|
+
console.error(`${colors.yellow}Reason:${colors.reset} Connection refused`);
|
|
72
|
+
console.error(`\n${colors.cyan}Suggestions:${colors.reset}`);
|
|
73
|
+
console.error(` • Check if the server is running on ${url}`);
|
|
74
|
+
console.error(` • Verify the port number is correct`);
|
|
75
|
+
} else {
|
|
76
|
+
console.error(`${colors.yellow}Reason:${colors.reset} ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (process.env.VERBOSE) {
|
|
80
|
+
console.error(`\n${colors.gray}Full error:${colors.reset}`);
|
|
81
|
+
console.error(`${colors.gray}${error.stack}${colors.reset}`);
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`\n${colors.dim}Run with VERBOSE=true for more details${colors.reset}`);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Format and display an authentication error
|
|
89
|
+
*/
|
|
90
|
+
authError(username, url) {
|
|
91
|
+
console.error(`\n${colors.red}✗ Authentication Failed${colors.reset}`);
|
|
92
|
+
console.error(`${colors.dim}Unable to authenticate user: ${colors.reset}${username}`);
|
|
93
|
+
console.error(`${colors.dim}Server: ${colors.reset}${url}`);
|
|
94
|
+
console.error(`\n${colors.cyan}Suggestions:${colors.reset}`);
|
|
95
|
+
console.error(` • Verify QELOS_USERNAME is correct: ${username}`);
|
|
96
|
+
console.error(` • Check if QELOS_PASSWORD is correct`);
|
|
97
|
+
console.error(` • Ensure the user account exists and is active`);
|
|
98
|
+
console.error(` • Verify you have the necessary permissions`);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Display environment configuration
|
|
103
|
+
*/
|
|
104
|
+
showConfig(config) {
|
|
105
|
+
console.log(`\n${colors.bright}Configuration:${colors.reset}`);
|
|
106
|
+
Object.entries(config).forEach(([key, value]) => {
|
|
107
|
+
const displayValue = key.toLowerCase().includes('password') ? '***' : value;
|
|
108
|
+
console.log(` ${colors.cyan}${key}:${colors.reset} ${displayValue}`);
|
|
109
|
+
});
|
|
110
|
+
console.log('');
|
|
111
|
+
}
|
|
112
|
+
};
|
package/services/sdk.mjs
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createJiti } from 'jiti';
|
|
2
|
+
import { logger } from './logger.mjs';
|
|
3
|
+
|
|
4
|
+
const jiti = createJiti(import.meta.url);
|
|
5
|
+
|
|
6
|
+
export async function initializeSdk() {
|
|
7
|
+
const appUrl = process.env.QELOS_URL || "http://localhost:3000";
|
|
8
|
+
const username = process.env.QELOS_USERNAME || 'test@test.com';
|
|
9
|
+
const password = process.env.QELOS_PASSWORD || 'admin';
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
logger.debug('Initializing Qelos SDK...');
|
|
13
|
+
|
|
14
|
+
if (process.env.VERBOSE) {
|
|
15
|
+
logger.showConfig({
|
|
16
|
+
'QELOS_URL': appUrl,
|
|
17
|
+
'QELOS_USERNAME': username,
|
|
18
|
+
'QELOS_PASSWORD': password
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const QelosAdministratorSDK = await jiti('@qelos/sdk/src/administrator/index.ts');
|
|
23
|
+
|
|
24
|
+
const sdk = new QelosAdministratorSDK.default({
|
|
25
|
+
appUrl,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
logger.debug(`Authenticating as ${username}...`);
|
|
29
|
+
|
|
30
|
+
await sdk.authentication.oAuthSignin({
|
|
31
|
+
username,
|
|
32
|
+
password,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
logger.debug('Authentication successful');
|
|
36
|
+
return sdk;
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// Handle connection errors
|
|
40
|
+
if (error.cause?.code === 'UND_ERR_CONNECT_TIMEOUT' ||
|
|
41
|
+
error.cause?.code === 'ENOTFOUND' ||
|
|
42
|
+
error.cause?.code === 'ECONNREFUSED' ||
|
|
43
|
+
error.message?.includes('fetch failed')) {
|
|
44
|
+
logger.connectionError(appUrl, error.cause || error);
|
|
45
|
+
}
|
|
46
|
+
// Handle authentication errors
|
|
47
|
+
else if (error.response?.status === 401 || error.message?.includes('authentication')) {
|
|
48
|
+
logger.authError(username, appUrl);
|
|
49
|
+
}
|
|
50
|
+
// Handle other errors
|
|
51
|
+
else {
|
|
52
|
+
logger.error('Failed to initialize SDK', error);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|