@qelos/plugins-cli 0.0.23 → 0.0.24

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.23",
3
+ "version": "0.0.24",
4
4
  "description": "CLI to manage QELOS plugins",
5
5
  "main": "cli.mjs",
6
6
  "bin": {
@@ -1,23 +1,57 @@
1
1
  import fs from 'node:fs';
2
- import { join } from 'node:path';
2
+ import path, { join } from 'node:path';
3
3
  import { logger } from './logger.mjs';
4
4
 
5
+ function normalizeRelativeIdentifier(value) {
6
+ return String(value || '')
7
+ .replaceAll('\\', '/')
8
+ .replace(/^\//, '')
9
+ .trim();
10
+ }
11
+
12
+ function isSafeRelativeIdentifier(value) {
13
+ if (!value) return false;
14
+ if (value.includes('..')) return false;
15
+ if (value.startsWith('/')) return false;
16
+ return true;
17
+ }
18
+
19
+ function listVueFilesRecursively(rootDir, currentDir = rootDir, out = []) {
20
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
21
+ for (const entry of entries) {
22
+ const fullPath = join(currentDir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
25
+ listVueFilesRecursively(rootDir, fullPath, out);
26
+ continue;
27
+ }
28
+ if (!entry.isFile()) continue;
29
+ if (!entry.name.endsWith('.vue')) continue;
30
+ if (entry.name === 'components.json') continue;
31
+ out.push(fullPath);
32
+ }
33
+ return out;
34
+ }
35
+
5
36
  /**
6
37
  * Push components from local directory to remote
7
38
  * @param {Object} sdk - Initialized SDK instance
8
39
  * @param {string} path - Path to components directory
9
40
  */
10
- export async function pushComponents(sdk, path, options = {}) {
41
+ export async function pushComponents(sdk, componentsPath, options = {}) {
11
42
  const { targetFile } = options;
12
- const directoryFiles = fs.readdirSync(path);
13
- const files = targetFile ? [targetFile] : directoryFiles;
14
- const vueFiles = files.filter(f => f.endsWith('.vue'));
43
+ const vueFilePaths = targetFile
44
+ ? [join(componentsPath, targetFile)]
45
+ : listVueFilesRecursively(componentsPath);
46
+ const vueFiles = vueFilePaths
47
+ .filter(filePath => fs.existsSync(filePath))
48
+ .filter(filePath => filePath.endsWith('.vue'));
15
49
 
16
50
  if (vueFiles.length === 0) {
17
51
  if (targetFile) {
18
52
  logger.warning(`File ${targetFile} is not a .vue component. Skipping.`);
19
53
  } else {
20
- logger.warning(`No .vue files found in ${path}`);
54
+ logger.warning(`No .vue files found in ${componentsPath}`);
21
55
  }
22
56
  return;
23
57
  }
@@ -26,7 +60,7 @@ export async function pushComponents(sdk, path, options = {}) {
26
60
  let componentsJson = {};
27
61
 
28
62
  try {
29
- const jsonPath = join(path, 'components.json');
63
+ const jsonPath = join(componentsPath, 'components.json');
30
64
  if (fs.existsSync(jsonPath)) {
31
65
  componentsJson = JSON.parse(fs.readFileSync(jsonPath));
32
66
  }
@@ -38,11 +72,17 @@ export async function pushComponents(sdk, path, options = {}) {
38
72
 
39
73
  await Promise.all(vueFiles.map(async (file) => {
40
74
  if (file.endsWith('.vue')) {
41
- const componentName = file.replace('.vue', '');
75
+ const relativePath = normalizeRelativeIdentifier(path.relative(componentsPath, file));
76
+ const componentName = relativePath.split('/').pop().replace('.vue', '');
42
77
  const info = componentsJson[componentName] || {};
43
- const content = fs.readFileSync(join(path, file), 'utf-8');
44
- const targetIdentifier = info.identifier || componentName;
78
+ const content = fs.readFileSync(file, 'utf-8');
79
+ const targetIdentifier = normalizeRelativeIdentifier(info.identifier || relativePath.replace('.vue', ''));
45
80
  const targetDescription = info.description || 'Component description';
81
+
82
+ if (!isSafeRelativeIdentifier(targetIdentifier)) {
83
+ logger.error(`Skipping component with unsafe identifier: ${targetIdentifier}`);
84
+ return;
85
+ }
46
86
 
47
87
  logger.step(`Pushing component: ${componentName}`);
48
88
 
@@ -102,9 +142,20 @@ export async function pullComponents(sdk, targetPath) {
102
142
  logger.info(`Found ${components.length} component(s) to pull`);
103
143
 
104
144
  const componentsInformation = await Promise.all(components.map(async (component) => {
105
- const fileName = `${component.componentName}.vue`;
145
+ const normalizedIdentifier = normalizeRelativeIdentifier(component.identifier);
146
+ const fileName = `${normalizedIdentifier}.vue`;
106
147
  const filePath = join(targetPath, fileName);
107
148
 
149
+ if (!isSafeRelativeIdentifier(normalizedIdentifier)) {
150
+ logger.error(`Skipping pull for unsafe identifier: ${component.identifier}`);
151
+ return null;
152
+ }
153
+
154
+ const targetDir = join(targetPath, fileName.split('/').slice(0, -1).join('/'));
155
+ if (targetDir && !fs.existsSync(targetDir)) {
156
+ fs.mkdirSync(targetDir, { recursive: true });
157
+ }
158
+
108
159
  const { content, description } = await sdk.components.getComponent(component._id);
109
160
 
110
161
  fs.writeFileSync(filePath, content, 'utf-8');
@@ -118,10 +169,12 @@ export async function pullComponents(sdk, targetPath) {
118
169
  };
119
170
  }));
120
171
 
172
+ const filteredComponentsInformation = componentsInformation.filter(Boolean);
173
+
121
174
  fs.writeFileSync(
122
175
  join(targetPath, 'components.json'),
123
176
  JSON.stringify(
124
- componentsInformation.reduce((obj, current) => {
177
+ filteredComponentsInformation.reduce((obj, current) => {
125
178
  obj[current.componentName] = current;
126
179
  return obj;
127
180
  }, {}),
@@ -131,5 +184,5 @@ export async function pullComponents(sdk, targetPath) {
131
184
  );
132
185
 
133
186
  logger.info(`Saved components.json with metadata`);
134
- logger.info(`Pulled ${components.length} component(s)`);
187
+ logger.info(`Pulled ${filteredComponentsInformation.length} component(s)`);
135
188
  }