@qelos/plugins-cli 0.0.9 → 0.0.11

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 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
- > npm install -g @qelos/plugins-cli
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
- Basic usage to create new plugin
13
- > qplay create my-app
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, plugins, integrations, blueprints, and more.',
5
+ .command('pull [type] [path]', 'pull from qelos app. Ability to pull components, blueprints, configurations, plugins, blocks, and more.',
6
6
  (yargs) => {
7
7
  return yargs
8
8
  .positional('type', {
9
- describe: 'Type of the resource to pull. Can be components, plugins, integrations, blueprints, or more.',
9
+ describe: 'Type of the resource to pull. Can be components, blueprints, configurations, plugins, blocks, or all.',
10
10
  type: 'string',
11
+ choices: ['components', 'blueprints', 'configs', 'plugins', 'blocks', 'all', '*'],
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, plugins, integrations, blueprints, and more.',
5
+ .command('push [type] [path]', 'push to qelos app. Ability to push components, blueprints, configurations, plugins, blocks, and more.',
6
6
  (yargs) => {
7
7
  return yargs
8
8
  .positional('type', {
9
- describe: 'Type of the plugin to push. Can be components, plugins, integrations, blueprints, or more.',
9
+ describe: 'Type of the resource to push. Can be components, blueprints, configurations, plugins, blocks, or all.',
10
10
  type: 'string',
11
+ choices: ['components', 'blueprints', 'configs', 'plugins', 'blocks', 'all', '*'],
11
12
  required: true
12
13
  })
13
14
  .positional('path', {
14
- describe: 'Path to the context file to push.',
15
+ describe: 'Path to the resource to push.',
15
16
  type: 'string',
16
17
  required: true
17
18
  })
@@ -1,43 +1,81 @@
1
- import fs from 'node:fs'
2
- import path from 'node:path'
3
- import {createJiti} from 'jiti';
4
-
5
- const jiti = createJiti(import.meta.url);
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 { pullPlugins } from '../services/plugins.mjs';
6
+ import { pullBlocks } from '../services/blocks.mjs';
7
+ import { logger } from '../services/logger.mjs';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
6
10
 
7
11
  export default async function pullController({ type, path: targetPath }) {
12
+ try {
13
+ // Validate parent directory exists
14
+ const parentDir = path.dirname(targetPath);
15
+ if (!fs.existsSync(parentDir)) {
16
+ logger.error(`Parent directory does not exist: ${parentDir}`);
17
+ logger.info('Please ensure the parent directory exists');
18
+ process.exit(1);
19
+ }
8
20
 
9
- const QelosAdministratorSDK = await jiti('@qelos/sdk/src/administrator/index.ts');
21
+ // Warn if target path exists and is not a directory
22
+ if (fs.existsSync(targetPath) && !fs.statSync(targetPath).isDirectory()) {
23
+ logger.error(`Path exists but is not a directory: ${targetPath}`);
24
+ logger.info('Please provide a directory path, not a file');
25
+ process.exit(1);
26
+ }
10
27
 
11
- const sdk = new QelosAdministratorSDK.default({
12
- appUrl: process.env.QELOS_URL || "http://localhost:3000",
13
- })
28
+ const sdk = await initializeSdk();
14
29
 
15
- await sdk.authentication.oAuthSignin({
16
- username: process.env.QELOS_USERNAME || 'test@test.com',
17
- password: process.env.QELOS_PASSWORD || 'admin',
18
- })
30
+ // Handle "all" or "*" type
31
+ if (type === 'all' || type === '*') {
32
+ logger.section(`Pulling all resources to ${targetPath}`);
33
+
34
+ const types = [
35
+ { name: 'components', fn: pullComponents },
36
+ { name: 'blueprints', fn: pullBlueprints },
37
+ { name: 'configs', fn: pullConfigurations },
38
+ { name: 'plugins', fn: pullPlugins },
39
+ { name: 'blocks', fn: pullBlocks }
40
+ ];
19
41
 
20
- // if type === 'components' - fetch all components and save them as vue files
21
- if (type === 'components') {
22
- // Create directory if it doesn't exist
23
- if (!fs.existsSync(targetPath)) {
24
- fs.mkdirSync(targetPath, { recursive: true })
25
- console.log('Created directory:', targetPath)
42
+ for (const { name, fn } of types) {
43
+ const typePath = path.join(targetPath, name);
44
+ logger.section(`Pulling ${name} to ${typePath}`);
45
+ try {
46
+ await fn(sdk, typePath);
47
+ logger.success(`Successfully pulled ${name}`);
48
+ } catch (error) {
49
+ logger.error(`Failed to pull ${name}`, error);
50
+ }
51
+ }
52
+
53
+ logger.success(`Successfully pulled all resources to ${targetPath}`);
54
+ return;
26
55
  }
27
56
 
28
- const components = await sdk.components.getList()
29
- console.log(`Found ${components.length} components to pull`)
57
+ logger.section(`Pulling ${type} to ${targetPath}`);
30
58
 
31
- await Promise.all(components.map(async (component) => {
32
- const fileName = `${component.identifier}.vue`
33
- const filePath = path.join(targetPath, fileName)
59
+ if (type === 'components') {
60
+ await pullComponents(sdk, targetPath);
61
+ } else if (type === 'blueprints') {
62
+ await pullBlueprints(sdk, targetPath);
63
+ } else if (type === 'plugins') {
64
+ await pullPlugins(sdk, targetPath);
65
+ } else if (type === 'blocks') {
66
+ await pullBlocks(sdk, targetPath);
67
+ } else if (type === 'config' || type === 'configs' || type === 'configuration') {
68
+ await pullConfigurations(sdk, targetPath);
69
+ } else {
70
+ logger.error(`Unknown type: ${type}`);
71
+ logger.info('Supported types: components, blueprints, plugins, blocks, config, configs, configuration, all');
72
+ process.exit(1);
73
+ }
34
74
 
35
- const {content} = await sdk.components.getComponent(component._id);
36
-
37
- fs.writeFileSync(filePath, content, 'utf-8')
38
- console.log('Pulled component:', component.identifier)
39
- }))
75
+ logger.success(`Successfully pulled ${type} to ${targetPath}`);
40
76
 
41
- console.log(`All ${components.length} components pulled to ${targetPath}`)
77
+ } catch (error) {
78
+ logger.error(`Failed to pull ${type}`, error);
79
+ process.exit(1);
42
80
  }
43
81
  }
@@ -1,47 +1,98 @@
1
- import fs from 'node:fs'
2
- import {createJiti} from 'jiti';
3
-
4
- const jiti = createJiti(import.meta.url);
5
-
6
- export default async function pushController({ type, path }) {
7
-
8
- const QelosAdministratorSDK = await jiti('@qelos/sdk/src/administrator/index.ts');
9
-
10
- const sdk = new QelosAdministratorSDK.default({
11
- appUrl: process.env.QELOS_URL || "http://localhost:3000",
12
- })
13
-
14
- await sdk.authentication.oAuthSignin({
15
- username: process.env.QELOS_USERNAME || 'test@test.com',
16
- password: process.env.QELOS_PASSWORD || 'admin',
17
- })
18
-
19
- // if type === 'components' - load all vue files from path and push them using sdk
20
- if (type === 'components') {
21
- const files = fs.readdirSync(path)
22
- const existingComponents = await sdk.components.getList()
23
- await Promise.all(files.map(async (file) => {
24
- if (file.endsWith('.vue')) {
25
- const content = fs.readFileSync(path + '/' + file, 'utf-8')
26
- console.log('Pushing component:', file.replace('.vue', ''))
27
- const existingComponent = existingComponents.find(component => component.identifier === file.replace('.vue', ''))
28
- if (existingComponent) {
29
- await sdk.components.update(existingComponent._id, {
30
- content,
31
- description: 'Component description'
32
- })
33
- console.log('Component updated:', file.replace('.vue', ''))
34
- } else {
35
- await sdk.components.create({
36
- identifier: file.replace('.vue', ''),
37
- componentName: file.replace('.vue', ''),
38
- content,
39
- description: 'Component description'
40
- })
41
- console.log('Component pushed:', file.replace('.vue', ''))
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 { pushPlugins } from '../services/plugins.mjs';
6
+ import { pushBlocks } from '../services/blocks.mjs';
7
+ import { logger } from '../services/logger.mjs';
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ export default async function pushController({ type, path: sourcePath }) {
12
+ try {
13
+ // Validate path exists
14
+ if (!fs.existsSync(sourcePath)) {
15
+ logger.error(`Path does not exist: ${sourcePath}`);
16
+ logger.info('Please provide a valid directory path');
17
+ process.exit(1);
18
+ }
19
+
20
+ // Validate path is a directory
21
+ if (!fs.statSync(sourcePath).isDirectory()) {
22
+ logger.error(`Path is not a directory: ${sourcePath}`);
23
+ logger.info('Please provide a directory path, not a file');
24
+ process.exit(1);
25
+ }
26
+
27
+ const sdk = await initializeSdk();
28
+
29
+ // Handle "all" or "*" type
30
+ if (type === 'all' || type === '*') {
31
+ logger.section(`Pushing all resources from ${sourcePath}`);
32
+
33
+ const types = [
34
+ { name: 'components', fn: pushComponents },
35
+ { name: 'blueprints', fn: pushBlueprints },
36
+ { name: 'configs', fn: pushConfigurations },
37
+ { name: 'plugins', fn: pushPlugins },
38
+ { name: 'blocks', fn: pushBlocks }
39
+ ];
40
+
41
+ for (const { name, fn } of types) {
42
+ const typePath = path.join(sourcePath, name);
43
+
44
+ // Skip if directory doesn't exist
45
+ if (!fs.existsSync(typePath)) {
46
+ logger.info(`Skipping ${name} (directory not found: ${typePath})`);
47
+ continue;
48
+ }
49
+
50
+ logger.section(`Pushing ${name} from ${typePath}`);
51
+ try {
52
+ await fn(sdk, typePath);
53
+ logger.success(`Successfully pushed ${name}`);
54
+ } catch (error) {
55
+ logger.error(`Failed to push ${name}`, error);
42
56
  }
43
57
  }
44
- }))
45
- console.log('All components pushed')
58
+
59
+ logger.success(`Successfully pushed all resources from ${sourcePath}`);
60
+ return;
61
+ }
62
+
63
+ logger.section(`Pushing ${type} from ${sourcePath}`);
64
+
65
+ if (type === 'components') {
66
+ await pushComponents(sdk, sourcePath);
67
+ } else if (type === 'blueprints') {
68
+ await pushBlueprints(sdk, sourcePath);
69
+ } else if (type === 'plugins') {
70
+ await pushPlugins(sdk, sourcePath);
71
+ } else if (type === 'blocks') {
72
+ await pushBlocks(sdk, sourcePath);
73
+ } else if (type === 'config' || type === 'configs' || type === 'configuration') {
74
+ await pushConfigurations(sdk, sourcePath);
75
+ } else {
76
+ logger.error(`Unknown type: ${type}`);
77
+ logger.info('Supported types: components, blueprints, plugins, blocks, config, configs, configuration, all');
78
+ process.exit(1);
79
+ }
80
+
81
+ logger.success(`Successfully pushed ${type}`);
82
+
83
+ } catch (error) {
84
+ // Don't log the error again if it's already been logged by the service
85
+ if (!error.message?.includes('Failed to push')) {
86
+ logger.error(`Failed to push ${type}`, error);
87
+ }
88
+
89
+ if (process.env.VERBOSE) {
90
+ console.error('\nStack trace:');
91
+ console.error(error.stack);
92
+ } else {
93
+ console.error('\nRun with VERBOSE=true for full stack trace');
94
+ }
95
+
96
+ process.exit(1);
46
97
  }
47
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/plugins-cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "CLI to manage QELOS plugins",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -0,0 +1,166 @@
1
+ import fs from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { logger } from './logger.mjs';
4
+
5
+ /**
6
+ * Convert string to kebab-case
7
+ */
8
+ function toKebabCase(str) {
9
+ return str
10
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
11
+ .replace(/[\s_]+/g, '-')
12
+ .toLowerCase();
13
+ }
14
+
15
+ /**
16
+ * Push blocks from local directory to remote
17
+ * @param {Object} sdk - Initialized SDK instance
18
+ * @param {string} path - Path to blocks directory
19
+ */
20
+ export async function pushBlocks(sdk, path) {
21
+ const files = fs.readdirSync(path);
22
+ const blockFiles = files.filter(f => f.endsWith('.html'));
23
+
24
+ if (blockFiles.length === 0) {
25
+ logger.warning(`No .html files found in ${path}`);
26
+ return;
27
+ }
28
+
29
+ logger.info(`Found ${blockFiles.length} block(s) to push`);
30
+ let blocksJson = {};
31
+
32
+ try {
33
+ const jsonPath = join(path, 'blocks.json');
34
+ if (fs.existsSync(jsonPath)) {
35
+ blocksJson = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
36
+ }
37
+ } catch (error) {
38
+ logger.debug('No blocks.json found or invalid format');
39
+ }
40
+
41
+ const existingBlocks = await sdk.blocks.getList();
42
+ const updatedBlocksJson = { ...blocksJson };
43
+
44
+ await Promise.all(blockFiles.map(async (file) => {
45
+ const fileName = file.replace('.html', '');
46
+ const info = blocksJson[fileName] || {};
47
+ const content = fs.readFileSync(join(path, file), 'utf-8');
48
+
49
+ logger.step(`Pushing block: ${fileName}`);
50
+
51
+ // First check if we have an _id in blocks.json
52
+ let blockId = info._id;
53
+
54
+ // If we have an _id, try to update directly
55
+ if (blockId) {
56
+ try {
57
+ await sdk.blocks.update(blockId, {
58
+ name: info.name || fileName,
59
+ content,
60
+ contentType: info.contentType || 'html',
61
+ description: info.description || 'Block description'
62
+ });
63
+ logger.success(`Updated: ${fileName}`);
64
+ return;
65
+ } catch (error) {
66
+ // If update fails, the block might have been deleted, so we'll create a new one
67
+ logger.debug(`Block ${fileName} not found by _id, will search or create`);
68
+ }
69
+ }
70
+
71
+ // Try to find existing block by name
72
+ const existingBlock = existingBlocks.find(
73
+ block => toKebabCase(block.name) === fileName
74
+ );
75
+
76
+ if (existingBlock) {
77
+ await sdk.blocks.update(existingBlock._id, {
78
+ name: info.name || existingBlock.name,
79
+ content,
80
+ contentType: info.contentType || 'html',
81
+ description: info.description || existingBlock.description || 'Block description'
82
+ });
83
+ updatedBlocksJson[fileName] = {
84
+ ...info,
85
+ _id: existingBlock._id
86
+ };
87
+ logger.success(`Updated: ${fileName}`);
88
+ } else {
89
+ const newBlock = await sdk.blocks.create({
90
+ name: info.name || fileName,
91
+ content,
92
+ contentType: info.contentType || 'html',
93
+ description: info.description || 'Block description'
94
+ });
95
+ updatedBlocksJson[fileName] = {
96
+ ...info,
97
+ _id: newBlock._id,
98
+ name: newBlock.name
99
+ };
100
+ logger.success(`Created: ${fileName}`);
101
+ }
102
+ }));
103
+
104
+ // Update blocks.json with new _ids
105
+ fs.writeFileSync(
106
+ join(path, 'blocks.json'),
107
+ JSON.stringify(updatedBlocksJson, null, 2)
108
+ );
109
+
110
+ logger.info(`Pushed ${blockFiles.length} block(s)`);
111
+ }
112
+
113
+ /**
114
+ * Pull blocks from remote to local directory
115
+ * @param {Object} sdk - Initialized SDK instance
116
+ * @param {string} targetPath - Path to save blocks
117
+ */
118
+ export async function pullBlocks(sdk, targetPath) {
119
+ // Create directory if it doesn't exist
120
+ if (!fs.existsSync(targetPath)) {
121
+ fs.mkdirSync(targetPath, { recursive: true });
122
+ logger.info(`Created directory: ${targetPath}`);
123
+ }
124
+
125
+ const blocks = await sdk.blocks.getList();
126
+
127
+ if (blocks.length === 0) {
128
+ logger.warning('No blocks found to pull');
129
+ return;
130
+ }
131
+
132
+ logger.info(`Found ${blocks.length} block(s) to pull`);
133
+
134
+ const blocksInformation = await Promise.all(blocks.map(async (block) => {
135
+ const fileName = toKebabCase(block.name);
136
+ const filePath = join(targetPath, `${fileName}.html`);
137
+
138
+ const blockDetails = await sdk.blocks.getBlock(block._id);
139
+
140
+ fs.writeFileSync(filePath, blockDetails.content, 'utf-8');
141
+ logger.step(`Pulled: ${block.name}`);
142
+
143
+ return {
144
+ _id: block._id,
145
+ name: block.name,
146
+ description: blockDetails.description,
147
+ contentType: blockDetails.contentType,
148
+ };
149
+ }));
150
+
151
+ fs.writeFileSync(
152
+ join(targetPath, 'blocks.json'),
153
+ JSON.stringify(
154
+ blocksInformation.reduce((obj, current) => {
155
+ const fileName = toKebabCase(current.name);
156
+ obj[fileName] = current;
157
+ return obj;
158
+ }, {}),
159
+ null,
160
+ 2
161
+ )
162
+ );
163
+
164
+ logger.info(`Saved blocks.json with metadata`);
165
+ logger.info(`Pulled ${blocks.length} block(s)`);
166
+ }
@@ -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
+ };
@@ -0,0 +1,159 @@
1
+ import fs from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { logger } from './logger.mjs';
4
+
5
+ /**
6
+ * Push plugins from local directory to remote
7
+ * @param {Object} sdk - Initialized SDK instance
8
+ * @param {string} path - Path to plugins directory
9
+ */
10
+ export async function pushPlugins(sdk, path) {
11
+ const files = fs.readdirSync(path);
12
+ const pluginFiles = files.filter(f => f.endsWith('.plugin.json'));
13
+
14
+ if (pluginFiles.length === 0) {
15
+ logger.warning(`No plugin files (*.plugin.json) found in ${path}`);
16
+ return;
17
+ }
18
+
19
+ logger.info(`Found ${pluginFiles.length} plugin(s) to push`);
20
+ const existingPlugins = await sdk.managePlugins.getList();
21
+
22
+ const results = await Promise.allSettled(pluginFiles.map(async (file) => {
23
+ if (file.endsWith('.plugin.json')) {
24
+ let pluginData;
25
+ try {
26
+ pluginData = 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 apiPath = pluginData.apiPath;
33
+
34
+ if (!apiPath) {
35
+ logger.warning(`Skipping ${file}: missing apiPath field`);
36
+ return { skipped: true, file };
37
+ }
38
+
39
+ logger.step(`Pushing plugin: ${apiPath}`);
40
+
41
+ const existingPlugin = existingPlugins.find(
42
+ plugin => plugin.apiPath === apiPath
43
+ );
44
+
45
+ try {
46
+ if (existingPlugin) {
47
+ await sdk.managePlugins.update(existingPlugin._id, pluginData);
48
+ logger.success(`Updated: ${apiPath}`);
49
+ } else {
50
+ await sdk.managePlugins.create(pluginData);
51
+ logger.success(`Created: ${apiPath}`);
52
+ }
53
+ return { success: true, apiPath };
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 ${apiPath}: ${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 ${apiPath}: ${errorMessage}`);
87
+ }
88
+ }
89
+ }));
90
+
91
+ // Check for failures
92
+ const failures = results.filter(r => r.status === 'rejected');
93
+
94
+ if (failures.length > 0) {
95
+ logger.error(`\n${failures.length} plugin(s) failed to push:`);
96
+ failures.forEach(f => {
97
+ logger.error(` • ${f.reason.message}`);
98
+ });
99
+ throw new Error(`Failed to push ${failures.length} plugin(s)`);
100
+ }
101
+
102
+ logger.info(`Pushed ${pluginFiles.length} plugin(s)`);
103
+ }
104
+
105
+ /**
106
+ * Pull plugins from remote to local directory
107
+ * @param {Object} sdk - Initialized SDK instance
108
+ * @param {string} targetPath - Path to save plugins
109
+ */
110
+ export async function pullPlugins(sdk, targetPath) {
111
+ // Create directory if it doesn't exist
112
+ if (!fs.existsSync(targetPath)) {
113
+ fs.mkdirSync(targetPath, { recursive: true });
114
+ logger.info(`Created directory: ${targetPath}`);
115
+ }
116
+
117
+ const plugins = await sdk.managePlugins.getList();
118
+
119
+ if (plugins.length === 0) {
120
+ logger.warning('No plugins found to pull');
121
+ return;
122
+ }
123
+
124
+ logger.info(`Found ${plugins.length} plugin(s) to pull`);
125
+
126
+ await Promise.all(plugins.map(async (plugin) => {
127
+ const fileName = `${plugin.apiPath}.plugin.json`;
128
+ const filePath = join(targetPath, fileName);
129
+
130
+ // Fetch full plugin details
131
+ const fullPlugin = await sdk.managePlugins.getById(plugin._id);
132
+
133
+ function removeIdFromObject(obj) {
134
+ const { _id, ...rest } = obj;
135
+ return rest;
136
+ }
137
+
138
+ const relevantFields = {
139
+ name: fullPlugin.name,
140
+ description: fullPlugin.description,
141
+ manifestUrl: fullPlugin.manifestUrl,
142
+ callbackUrl: fullPlugin.callbackUrl,
143
+ registerUrl: fullPlugin.registerUrl,
144
+ apiPath: fullPlugin.apiPath,
145
+ authAcquire: fullPlugin.authAcquire,
146
+ proxyUrl: fullPlugin.proxyUrl,
147
+ subscribedEvents: (fullPlugin.subscribedEvents || []).map(removeIdFromObject),
148
+ microFrontends: (fullPlugin.microFrontends || []).map(removeIdFromObject),
149
+ injectables: (fullPlugin.injectables || []).map(removeIdFromObject),
150
+ navBarGroups: (fullPlugin.navBarGroups || []).map(removeIdFromObject),
151
+ cruds: (fullPlugin.cruds || []).map(removeIdFromObject),
152
+ }
153
+
154
+ fs.writeFileSync(filePath, JSON.stringify(relevantFields, null, 2), 'utf-8');
155
+ logger.step(`Pulled: ${plugin.apiPath}`);
156
+ }));
157
+
158
+ logger.info(`Pulled ${plugins.length} plugin(s)`);
159
+ }
@@ -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
+ }