@boltic/cli 1.0.6 → 1.0.8

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
@@ -519,15 +519,6 @@ npm install -g @boltic/cli@latest
519
519
  boltic --version
520
520
  ```
521
521
 
522
- **Development mode troubleshooting**:
523
-
524
- ```bash
525
- # For contributors using development installation
526
- npm install # Update dependencies
527
- npm link # Re-link CLI
528
- npm test # Run test suite
529
- ```
530
-
531
522
  #### 9. Emergency Recovery
532
523
 
533
524
  **If all else fails**:
@@ -54,10 +54,6 @@ const commands = {
54
54
  description: "Show detailed information about an integration",
55
55
  action: handleStatus,
56
56
  },
57
- test: {
58
- description: "Run tests for the integration with coverage",
59
- action: handleTest,
60
- },
61
57
  help: {
62
58
  description: "Show help for integration commands",
63
59
  action: showHelp,
@@ -1064,6 +1060,13 @@ async function handleStatus() {
1064
1060
  console.log(integration.documentation);
1065
1061
  }
1066
1062
  } catch (error) {
1063
+ if (
1064
+ error.message &&
1065
+ error.message.includes("User force closed the prompt")
1066
+ ) {
1067
+ console.log(chalk.yellow("\n⚠️ Operation cancelled by user"));
1068
+ return;
1069
+ }
1067
1070
  console.error(
1068
1071
  chalk.red("\n❌ Error fetching integration status:"),
1069
1072
  error.message || "Unknown error"
@@ -1071,258 +1074,6 @@ async function handleStatus() {
1071
1074
  }
1072
1075
  }
1073
1076
 
1074
- async function handleTest(args) {
1075
- // Parse command line arguments
1076
- let currentDir = process.cwd();
1077
- let jestConfigFile = null;
1078
- const pathIndex = args.indexOf("--path");
1079
- const configIndex = args.indexOf("--config");
1080
-
1081
- if (pathIndex !== -1 && args[pathIndex + 1]) {
1082
- currentDir = args[pathIndex + 1];
1083
- // Validate the provided path
1084
- if (!fs.existsSync(currentDir)) {
1085
- console.error(
1086
- chalk.red(
1087
- `Error: The specified path does not exist: ${currentDir}`
1088
- )
1089
- );
1090
- return;
1091
- }
1092
- }
1093
-
1094
- if (configIndex !== -1 && args[configIndex + 1]) {
1095
- jestConfigFile = args[configIndex + 1];
1096
- // Validate the config file path
1097
- const configPath = path.isAbsolute(jestConfigFile)
1098
- ? jestConfigFile
1099
- : path.join(currentDir, jestConfigFile);
1100
-
1101
- if (!fs.existsSync(configPath)) {
1102
- console.error(
1103
- chalk.red(
1104
- `Error: Jest config file does not exist: ${configPath}`
1105
- )
1106
- );
1107
- return;
1108
- }
1109
- }
1110
-
1111
- const { spawn } = await import("child_process");
1112
-
1113
- console.log(chalk.cyan.bold("\n🧪 Running integration tests...\n"));
1114
-
1115
- // Look for test directory and test files
1116
- const testDirs = ["test", "tests", "__tests__"];
1117
- let testDir = null;
1118
- let testFiles = [];
1119
-
1120
- // Check for test directories
1121
- for (const dir of testDirs) {
1122
- const testPath = path.join(currentDir, dir);
1123
- if (fs.existsSync(testPath)) {
1124
- testDir = dir;
1125
- // Find all test files in the directory
1126
- const files = fs.readdirSync(testPath);
1127
- testFiles = files.filter(
1128
- (file) =>
1129
- file.endsWith(".test.js") ||
1130
- file.endsWith(".spec.js") ||
1131
- file.endsWith(".test.ts") ||
1132
- file.endsWith(".spec.ts")
1133
- );
1134
- break;
1135
- }
1136
- }
1137
-
1138
- // If no test directory found, look for test files in current directory
1139
- if (!testDir) {
1140
- const currentDirFiles = fs.readdirSync(currentDir);
1141
- testFiles = currentDirFiles.filter(
1142
- (file) =>
1143
- file.endsWith(".test.js") ||
1144
- file.endsWith(".spec.js") ||
1145
- file.endsWith(".test.ts") ||
1146
- file.endsWith(".spec.ts")
1147
- );
1148
-
1149
- if (testFiles.length === 0) {
1150
- console.log(
1151
- chalk.yellow(
1152
- "⚠️ No test files found. Looked for: test, tests, __tests__ directories and *.test.js, *.spec.js, *.test.ts, *.spec.ts files"
1153
- )
1154
- );
1155
- return;
1156
- }
1157
- }
1158
-
1159
- if (testFiles.length === 0 && testDir) {
1160
- console.log(
1161
- chalk.yellow(
1162
- `⚠️ No test files found in ${testDir} directory. Looking for: *.test.js, *.spec.js, *.test.ts, *.spec.ts files`
1163
- )
1164
- );
1165
- return;
1166
- }
1167
-
1168
- console.log(chalk.dim(`📁 Found test directory: ${testDir || currentDir}`));
1169
- console.log(
1170
- chalk.dim(
1171
- `📋 Found ${testFiles.length} test file(s): ${testFiles.join(", ")}`
1172
- )
1173
- );
1174
-
1175
- // Check for Jest configuration
1176
- let jestConfigPath;
1177
- if (jestConfigFile) {
1178
- jestConfigPath = path.isAbsolute(jestConfigFile)
1179
- ? jestConfigFile
1180
- : path.join(currentDir, jestConfigFile);
1181
- } else {
1182
- // Look for default config files
1183
- const defaultConfigs = [
1184
- "jest.config.cjs",
1185
- "jest.config.js",
1186
- "jest.config.json",
1187
- ];
1188
- for (const configFile of defaultConfigs) {
1189
- const configPath = path.join(currentDir, configFile);
1190
- if (fs.existsSync(configPath)) {
1191
- jestConfigPath = configPath;
1192
- break;
1193
- }
1194
- }
1195
- }
1196
-
1197
- const jestConfigExists = !!jestConfigPath;
1198
- const configFileName = jestConfigPath
1199
- ? path.basename(jestConfigPath)
1200
- : null;
1201
-
1202
- console.log(
1203
- chalk.dim(
1204
- `⚙️ Jest configuration: ${jestConfigExists ? `Found ${configFileName}` : "Using default configuration"}`
1205
- )
1206
- );
1207
-
1208
- // Prepare Jest arguments
1209
- const jestArgs = ["jest"];
1210
-
1211
- // Add basic Jest flags
1212
- jestArgs.push("--coverage");
1213
- jestArgs.push("--verbose");
1214
- jestArgs.push("--colors");
1215
- jestArgs.push("--passWithNoTests");
1216
-
1217
- // Add config file if exists, otherwise use simple patterns
1218
- if (jestConfigExists) {
1219
- jestArgs.push("--config", jestConfigPath);
1220
- } else {
1221
- // Use simple test matching patterns
1222
- if (testDir) {
1223
- jestArgs.push(
1224
- "--testMatch",
1225
- `**/${testDir}/**/*.{test,spec}.{js,ts}`
1226
- );
1227
- } else {
1228
- jestArgs.push("--testMatch", "**/*.{test,spec}.{js,ts}");
1229
- }
1230
- jestArgs.push("--collectCoverageFrom", "lib/**/*.{js,ts}");
1231
- jestArgs.push("--collectCoverageFrom", "src/**/*.{js,ts}");
1232
- jestArgs.push("--collectCoverageFrom", "*.{js,ts}");
1233
- jestArgs.push("--coveragePathIgnorePatterns", "/node_modules/");
1234
- jestArgs.push("--coveragePathIgnorePatterns", "/tests/");
1235
- }
1236
-
1237
- // Add any additional Jest arguments passed by user
1238
- const additionalArgs = args.filter((arg, index) => {
1239
- // Skip our custom flags and their values
1240
- if (arg === "--path" || arg === "--config") return false;
1241
- if (args[index - 1] === "--path" || args[index - 1] === "--config")
1242
- return false;
1243
- return true;
1244
- });
1245
-
1246
- if (additionalArgs.length > 0) {
1247
- jestArgs.push(...additionalArgs);
1248
- console.log(
1249
- chalk.dim(
1250
- `🔧 Additional Jest arguments: ${additionalArgs.join(" ")}`
1251
- )
1252
- );
1253
- }
1254
-
1255
- console.log(chalk.dim(`🚀 Running command: npx ${jestArgs.join(" ")}`));
1256
- console.log(chalk.cyan("─".repeat(50)));
1257
-
1258
- // Run Jest with coverage
1259
- return new Promise((resolve, reject) => {
1260
- const jestProcess = spawn("npx", jestArgs, {
1261
- stdio: "inherit",
1262
- shell: true,
1263
- cwd: currentDir,
1264
- });
1265
-
1266
- jestProcess.on("close", (code) => {
1267
- console.log(chalk.cyan("─".repeat(50)));
1268
-
1269
- if (code === 0) {
1270
- console.log(chalk.green.bold("\n✅ All tests passed!"));
1271
-
1272
- // Check for coverage directory
1273
- const coverageDir = path.join(currentDir, "coverage");
1274
- if (fs.existsSync(coverageDir)) {
1275
- console.log(chalk.cyan("\n📊 Coverage Report Generated:"));
1276
- console.log(chalk.dim(` Directory: ${coverageDir}`));
1277
-
1278
- // Check for HTML report
1279
- const htmlReportPath = path.join(
1280
- coverageDir,
1281
- "lcov-report",
1282
- "index.html"
1283
- );
1284
- if (fs.existsSync(htmlReportPath)) {
1285
- console.log(
1286
- chalk.dim(` HTML Report: ${htmlReportPath}`)
1287
- );
1288
- }
1289
-
1290
- // Check for text report
1291
- const textReportPath = path.join(coverageDir, "lcov.info");
1292
- if (fs.existsSync(textReportPath)) {
1293
- console.log(
1294
- chalk.dim(` LCOV Report: ${textReportPath}`)
1295
- );
1296
- }
1297
- }
1298
-
1299
- // Show test summary
1300
- console.log(chalk.cyan("\n📋 Test Summary:"));
1301
- console.log(chalk.dim(` Test files: ${testFiles.length}`));
1302
- console.log(
1303
- chalk.dim(` Test directory: ${testDir || currentDir}`)
1304
- );
1305
- console.log(chalk.green(" Status: PASSED"));
1306
- } else {
1307
- console.log(chalk.red.bold("\n❌ Some tests failed."));
1308
- console.log(chalk.red(` Exit code: ${code}`));
1309
- }
1310
-
1311
- console.log(
1312
- chalk.cyan(
1313
- "\n💡 Tip: Check the coverage directory for detailed coverage reports"
1314
- )
1315
- );
1316
- resolve(code);
1317
- });
1318
-
1319
- jestProcess.on("error", (error) => {
1320
- console.error(chalk.red("❌ Error running tests:"), error.message);
1321
- reject(error);
1322
- });
1323
- });
1324
- }
1325
-
1326
1077
  export default {
1327
1078
  execute,
1328
1079
  };
@@ -142,21 +142,42 @@ const validateSchemaKeys = (
142
142
  schemaName,
143
143
  displayType,
144
144
  errors,
145
- prefix = ""
145
+ prefix = "",
146
+ filename = ""
146
147
  ) => {
148
+ const fileLabel = filename ? ` in "${filename}"` : "";
149
+
147
150
  Object.keys(schemaObj).forEach((key) => {
148
151
  const fullKey = prefix ? `${prefix}.${key}` : key;
149
152
 
153
+ // Special handling for config.body - skip validation for select, autocomplete, and multiselect
154
+ if (
155
+ fullKey === "config.body" &&
156
+ (displayType === "select" ||
157
+ displayType === "autocomplete" ||
158
+ displayType === "multiselect")
159
+ ) {
160
+ // For these display types, config.body can contain any keys, so we skip validation
161
+ return;
162
+ }
163
+
150
164
  if (!allowedKeys.has(fullKey)) {
151
165
  errors.add(
152
- `"${schemaName}" has an invalid key "${fullKey}" for displayType "${displayType}".`
166
+ `"${schemaName}" has an invalid key "${fullKey}" for displayType "${displayType}"${fileLabel}.`
153
167
  );
154
168
  }
155
169
 
156
170
  if (
157
171
  typeof schemaObj[key] === "object" &&
158
172
  schemaObj[key] !== null &&
159
- !Array.isArray(schemaObj[key])
173
+ !Array.isArray(schemaObj[key]) &&
174
+ // Skip recursion for config.body of select/autocomplete/multiselect
175
+ !(
176
+ fullKey === "config.body" &&
177
+ (displayType === "select" ||
178
+ displayType === "autocomplete" ||
179
+ displayType === "multiselect")
180
+ )
160
181
  ) {
161
182
  validateSchemaKeys(
162
183
  schemaObj[key],
@@ -164,7 +185,8 @@ const validateSchemaKeys = (
164
185
  schemaName,
165
186
  displayType,
166
187
  errors,
167
- fullKey
188
+ fullKey,
189
+ filename
168
190
  );
169
191
  }
170
192
  });
@@ -175,21 +197,29 @@ const validateSchemaKeys = (
175
197
  * @param {Object} schema - The schema to validate
176
198
  * @param {string} displayType - The display type to validate against
177
199
  * @param {Set} errors - Error collection
200
+ * @param {string} filename - The filename for error messages
178
201
  */
179
- const validateComponentByType = (schema, displayType, errors) => {
202
+ const validateComponentByType = (
203
+ schema,
204
+ displayType,
205
+ errors,
206
+ filename = ""
207
+ ) => {
208
+ const fileLabel = filename ? ` in "${filename}"` : "";
209
+
180
210
  // Get the component schema definition for this display type
181
211
  const componentSchema = componentSchemas[displayType];
182
212
 
183
213
  if (!componentSchema) {
184
214
  errors.add(
185
- `"${schema.name}" has an unsupported displayType "${displayType}".`
215
+ `"${schema.name}" has an unsupported displayType "${displayType}"${fileLabel}.`
186
216
  );
187
217
  return;
188
218
  }
189
219
 
190
220
  if (!componentSchema.meta) {
191
221
  errors.add(
192
- `Component schema for "${displayType}" is missing meta definition.`
222
+ `Component schema for "${displayType}" is missing meta definition${fileLabel}.`
193
223
  );
194
224
  return;
195
225
  }
@@ -204,29 +234,39 @@ const validateComponentByType = (schema, displayType, errors) => {
204
234
  allowedKeys,
205
235
  schema.name,
206
236
  currentDisplayType,
207
- errors
237
+ errors,
238
+ "",
239
+ filename
208
240
  );
209
241
  };
210
242
 
211
- const validateComponentSchemas = (schemas, errors) => {
243
+ const validateComponentSchemas = (schemas, errors, filename = "") => {
244
+ const fileLabel = filename ? ` in "${filename}"` : "";
245
+
212
246
  schemas.forEach((schema) => {
213
247
  // Basic required field validation
214
248
  if (!schema.name) {
215
- errors.add(`Schema is missing a name.`);
249
+ errors.add(`Schema is missing a name${fileLabel}.`);
216
250
  return; // Can't continue without a name
217
251
  }
218
252
  if (!schema.meta) {
219
- errors.add(`"${schema.name}" is missing a meta object.`);
253
+ errors.add(
254
+ `"${schema.name}" is missing a meta object${fileLabel}.`
255
+ );
220
256
  return; // Can't continue without meta
221
257
  }
222
258
  if (!schema.meta.displayType) {
223
- errors.add(`"${schema.name}" is missing a displayType.`);
259
+ errors.add(
260
+ `"${schema.name}" is missing a displayType${fileLabel}.`
261
+ );
224
262
  return; // Can't continue without displayType
225
263
  }
226
264
 
227
265
  // Optional field validation (these are warnings, not blocking)
228
266
  if (!schema.meta.displayName) {
229
- errors.add(`"${schema.name}" is missing a displayName.`);
267
+ errors.add(
268
+ `"${schema.name}" is missing a displayName${fileLabel}.`
269
+ );
230
270
  }
231
271
 
232
272
  // Only require placeholder if the component schema defines it
@@ -237,7 +277,9 @@ const validateComponentSchemas = (schemas, errors) => {
237
277
  "placeholder" in componentSchema.meta &&
238
278
  !schema.meta.placeholder
239
279
  ) {
240
- errors.add(`"${schema.name}" is missing a placeholder.`);
280
+ errors.add(
281
+ `"${schema.name}" is missing a placeholder${fileLabel}.`
282
+ );
241
283
  }
242
284
 
243
285
  // Only require description if the component schema defines it
@@ -247,7 +289,9 @@ const validateComponentSchemas = (schemas, errors) => {
247
289
  "description" in componentSchema.meta &&
248
290
  !schema.meta.description
249
291
  ) {
250
- errors.add(`"${schema.name}" is missing a description.`);
292
+ errors.add(
293
+ `"${schema.name}" is missing a description${fileLabel}.`
294
+ );
251
295
  }
252
296
 
253
297
  // 🚨 Validate for duplicate options with same label and value
@@ -258,7 +302,7 @@ const validateComponentSchemas = (schemas, errors) => {
258
302
  const key = `${option.label}::${option.value}`;
259
303
  if (seen.has(key)) {
260
304
  errors.add(
261
- `"${schema.name}" contains duplicate option at index ${index} with label "${option.label}" and value "${option.value}".`
305
+ `"${schema.name}" contains duplicate option at index ${index} with label "${option.label}" and value "${option.value}"${fileLabel}.`
262
306
  );
263
307
  } else {
264
308
  seen.add(key);
@@ -268,7 +312,12 @@ const validateComponentSchemas = (schemas, errors) => {
268
312
  }
269
313
 
270
314
  // Validate against the specific component type schema
271
- validateComponentByType(schema, schema.meta.displayType, errors);
315
+ validateComponentByType(
316
+ schema,
317
+ schema.meta.displayType,
318
+ errors,
319
+ filename
320
+ );
272
321
  });
273
322
  };
274
323
 
@@ -314,7 +363,11 @@ const validateWebhook = (webhookPath, spec, errors) => {
314
363
  errors
315
364
  );
316
365
  if (webhookSchema && Array.isArray(webhookSchema.parameters)) {
317
- validateComponentSchemas(webhookSchema.parameters, errors);
366
+ validateComponentSchemas(
367
+ webhookSchema.parameters,
368
+ errors,
369
+ "webhook.json"
370
+ );
318
371
  }
319
372
  }
320
373
  };
@@ -329,7 +382,7 @@ const validateBaseSchema = (baseSchemaPath, errors) => {
329
382
 
330
383
  // Validate base schema parameters
331
384
  if (baseSchema && Array.isArray(baseSchema.parameters)) {
332
- validateComponentSchemas(baseSchema.parameters, errors);
385
+ validateComponentSchemas(baseSchema.parameters, errors, "base.json");
333
386
  }
334
387
 
335
388
  return baseSchema;
@@ -346,7 +399,11 @@ const validateAuthentication = (authPath, errors) => {
346
399
 
347
400
  // Validate authentication schema parameters
348
401
  if (authSchema && Array.isArray(authSchema.parameters)) {
349
- validateComponentSchemas(authSchema.parameters, errors);
402
+ validateComponentSchemas(
403
+ authSchema.parameters,
404
+ errors,
405
+ "authentication.json"
406
+ );
350
407
  }
351
408
 
352
409
  // Validate authentication type-specific parameters (like api_key, oauth, etc.)
@@ -360,7 +417,8 @@ const validateAuthentication = (authPath, errors) => {
360
417
  if (Array.isArray(authSchema[key].parameters)) {
361
418
  validateComponentSchemas(
362
419
  authSchema[key].parameters,
363
- errors
420
+ errors,
421
+ "authentication.json"
364
422
  );
365
423
  }
366
424
  }
@@ -399,7 +457,11 @@ const validateResources = (resourcesDir, resourceFields, errors) => {
399
457
 
400
458
  // Validate resource file parameters
401
459
  if (Array.isArray(schema.parameters)) {
402
- validateComponentSchemas(schema.parameters, errors);
460
+ validateComponentSchemas(
461
+ schema.parameters,
462
+ errors,
463
+ `${resourceFile}.json`
464
+ );
403
465
  }
404
466
 
405
467
  const operationFields = findOperationFieldsWithOptions(
@@ -433,7 +495,11 @@ const validateResources = (resourcesDir, resourceFields, errors) => {
433
495
  } else {
434
496
  // Validate operation parameters using component schemas
435
497
  if (Array.isArray(methodDef.parameters)) {
436
- validateComponentSchemas(methodDef.parameters, errors);
498
+ validateComponentSchemas(
499
+ methodDef.parameters,
500
+ errors,
501
+ `${resourceFile}.json`
502
+ );
437
503
  }
438
504
  }
439
505
  if (!methodDef.definition) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boltic/cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "A powerful CLI tool for managing Boltic Workflow integrations",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -110,7 +110,12 @@ const select = {
110
110
  url: "/api/options",
111
111
  labelKey: "label",
112
112
  valueKey: "value",
113
- body: {},
113
+ body: {
114
+ secret: "secret",
115
+ loadOptionsMethod: "get",
116
+ resource: "resource",
117
+ operation: "operation",
118
+ },
114
119
  },
115
120
  displayProps: { loading: false },
116
121
  validation: {
@@ -118,6 +123,17 @@ const select = {
118
123
  requiredDetail: {
119
124
  errorMsg: "Selection is required",
120
125
  },
126
+ max: 10,
127
+ maxDetail: {
128
+ errorMsg: "Too many options",
129
+ },
130
+ min: 1,
131
+ minDetail: {
132
+ errorMsg: "At least one option is required",
133
+ },
134
+ },
135
+ htmlProps: {
136
+ allowDynamic: false,
121
137
  },
122
138
  dependencies: {
123
139
  logic: "AND",
@@ -898,6 +914,29 @@ const button = {
898
914
  },
899
915
  };
900
916
 
917
+ const datetime = {
918
+ name: "datetime-picker",
919
+ meta: {
920
+ displayName: "Enter Date with Start Time",
921
+ description: "Please enter date and start time",
922
+ displayType: "datetime",
923
+ value: "2024-02-14",
924
+ validation: {
925
+ required: false,
926
+ },
927
+ readOnly: false,
928
+ htmlProps: {
929
+ format: "YYYY-MM-DD HH:mm:ss",
930
+ disabled: false,
931
+ timeSteps: {
932
+ hours: 1,
933
+ minutes: 5,
934
+ seconds: 5,
935
+ },
936
+ },
937
+ },
938
+ };
939
+
901
940
  export {
902
941
  accordion,
903
942
  array,
@@ -906,6 +945,7 @@ export {
906
945
  checkbox,
907
946
  code,
908
947
  date,
948
+ datetime,
909
949
  divider,
910
950
  email,
911
951
  file,