@qelos/plugins-cli 0.0.29 → 0.1.0

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/cli.mjs CHANGED
@@ -11,6 +11,7 @@ import pushCommand from './commands/push.mjs';
11
11
  import pullCommand from './commands/pull.mjs';
12
12
  import generateCommand from './commands/generate.mjs';
13
13
  import blueprintsCommand from './commands/blueprints.mjs';
14
+ import getCommand from './commands/get.mjs';
14
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
15
16
 
16
17
  const program = yargs(hideBin(process.argv));
@@ -28,5 +29,6 @@ pushCommand(program)
28
29
  pullCommand(program)
29
30
  generateCommand(program)
30
31
  blueprintsCommand(program)
32
+ getCommand(program)
31
33
 
32
34
  program.help().argv;
@@ -0,0 +1,26 @@
1
+ import getController from "../controllers/get.mjs";
2
+
3
+ export default function getCommand(program) {
4
+ program
5
+ .command('get [type] [path]', 'get files from git without pushing. Ability to view components, blueprints, configurations, plugins, blocks, committed files, or staged files.',
6
+ (yargs) => {
7
+ return yargs
8
+ .positional('type', {
9
+ describe: 'Type of the resource to get. Can be components, blueprints, configurations, plugins, blocks, integrations, connections, committed, staged, or all.',
10
+ type: 'string',
11
+ choices: ['components', 'blueprints', 'configs', 'plugins', 'blocks', 'integrations', 'connections', 'committed', 'staged', 'all', '*'],
12
+ required: true
13
+ })
14
+ .positional('path', {
15
+ describe: 'Path to search for resources.',
16
+ type: 'string',
17
+ required: true
18
+ })
19
+ .option('json', {
20
+ alias: 'j',
21
+ type: 'boolean',
22
+ description: 'Output in JSON format'
23
+ })
24
+ },
25
+ getController)
26
+ }
@@ -0,0 +1,54 @@
1
+ import { logger } from '../services/logger.mjs';
2
+ import { getGitFiles } from '../services/git-files.mjs';
3
+ import { green, blue, yellow, red } from '../utils/colors.mjs';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ export default async function getController(argv) {
8
+ const { type, path: basePath, json, verbose } = argv;
9
+
10
+ if (verbose) {
11
+ process.env.VERBOSE = 'true';
12
+ }
13
+
14
+ try {
15
+ // For git-based types (committed, staged)
16
+ if (type === 'committed' || type === 'staged') {
17
+ const classified = getGitFiles(type, basePath || '.');
18
+
19
+ if (json) {
20
+ console.log(JSON.stringify(classified, null, 2));
21
+ return;
22
+ }
23
+
24
+ // Display results
25
+ console.log(blue(`\n=== ${type.toUpperCase()} FILES ===\n`));
26
+
27
+ Object.entries(classified).forEach(([fileType, files]) => {
28
+ if (files.length > 0) {
29
+ console.log(yellow(`${fileType.toUpperCase()} (${files.length}):`));
30
+ files.forEach(file => {
31
+ const relativePath = path.relative(basePath || '.', file);
32
+ console.log(` - ${relativePath}`);
33
+ });
34
+ console.log('');
35
+ }
36
+ });
37
+
38
+ return;
39
+ }
40
+
41
+ // For other types, we would need to implement similar logic to push
42
+ // For now, just show what would be searched for
43
+ console.log(blue(`\n=== SEARCH FOR ${type.toUpperCase()} ===\n`));
44
+ console.log(yellow(`Base path: ${basePath || '.'}`));
45
+ console.log(red(`Note: Only 'committed' and 'staged' types are currently supported for preview.`));
46
+
47
+ } catch (error) {
48
+ logger.error(error.message);
49
+ if (verbose) {
50
+ console.error(error.stack);
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/plugins-cli",
3
- "version": "0.0.29",
3
+ "version": "0.1.0",
4
4
  "description": "CLI to manage QELOS plugins",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -73,13 +73,17 @@ function getCommittedFiles() {
73
73
  }
74
74
 
75
75
  /**
76
- * Get the list of staged files
76
+ * Get the list of staged files (excluding deleted files)
77
77
  * @returns {string[]} Array of file paths
78
78
  */
79
79
  function getStagedFiles() {
80
80
  try {
81
- const output = execSync('git diff --cached --name-only', { encoding: 'utf-8' });
82
- return output.trim().split('\n').filter(file => file);
81
+ // Use --name-status to get file status, then filter out deleted files (status D)
82
+ const output = execSync('git diff --cached --name-status', { encoding: 'utf-8' });
83
+ return output.trim()
84
+ .split('\n')
85
+ .filter(line => line && !line.startsWith('D')) // Skip deleted files
86
+ .map(line => line.substring(1).trim()); // Remove status prefix and get file path
83
87
  } catch (error) {
84
88
  logger.error('Failed to get staged files', error);
85
89
  throw new Error('Unable to retrieve staged files from git');
@@ -130,11 +134,95 @@ function findReferencingConfigs(refPath, basePath) {
130
134
  return referencingConfigs;
131
135
  }
132
136
 
137
+ /**
138
+ * Find plugins that reference a specific HTML file through $ref
139
+ * @param {string} htmlRelativePath - Relative path of the HTML file from the plugin directory
140
+ * @param {string} basePath - Base path to search for plugins
141
+ * @returns {Array} Array of plugin.json file paths that reference the HTML file
142
+ */
143
+ function findReferencingPlugins(htmlRelativePath, basePath) {
144
+ const referencingPlugins = [];
145
+ // Ensure basePath is absolute
146
+ const absoluteBasePath = path.resolve(basePath);
147
+ const pluginsDir = path.join(absoluteBasePath, 'plugins');
148
+
149
+ if (!fs.existsSync(pluginsDir)) {
150
+ return referencingPlugins;
151
+ }
152
+
153
+ // Get all plugin directories and .plugin.json files in the plugins directory
154
+ const pluginDirs = fs.readdirSync(pluginsDir, { withFileTypes: true })
155
+ .filter(dirent => dirent.isDirectory())
156
+ .map(dirent => dirent.name);
157
+
158
+ // Also check for .plugin.json files directly in the plugins directory
159
+ const pluginFiles = fs.readdirSync(pluginsDir)
160
+ .filter(file => file.endsWith('.plugin.json'));
161
+
162
+ // Check plugin files in subdirectories
163
+ for (const pluginDir of pluginDirs) {
164
+ const pluginJsonPath = path.join(pluginsDir, pluginDir, 'plugin.json');
165
+
166
+ if (fs.existsSync(pluginJsonPath)) {
167
+ try {
168
+ const content = fs.readFileSync(pluginJsonPath, 'utf-8');
169
+ const plugin = JSON.parse(content);
170
+
171
+ // Check all $ref references in the plugin
172
+ const refs = findAllRefs(plugin);
173
+
174
+ // Check if any ref matches our target HTML file
175
+ // The ref might be like "./micro-frontends/categories.html"
176
+ for (const ref of refs) {
177
+ if (ref.includes(htmlRelativePath) || ref.endsWith(path.basename(htmlRelativePath))) {
178
+ if (!referencingPlugins.includes(pluginJsonPath)) {
179
+ referencingPlugins.push(pluginJsonPath);
180
+ }
181
+ logger.debug(`Plugin ${pluginDir} references HTML file: ${ref}`);
182
+ break;
183
+ }
184
+ }
185
+ } catch (error) {
186
+ logger.warning(`Error reading plugin.json ${pluginJsonPath}: ${error.message}`);
187
+ }
188
+ }
189
+ }
190
+
191
+ // Check .plugin.json files directly in plugins directory
192
+ for (const pluginFile of pluginFiles) {
193
+ const pluginJsonPath = path.join(pluginsDir, pluginFile);
194
+
195
+ try {
196
+ const content = fs.readFileSync(pluginJsonPath, 'utf-8');
197
+ const plugin = JSON.parse(content);
198
+
199
+ // Check all $ref references in the plugin
200
+ const refs = findAllRefs(plugin);
201
+
202
+ // Check if any ref matches our target HTML file
203
+ // The ref might be like "./micro-frontends/categories.html"
204
+ for (const ref of refs) {
205
+ if (ref.includes(htmlRelativePath) || ref.endsWith(path.basename(htmlRelativePath))) {
206
+ if (!referencingPlugins.includes(pluginJsonPath)) {
207
+ referencingPlugins.push(pluginJsonPath);
208
+ }
209
+ logger.debug(`Plugin ${pluginFile} references HTML file: ${ref}`);
210
+ break;
211
+ }
212
+ }
213
+ } catch (error) {
214
+ logger.warning(`Error reading plugin.json ${pluginJsonPath}: ${error.message}`);
215
+ }
216
+ }
217
+
218
+ return referencingPlugins;
219
+ }
220
+
133
221
  /**
134
222
  * Find integration files that reference a specific file via $ref
135
- * @param {string} refPath - The referenced file path (relative)
223
+ * @param {string} refPath - Reference path to look for
136
224
  * @param {string} basePath - Base path to search for integrations
137
- * @returns {string[]} Array of integration file paths that reference the file
225
+ * @returns {Array} Array of integration file paths that reference the target
138
226
  */
139
227
  function findReferencingIntegrations(refPath, basePath) {
140
228
  const referencingIntegrations = [];
@@ -240,21 +328,23 @@ function classifyFiles(files, basePath) {
240
328
  const basename = path.basename(fullPath, ext);
241
329
 
242
330
  // Check for specific file types
243
- if (relativePath.includes('components/') && ext === '.vue') {
331
+ if (relativePath.startsWith('components/') && ext === '.vue') {
244
332
  classified.components.push(fullPath);
245
- } else if (relativePath.includes('blueprints/') && ext === '.json') {
333
+ } else if (relativePath.startsWith('blueprints/') && ext === '.json') {
246
334
  classified.blueprints.push(fullPath);
247
- } else if (relativePath.includes('configs/') && ext === '.json') {
335
+ } else if (relativePath.startsWith('configs/') && ext === '.json') {
248
336
  classified.configs.push(fullPath);
249
- } else if (relativePath.includes('plugins/') && ext === '.json') {
250
- classified.plugins.push(fullPath);
251
- } else if (relativePath.includes('blocks/') && ext === '.json') {
337
+ } else if (relativePath.startsWith('plugins/') && ext === '.json') {
338
+ if (!classified.plugins.includes(fullPath)) {
339
+ classified.plugins.push(fullPath);
340
+ }
341
+ } else if (relativePath.startsWith('blocks/') && ext === '.json') {
252
342
  classified.blocks.push(fullPath);
253
- } else if (relativePath.includes('integrations/') && ext === '.json') {
343
+ } else if (relativePath.startsWith('integrations/') && ext === '.json') {
254
344
  classified.integrations.push(fullPath);
255
- } else if (relativePath.includes('connections/') && ext === '.json') {
345
+ } else if (relativePath.startsWith('connections/') && ext === '.json') {
256
346
  classified.connections.push(fullPath);
257
- } else if (dir.includes('prompts') && ext === '.md') {
347
+ } else if (relativePath.startsWith('integrations/prompts/') && ext === '.md') {
258
348
  // Find integrations that reference this prompt file
259
349
  classified.prompts.push(fullPath);
260
350
 
@@ -277,7 +367,7 @@ function classifyFiles(files, basePath) {
277
367
  // 2. In configs directory -> These are typically referenced by configs, not pushed directly
278
368
  // 3. Other locations -> treat as micro-frontends
279
369
 
280
- if (relativePath.includes('configs/') || relativePath.includes('configs\\')) {
370
+ if (relativePath.startsWith('configs/') || relativePath.startsWith('configs\\')) {
281
371
  // HTML file in configs directory - these are usually referenced by config files, not pushed directly
282
372
  logger.debug(`Found HTML file in configs directory (will be pushed via referencing config): ${relativePath}`);
283
373
 
@@ -296,44 +386,41 @@ function classifyFiles(files, basePath) {
296
386
  // Find plugins that contain this HTML file (micro-frontends)
297
387
  classified.microFrontends.push(fullPath);
298
388
 
299
- // For HTML files, we need to find which plugin contains them
300
- // HTML files in plugins are typically part of the plugin structure
301
- let pluginDir = path.dirname(fullPath);
302
- let pluginJson = path.join(pluginDir, 'plugin.json');
389
+ // For HTML files, we need to find which plugin references them
390
+ // The HTML file should be in a micro-frontends directory within a plugin
391
+ const htmlBasename = path.basename(fullPath);
303
392
 
304
- // If the file is in a temp path or unusual location, try to find the actual plugin
305
- if (!fs.existsSync(pluginJson)) {
306
- // Check if we're in a micro-frontends subdirectory
307
- if (path.basename(pluginDir) === 'micro-frontends' ||
308
- relativePath.includes('micro-frontends/') ||
309
- relativePath.includes('micro-frontends\\')) {
310
- // Go up one more level to find the plugin directory
311
- pluginDir = path.dirname(pluginDir);
312
- pluginJson = path.join(pluginDir, 'plugin.json');
393
+ // Try to find plugins that reference this HTML file
394
+ // The ref path should be relative to the plugin directory
395
+ // e.g., "./micro-frontends/categories.html"
396
+ let refPath = `./micro-frontends/${htmlBasename}`;
397
+
398
+ // If the file is in a nested path, preserve that structure
399
+ if (relativePath.startsWith('plugins/micro-frontends/')) {
400
+ const parts = relativePath.split('plugins/micro-frontends/');
401
+ if (parts[1]) {
402
+ refPath = `./micro-frontends/${parts[1]}`;
313
403
  }
314
-
315
- // If still not found, try searching for plugin.json in parent directories
316
- if (!fs.existsSync(pluginJson)) {
317
- let searchDir = pluginDir;
318
- for (let i = 0; i < 3; i++) { // Search up to 3 levels up
319
- searchDir = path.dirname(searchDir);
320
- const testPluginJson = path.join(searchDir, 'plugin.json');
321
- if (fs.existsSync(testPluginJson)) {
322
- pluginJson = testPluginJson;
323
- break;
324
- }
325
- }
404
+ } else if (relativePath.includes('micro-frontends/')) {
405
+ // Handle other micro-frontends paths
406
+ const parts = relativePath.split('micro-frontends/');
407
+ if (parts[1]) {
408
+ refPath = `./micro-frontends/${parts[1]}`;
326
409
  }
327
410
  }
328
411
 
329
- if (fs.existsSync(pluginJson)) {
330
- // This HTML file is part of a plugin
331
- if (!classified.plugins.includes(pluginJson)) {
332
- classified.plugins.push(pluginJson);
333
- logger.debug(`Found plugin containing HTML ${relativePath}: ${path.basename(pluginJson)}`);
412
+ const referencingPlugins = findReferencingPlugins(refPath, basePath);
413
+
414
+ // Add all referencing plugins to the classified list
415
+ for (const pluginPath of referencingPlugins) {
416
+ if (!classified.plugins.includes(pluginPath)) {
417
+ classified.plugins.push(pluginPath);
418
+ logger.debug(`Found plugin referencing HTML ${relativePath}: ${path.basename(pluginPath)}`);
334
419
  }
335
- } else {
336
- logger.warning(`Could not find plugin.json for HTML file: ${relativePath}`);
420
+ }
421
+
422
+ if (referencingPlugins.length === 0) {
423
+ logger.warning(`Could not find any plugin referencing HTML file: ${relativePath} (searched for ref: ${refPath})`);
337
424
  }
338
425
  }
339
426
  } else {
@@ -446,6 +533,39 @@ export function prepareTempDirectories(classifiedFiles, tempDir) {
446
533
  }
447
534
  }
448
535
 
536
+ // If this is a config, check for $ref files and copy them too
537
+ if (type === 'configs' && file.endsWith('.config.json')) {
538
+ try {
539
+ const content = fs.readFileSync(dest, 'utf-8');
540
+ const config = JSON.parse(content);
541
+ const refs = findAllRefs(config);
542
+
543
+ for (const ref of refs) {
544
+ if (copiedRefs.has(ref)) continue;
545
+
546
+ // Resolve the ref path relative to the original file location
547
+ const originalDir = path.dirname(file);
548
+ const refSourcePath = path.resolve(originalDir, ref);
549
+
550
+ if (fs.existsSync(refSourcePath)) {
551
+ // Create the same directory structure in temp
552
+ // The ref is relative to the config file, so we need to copy it to the same relative path
553
+ const refDestPath = path.join(tempDir, 'configs', ref);
554
+ const refDestDir = path.dirname(refDestPath);
555
+
556
+ fs.mkdirSync(refDestDir, { recursive: true });
557
+ fs.copyFileSync(refSourcePath, refDestPath);
558
+ copiedRefs.add(ref);
559
+ logger.debug(`Copied referenced file ${ref} from ${refSourcePath} to ${refDestPath}`);
560
+ } else {
561
+ logger.debug(`Referenced file not found: ${refSourcePath}`);
562
+ }
563
+ }
564
+ } catch (error) {
565
+ logger.debug(`Failed to process refs for ${path.basename(file)}: ${error.message}`);
566
+ }
567
+ }
568
+
449
569
  // If this is a plugin, check for micro-frontend HTML files and copy them too
450
570
  if (type === 'plugins' && file.endsWith('.plugin.json')) {
451
571
  try {