@qelos/plugins-cli 0.0.11 → 0.0.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/plugins-cli",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "CLI to manage QELOS plugins",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { logger } from './logger.mjs';
4
+ import { appUrl } from './sdk.mjs';
4
5
 
5
6
  /**
6
7
  * Push configurations from local directory to remote
@@ -38,6 +39,25 @@ export async function pushConfigurations(sdk, path) {
38
39
 
39
40
  logger.step(`Pushing configuration: ${key}`);
40
41
 
42
+ // Special handling for app-configuration: ensure QELOS_URL hostname is in websiteUrls
43
+ if (key === 'app-configuration') {
44
+ try {
45
+ const qelosUrl = new URL(appUrl);
46
+ const hostname = qelosUrl.hostname;
47
+
48
+ if (!configData.metadata.websiteUrls) {
49
+ configData.metadata.websiteUrls = [];
50
+ }
51
+
52
+ if (!configData.metadata.websiteUrls.includes(hostname)) {
53
+ configData.metadata.websiteUrls.push(hostname);
54
+ logger.info(`Added ${hostname} to websiteUrls`);
55
+ }
56
+ } catch (error) {
57
+ logger.warning(`Failed to parse QELOS_URL: ${error.message}`);
58
+ }
59
+ }
60
+
41
61
  const existingConfig = existingConfigurations.find(
42
62
  config => config.key === key
43
63
  );
@@ -0,0 +1,129 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { logger } from './logger.mjs';
4
+
5
+ /**
6
+ * Convert a string to kebab-case
7
+ * @param {string} str - String to convert
8
+ * @returns {string} Kebab-cased string
9
+ */
10
+ function toKebabCase(str) {
11
+ return str
12
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
13
+ .replace(/[\s_]+/g, '-')
14
+ .toLowerCase();
15
+ }
16
+
17
+ /**
18
+ * Extract micro-frontend structures to separate HTML files
19
+ * @param {Array} microFrontends - Array of micro-frontend objects
20
+ * @param {string} pluginPath - Base path for the plugin directory
21
+ * @returns {Array} Updated micro-frontends with $ref instead of structure
22
+ */
23
+ export function extractMicroFrontendStructures(microFrontends, pluginPath) {
24
+ if (!microFrontends || microFrontends.length === 0) {
25
+ return microFrontends;
26
+ }
27
+
28
+ const microFrontendsDir = path.join(pluginPath, 'micro-frontends');
29
+
30
+ // Create micro-frontends directory if it doesn't exist
31
+ if (!fs.existsSync(microFrontendsDir)) {
32
+ fs.mkdirSync(microFrontendsDir, { recursive: true });
33
+ }
34
+
35
+ return microFrontends.map((mf) => {
36
+ // Skip if no structure or structure is already a reference
37
+ if (!mf.structure || typeof mf.structure === 'object') {
38
+ return mf;
39
+ }
40
+
41
+ // Generate filename from route name or micro-frontend name
42
+ const fileName = mf.route?.name
43
+ ? toKebabCase(mf.route.name)
44
+ : toKebabCase(mf.name);
45
+
46
+ const htmlFileName = `${fileName}.html`;
47
+ const htmlFilePath = path.join(microFrontendsDir, htmlFileName);
48
+ const relativeRef = `./micro-frontends/${htmlFileName}`;
49
+
50
+ // Write structure to HTML file
51
+ fs.writeFileSync(htmlFilePath, mf.structure, 'utf-8');
52
+ logger.debug(`Extracted structure to: ${relativeRef}`);
53
+
54
+ // Return micro-frontend with $ref instead of structure
55
+ return {
56
+ ...mf,
57
+ structure: { $ref: relativeRef }
58
+ };
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Load content from a $ref reference
64
+ * @param {string} ref - Reference path (relative, absolute, or URL)
65
+ * @param {string} basePath - Base path for resolving relative references
66
+ * @returns {Promise<string>} Loaded content
67
+ */
68
+ async function loadReference(ref, basePath) {
69
+ // Handle HTTP/HTTPS URLs
70
+ if (ref.startsWith('http://') || ref.startsWith('https://')) {
71
+ logger.debug(`Loading structure from URL: ${ref}`);
72
+ try {
73
+ const response = await fetch(ref);
74
+ if (!response.ok) {
75
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
76
+ }
77
+ return await response.text();
78
+ } catch (error) {
79
+ throw new Error(`Failed to load from URL ${ref}: ${error.message}`);
80
+ }
81
+ }
82
+
83
+ // Handle absolute and relative paths
84
+ const filePath = path.isAbsolute(ref)
85
+ ? ref
86
+ : path.resolve(basePath, ref);
87
+
88
+ logger.debug(`Loading structure from file: ${filePath}`);
89
+
90
+ if (!fs.existsSync(filePath)) {
91
+ throw new Error(`Referenced file does not exist: ${filePath}`);
92
+ }
93
+
94
+ return fs.readFileSync(filePath, 'utf-8');
95
+ }
96
+
97
+ /**
98
+ * Resolve micro-frontend structures from $ref references
99
+ * @param {Array} microFrontends - Array of micro-frontend objects
100
+ * @param {string} pluginPath - Base path for resolving relative references
101
+ * @returns {Promise<Array>} Updated micro-frontends with resolved structures
102
+ */
103
+ export async function resolveMicroFrontendStructures(microFrontends, pluginPath) {
104
+ if (!microFrontends || microFrontends.length === 0) {
105
+ return microFrontends;
106
+ }
107
+
108
+ return await Promise.all(microFrontends.map(async (mf) => {
109
+ // Skip if no structure or structure is not a reference object
110
+ if (!mf.structure || typeof mf.structure !== 'object' || !mf.structure.$ref) {
111
+ return mf;
112
+ }
113
+
114
+ const ref = mf.structure.$ref;
115
+
116
+ try {
117
+ const content = await loadReference(ref, pluginPath);
118
+
119
+ // Return micro-frontend with resolved structure
120
+ return {
121
+ ...mf,
122
+ structure: content
123
+ };
124
+ } catch (error) {
125
+ logger.error(`Failed to resolve $ref for ${mf.name}: ${error.message}`);
126
+ throw error;
127
+ }
128
+ }));
129
+ }
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
  import { join } from 'node:path';
3
4
  import { logger } from './logger.mjs';
5
+ import { extractMicroFrontendStructures, resolveMicroFrontendStructures } from './micro-frontends.mjs';
4
6
 
5
7
  /**
6
8
  * Push plugins from local directory to remote
@@ -38,6 +40,19 @@ export async function pushPlugins(sdk, path) {
38
40
 
39
41
  logger.step(`Pushing plugin: ${apiPath}`);
40
42
 
43
+ // Resolve micro-frontend structures from $ref references
44
+ if (pluginData.microFrontends && pluginData.microFrontends.length > 0) {
45
+ try {
46
+ pluginData.microFrontends = await resolveMicroFrontendStructures(
47
+ pluginData.microFrontends,
48
+ path
49
+ );
50
+ } catch (error) {
51
+ logger.error(`Failed to resolve micro-frontend structures for ${apiPath}`, error);
52
+ throw new Error(`Failed to resolve structures for ${apiPath}: ${error.message}`);
53
+ }
54
+ }
55
+
41
56
  const existingPlugin = existingPlugins.find(
42
57
  plugin => plugin.apiPath === apiPath
43
58
  );
@@ -126,6 +141,7 @@ export async function pullPlugins(sdk, targetPath) {
126
141
  await Promise.all(plugins.map(async (plugin) => {
127
142
  const fileName = `${plugin.apiPath}.plugin.json`;
128
143
  const filePath = join(targetPath, fileName);
144
+ const pluginDir = join(targetPath, plugin.apiPath);
129
145
 
130
146
  // Fetch full plugin details
131
147
  const fullPlugin = await sdk.managePlugins.getById(plugin._id);
@@ -135,6 +151,12 @@ export async function pullPlugins(sdk, targetPath) {
135
151
  return rest;
136
152
  }
137
153
 
154
+ // Extract micro-frontend structures to separate files
155
+ const processedMicroFrontends = extractMicroFrontendStructures(
156
+ (fullPlugin.microFrontends || []).map(removeIdFromObject),
157
+ targetPath
158
+ );
159
+
138
160
  const relevantFields = {
139
161
  name: fullPlugin.name,
140
162
  description: fullPlugin.description,
@@ -145,7 +167,7 @@ export async function pullPlugins(sdk, targetPath) {
145
167
  authAcquire: fullPlugin.authAcquire,
146
168
  proxyUrl: fullPlugin.proxyUrl,
147
169
  subscribedEvents: (fullPlugin.subscribedEvents || []).map(removeIdFromObject),
148
- microFrontends: (fullPlugin.microFrontends || []).map(removeIdFromObject),
170
+ microFrontends: processedMicroFrontends,
149
171
  injectables: (fullPlugin.injectables || []).map(removeIdFromObject),
150
172
  navBarGroups: (fullPlugin.navBarGroups || []).map(removeIdFromObject),
151
173
  cruds: (fullPlugin.cruds || []).map(removeIdFromObject),
package/services/sdk.mjs CHANGED
@@ -1,25 +1,27 @@
1
- import { createJiti } from 'jiti';
2
- import { logger } from './logger.mjs';
1
+ import { createJiti } from "jiti";
2
+ import { logger } from "./logger.mjs";
3
3
 
4
4
  const jiti = createJiti(import.meta.url);
5
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';
6
+ export const appUrl = process.env.QELOS_URL || "http://localhost:3000";
10
7
 
8
+ export async function initializeSdk() {
9
+ const username = process.env.QELOS_USERNAME || "test@test.com";
10
+ const password = process.env.QELOS_PASSWORD || "admin";
11
11
  try {
12
- logger.debug('Initializing Qelos SDK...');
13
-
12
+ logger.debug("Initializing Qelos SDK...");
13
+
14
14
  if (process.env.VERBOSE) {
15
15
  logger.showConfig({
16
- 'QELOS_URL': appUrl,
17
- 'QELOS_USERNAME': username,
18
- 'QELOS_PASSWORD': password
16
+ QELOS_URL: appUrl,
17
+ QELOS_USERNAME: username,
18
+ QELOS_PASSWORD: password,
19
19
  });
20
20
  }
21
21
 
22
- const QelosAdministratorSDK = await jiti('@qelos/sdk/src/administrator/index.ts');
22
+ const QelosAdministratorSDK = await jiti(
23
+ "@qelos/sdk/src/administrator/index.ts"
24
+ );
23
25
 
24
26
  const sdk = new QelosAdministratorSDK.default({
25
27
  appUrl,
@@ -32,24 +34,28 @@ export async function initializeSdk() {
32
34
  password,
33
35
  });
34
36
 
35
- logger.debug('Authentication successful');
37
+ logger.debug("Authentication successful");
36
38
  return sdk;
37
-
38
39
  } catch (error) {
39
40
  // 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')) {
41
+ if (
42
+ error.cause?.code === "UND_ERR_CONNECT_TIMEOUT" ||
43
+ error.cause?.code === "ENOTFOUND" ||
44
+ error.cause?.code === "ECONNREFUSED" ||
45
+ error.message?.includes("fetch failed")
46
+ ) {
44
47
  logger.connectionError(appUrl, error.cause || error);
45
- }
48
+ }
46
49
  // Handle authentication errors
47
- else if (error.response?.status === 401 || error.message?.includes('authentication')) {
50
+ else if (
51
+ error.response?.status === 401 ||
52
+ error.message?.includes("authentication")
53
+ ) {
48
54
  logger.authError(username, appUrl);
49
55
  }
50
56
  // Handle other errors
51
57
  else {
52
- logger.error('Failed to initialize SDK', error);
58
+ logger.error("Failed to initialize SDK", error);
53
59
  }
54
60
 
55
61
  process.exit(1);