@boltic/cli 1.0.6-beta.11 → 1.0.6-beta.12

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
@@ -5,6 +5,7 @@
5
5
  [![NPM Version](https://img.shields.io/npm/v/@boltic/cli)](https://www.npmjs.com/package/@boltic/cli)
6
6
  [![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-blue?logo=github)](https://github.com/<your-username>/<your-repo>)
7
7
  [![License](https://img.shields.io/npm/l/@boltic/cli)](./LICENSE)
8
+ [![Node.js Package](https://github.com/bolticio/cli/actions/workflows/npm-publish.yml/badge.svg)](https://github.com/bolticio/cli/actions/workflows/npm-publish.yml)
8
9
 
9
10
  ---
10
11
 
@@ -65,26 +66,26 @@ boltic integration edit
65
66
  boltic integration sync
66
67
  ```
67
68
 
68
- ### šŸš€ Publish an Integration
69
+ ### šŸš€ Submit an Integration
69
70
 
70
71
  ```bash
71
- boltic integration publish
72
+ boltic integration submit
72
73
  ```
73
74
 
74
75
  ---
75
76
 
76
77
  ## šŸ“Œ Command Reference
77
78
 
78
- | Command | Description |
79
- | ---------------------------- | -------------------------------------------------------- |
80
- | `boltic login` | Authenticate with Boltic |
81
- | `boltic integration create` | Create a new integration |
82
- | `boltic integration sync` | Sync changes to your draft |
83
- | `boltic integration publish` | Submit integration for review |
84
- | `boltic integration pull` | Pull the latest changes of an integration from the Cloud |
85
- | `boltic integration edit` | Edit an existing integration |
86
- | `boltic help` | Show CLI help |
87
- | `boltic version` | Display CLI version |
79
+ | Command | Description |
80
+ | --------------------------- | -------------------------------------------------------- |
81
+ | `boltic login` | Authenticate with Boltic |
82
+ | `boltic integration create` | Create a new integration |
83
+ | `boltic integration sync` | Sync changes to your draft |
84
+ | `boltic integration submit` | Submit integration for review |
85
+ | `boltic integration pull` | Pull the latest changes of an integration from the Cloud |
86
+ | `boltic integration edit` | Edit an existing integration |
87
+ | `boltic help` | Show CLI help |
88
+ | `boltic version` | Display CLI version |
88
89
 
89
90
  ---
90
91
 
@@ -101,7 +102,7 @@ boltic integration create
101
102
  boltic integration sync
102
103
 
103
104
  # Step 5: Submit for publishing and review
104
- boltic integration publish
105
+ boltic integration submit
105
106
 
106
107
  # Step 6: Pull the latest changes of a integration. Please call this command inside a integration folder.
107
108
  boltic integration pull
@@ -34,8 +34,12 @@ const commands = {
34
34
  description: "Edit an existing integration",
35
35
  action: handleEdit,
36
36
  },
37
+ submit: {
38
+ description: "Submit an integration for review",
39
+ action: handleSubmit,
40
+ },
37
41
  publish: {
38
- description: "Publish an integration",
42
+ description: "Publish an integration (deprecated, use submit)",
39
43
  action: handlePublish,
40
44
  },
41
45
  sync: {
@@ -236,8 +240,8 @@ async function handleSync(args) {
236
240
  }
237
241
 
238
242
  // Publish an integration
239
- async function handlePublish(args) {
240
- console.log(chalk.green("Publishing integration...\n"));
243
+ async function handleSubmit(args) {
244
+ console.log(chalk.green("Submitting integration for review...\n"));
241
245
  // Parse command line arguments
242
246
  let currentDir = process.cwd();
243
247
  const pathIndex = args.indexOf("--path");
@@ -347,18 +351,32 @@ async function handlePublish(args) {
347
351
  if (review) {
348
352
  console.log(
349
353
  chalk.green(
350
- "\nāœ… Integration sent to review successfully!\n"
354
+ "\nāœ… Integration submitted for review successfully!\n"
351
355
  )
352
356
  );
353
357
  }
354
358
  } else {
355
359
  console.error(
356
- chalk.red("\nāŒ Error publishing integration:", data.message)
360
+ chalk.red("\nāŒ Error submitting integration:", data.message)
357
361
  );
358
362
  }
359
363
  }
360
364
  }
361
365
 
366
+ async function handlePublish(args) {
367
+ console.log(
368
+ chalk.yellow(
369
+ "āš ļø WARNING: The 'publish' command is deprecated and will be removed in a future version."
370
+ )
371
+ );
372
+ console.log(
373
+ chalk.yellow("Please use 'boltic integration submit' instead.\n")
374
+ );
375
+
376
+ // Call the new submit function
377
+ await handleSubmit(args);
378
+ }
379
+
362
380
  // Execute the integration command
363
381
  const execute = async (args) => {
364
382
  const subCommand = args[0];
@@ -609,15 +627,15 @@ async function handleCreate() {
609
627
  create_catalogue
610
628
  );
611
629
 
612
- // Also share Documentation URL to the user: https://docs.boltic.io/docs/integration-builder/develop/boilerplate
613
630
  const documentationUrl =
614
631
  "https://docs.boltic.io/docs/integration-builder/develop/boilerplate";
632
+
615
633
  console.log(
616
634
  chalk.cyan(
617
- "\nšŸ“„ Documentation URL: " +
618
- chalk.underline.blue(documentationUrl)
635
+ `šŸ“„ For detailed instructions on next steps, refer to the official documentation:`
619
636
  )
620
637
  );
638
+ console.log(chalk.underline.blue(documentationUrl));
621
639
  }
622
640
  } catch (error) {
623
641
  console.error(
@@ -1,6 +1,21 @@
1
1
  import fs from "fs";
2
2
  import isEmpty from "lodash.isempty";
3
3
  import path from "path";
4
+ import * as componentSchemas from "../templates/component-schemas.js";
5
+
6
+ const validateOptionObject = (options, fieldName, fileLabel, errors) => {
7
+ options.forEach((opt, index) => {
8
+ const missingKeys = ["label", "value", "description"].filter(
9
+ (key) => !(key in opt)
10
+ );
11
+
12
+ if (missingKeys.length > 0) {
13
+ errors.add(
14
+ `"${fieldName}" field in "${fileLabel}" has an option at index ${index} missing keys: ${missingKeys.join(", ")}.`
15
+ );
16
+ }
17
+ });
18
+ };
4
19
 
5
20
  const readAndParseJson = (filePath, fileLabel, errors) => {
6
21
  try {
@@ -15,8 +30,7 @@ const readAndParseJson = (filePath, fileLabel, errors) => {
15
30
  return null;
16
31
  }
17
32
  };
18
-
19
- const findResourceFieldsWithOptions = (schema) => {
33
+ const findResourceFieldsWithOptions = (schema, fileLabel, errors) => {
20
34
  const resourceFields = [];
21
35
  if (Array.isArray(schema?.parameters)) {
22
36
  schema.parameters.forEach((param) => {
@@ -24,6 +38,12 @@ const findResourceFieldsWithOptions = (schema) => {
24
38
  param.name === "resource" &&
25
39
  Array.isArray(param.meta?.options)
26
40
  ) {
41
+ validateOptionObject(
42
+ param.meta.options,
43
+ "resource",
44
+ fileLabel,
45
+ errors
46
+ );
27
47
  resourceFields.push(
28
48
  ...param.meta.options.map((opt) => opt.value)
29
49
  );
@@ -32,8 +52,7 @@ const findResourceFieldsWithOptions = (schema) => {
32
52
  }
33
53
  return resourceFields;
34
54
  };
35
-
36
- const findOperationFieldsWithOptions = (schema) => {
55
+ const findOperationFieldsWithOptions = (schema, fileLabel, errors) => {
37
56
  const operationFields = [];
38
57
  if (Array.isArray(schema?.parameters)) {
39
58
  schema.parameters.forEach((param) => {
@@ -41,6 +60,12 @@ const findOperationFieldsWithOptions = (schema) => {
41
60
  param.name === "operation" &&
42
61
  Array.isArray(param.meta?.options)
43
62
  ) {
63
+ validateOptionObject(
64
+ param.meta.options,
65
+ "operation",
66
+ fileLabel,
67
+ errors
68
+ );
44
69
  operationFields.push(
45
70
  ...param.meta.options.map((opt) => opt.value)
46
71
  );
@@ -50,6 +75,164 @@ const findOperationFieldsWithOptions = (schema) => {
50
75
  return operationFields;
51
76
  };
52
77
 
78
+ // ─────────────────────────────────────────────────────────────────────────────
79
+ // VALIDATE COMPONENT SCHEMAS
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+
82
+ /**
83
+ * Extract all possible keys from a component schema structure recursively
84
+ * @param {Object} obj - The object to extract keys from
85
+ * @param {string} prefix - Current key prefix for nested objects
86
+ * @returns {Set<string>} Set of all allowed keys with dot notation for nested paths
87
+ */
88
+ const extractAllowedKeys = (obj, prefix = "") => {
89
+ const allowedKeys = new Set();
90
+
91
+ Object.keys(obj).forEach((key) => {
92
+ const fullKey = prefix ? `${prefix}.${key}` : key;
93
+ allowedKeys.add(fullKey);
94
+
95
+ if (
96
+ typeof obj[key] === "object" &&
97
+ obj[key] !== null &&
98
+ !Array.isArray(obj[key])
99
+ ) {
100
+ const nestedKeys = extractAllowedKeys(obj[key], fullKey);
101
+ nestedKeys.forEach((nestedKey) => allowedKeys.add(nestedKey));
102
+ }
103
+ });
104
+
105
+ return allowedKeys;
106
+ };
107
+
108
+ /**
109
+ * Validate that a schema object doesn't contain any extra keys
110
+ * @param {Object} schemaObj - The schema object to validate
111
+ * @param {Set<string>} allowedKeys - Set of allowed keys
112
+ * @param {string} schemaName - Name of the schema for error messages
113
+ * @param {string} displayType - The display type for error messages
114
+ * @param {Set} errors - Error collection
115
+ * @param {string} prefix - Current key prefix for nested objects
116
+ */
117
+ const validateSchemaKeys = (
118
+ schemaObj,
119
+ allowedKeys,
120
+ schemaName,
121
+ displayType,
122
+ errors,
123
+ prefix = ""
124
+ ) => {
125
+ Object.keys(schemaObj).forEach((key) => {
126
+ const fullKey = prefix ? `${prefix}.${key}` : key;
127
+
128
+ if (!allowedKeys.has(fullKey)) {
129
+ errors.add(
130
+ `"${schemaName}" has an invalid key "${fullKey}" for displayType "${displayType}".`
131
+ );
132
+ }
133
+
134
+ if (
135
+ typeof schemaObj[key] === "object" &&
136
+ schemaObj[key] !== null &&
137
+ !Array.isArray(schemaObj[key])
138
+ ) {
139
+ validateSchemaKeys(
140
+ schemaObj[key],
141
+ allowedKeys,
142
+ schemaName,
143
+ displayType,
144
+ errors,
145
+ fullKey
146
+ );
147
+ }
148
+ });
149
+ };
150
+
151
+ /**
152
+ * Validate a single component schema against its component type definition
153
+ * @param {Object} schema - The schema to validate
154
+ * @param {string} displayType - The display type to validate against
155
+ * @param {Set} errors - Error collection
156
+ */
157
+ const validateComponentByType = (schema, displayType, errors) => {
158
+ // Get the component schema definition for this display type
159
+ const componentSchema = componentSchemas[displayType];
160
+
161
+ if (!componentSchema) {
162
+ errors.add(
163
+ `"${schema.name}" has an unsupported displayType "${displayType}".`
164
+ );
165
+ return;
166
+ }
167
+
168
+ if (!componentSchema.meta) {
169
+ errors.add(
170
+ `Component schema for "${displayType}" is missing meta definition.`
171
+ );
172
+ return;
173
+ }
174
+
175
+ // Extract allowed keys from the component schema
176
+ const allowedKeys = extractAllowedKeys(componentSchema.meta);
177
+
178
+ // Validate the schema meta object (excluding displayType which we already handled)
179
+ const { displayType: currentDisplayType, ...restMeta } = schema.meta;
180
+ validateSchemaKeys(
181
+ restMeta,
182
+ allowedKeys,
183
+ schema.name,
184
+ currentDisplayType,
185
+ errors
186
+ );
187
+ };
188
+
189
+ const validateComponentSchemas = (schemas, errors) => {
190
+ schemas.forEach((schema) => {
191
+ // Basic required field validation
192
+ if (!schema.name) {
193
+ errors.add(`Schema is missing a name.`);
194
+ return; // Can't continue without a name
195
+ }
196
+ if (!schema.meta) {
197
+ errors.add(`"${schema.name}" is missing a meta object.`);
198
+ return; // Can't continue without meta
199
+ }
200
+ if (!schema.meta.displayType) {
201
+ errors.add(`"${schema.name}" is missing a displayType.`);
202
+ return; // Can't continue without displayType
203
+ }
204
+
205
+ // Optional field validation (these are warnings, not blocking)
206
+ if (!schema.meta.displayName) {
207
+ errors.add(`"${schema.name}" is missing a displayName.`);
208
+ }
209
+
210
+ // Only require placeholder if the component schema defines it
211
+ const componentSchema = componentSchemas[schema.meta.displayType];
212
+ if (
213
+ componentSchema &&
214
+ componentSchema.meta &&
215
+ "placeholder" in componentSchema.meta &&
216
+ !schema.meta.placeholder
217
+ ) {
218
+ errors.add(`"${schema.name}" is missing a placeholder.`);
219
+ }
220
+
221
+ // Only require description if the component schema defines it
222
+ if (
223
+ componentSchema &&
224
+ componentSchema.meta &&
225
+ "description" in componentSchema.meta &&
226
+ !schema.meta.description
227
+ ) {
228
+ errors.add(`"${schema.name}" is missing a description.`);
229
+ }
230
+
231
+ // Validate against the specific component type schema
232
+ validateComponentByType(schema, schema.meta.displayType, errors);
233
+ });
234
+ };
235
+
53
236
  // ─────────────────────────────────────────────────────────────────────────────
54
237
  // INDIVIDUAL VALIDATORS
55
238
  // ─────────────────────────────────────────────────────────────────────────────
@@ -83,6 +266,18 @@ const validateWebhook = (webhookPath, spec, errors) => {
83
266
  `"webhook.json" exists but trigger_type is not defined in spec.json.`
84
267
  );
85
268
  }
269
+
270
+ // Validate webhook schema parameters if webhook exists
271
+ if (hasWebhook) {
272
+ const webhookSchema = readAndParseJson(
273
+ webhookPath,
274
+ "webhook.json",
275
+ errors
276
+ );
277
+ if (webhookSchema && Array.isArray(webhookSchema.parameters)) {
278
+ validateComponentSchemas(webhookSchema.parameters, errors);
279
+ }
280
+ }
86
281
  };
87
282
 
88
283
  const validateBaseSchema = (baseSchemaPath, errors) => {
@@ -90,7 +285,49 @@ const validateBaseSchema = (baseSchemaPath, errors) => {
90
285
  errors.add(`"base.json" not found in the "schemas" directory.`);
91
286
  return null;
92
287
  }
93
- return readAndParseJson(baseSchemaPath, "base.json", errors);
288
+
289
+ const baseSchema = readAndParseJson(baseSchemaPath, "base.json", errors);
290
+
291
+ // Validate base schema parameters
292
+ if (baseSchema && Array.isArray(baseSchema.parameters)) {
293
+ validateComponentSchemas(baseSchema.parameters, errors);
294
+ }
295
+
296
+ return baseSchema;
297
+ };
298
+
299
+ const validateAuthentication = (authPath, errors) => {
300
+ // Authentication is optional, so only validate if it exists
301
+ if (fs.existsSync(authPath)) {
302
+ const authSchema = readAndParseJson(
303
+ authPath,
304
+ "authentication.json",
305
+ errors
306
+ );
307
+
308
+ // Validate authentication schema parameters
309
+ if (authSchema && Array.isArray(authSchema.parameters)) {
310
+ validateComponentSchemas(authSchema.parameters, errors);
311
+ }
312
+
313
+ // Validate authentication type-specific parameters (like api_key, oauth, etc.)
314
+ if (authSchema) {
315
+ Object.keys(authSchema).forEach((key) => {
316
+ if (
317
+ key !== "parameters" &&
318
+ typeof authSchema[key] === "object" &&
319
+ authSchema[key] !== null
320
+ ) {
321
+ if (Array.isArray(authSchema[key].parameters)) {
322
+ validateComponentSchemas(
323
+ authSchema[key].parameters,
324
+ errors
325
+ );
326
+ }
327
+ }
328
+ });
329
+ }
330
+ }
94
331
  };
95
332
 
96
333
  const validateResources = (resourcesDir, resourceFields, errors) => {
@@ -121,7 +358,16 @@ const validateResources = (resourcesDir, resourceFields, errors) => {
121
358
  );
122
359
  if (!schema) return;
123
360
 
124
- const operationFields = findOperationFieldsWithOptions(schema);
361
+ // Validate resource file parameters
362
+ if (Array.isArray(schema.parameters)) {
363
+ validateComponentSchemas(schema.parameters, errors);
364
+ }
365
+
366
+ const operationFields = findOperationFieldsWithOptions(
367
+ schema,
368
+ `${resourceFile}.json`,
369
+ errors
370
+ );
125
371
 
126
372
  operationFields.forEach((operation) => {
127
373
  const operationMethod = operation.split(".")[1];
@@ -144,6 +390,11 @@ const validateResources = (resourcesDir, resourceFields, errors) => {
144
390
  errors.add(
145
391
  `Operation "${operationMethod}" in "${resourceFile}.json" is missing parameters.`
146
392
  );
393
+ } else {
394
+ // Validate operation parameters using component schemas
395
+ if (Array.isArray(methodDef.parameters)) {
396
+ validateComponentSchemas(methodDef.parameters, errors);
397
+ }
147
398
  }
148
399
  if (!methodDef.definition) {
149
400
  errors.add(
@@ -154,23 +405,6 @@ const validateResources = (resourcesDir, resourceFields, errors) => {
154
405
  });
155
406
  };
156
407
 
157
- // const validateParametersScema = (schema, errors) => {
158
- // if (!schema || !Array.isArray(schema.parameters)) {
159
- // errors.add(
160
- // `Schema is missing or parameters are not defined as an array.`
161
- // );
162
- // return;
163
- // }
164
- // schema.parameters.forEach((param) => {
165
- // const { meta } = param;
166
- // if (!param.name) {
167
- // errors.add(
168
- // `Parameter in schema is missing a name. Ensure all parameters have a "name" field.`
169
- // );
170
- // }
171
- // });
172
- // };
173
-
174
408
  // ─────────────────────────────────────────────────────────────────────────────
175
409
  // MAIN FUNCTION
176
410
  // ─────────────────────────────────────────────────────────────────────────────
@@ -184,6 +418,7 @@ export const validateIntegrationSchemas = (currentDir) => {
184
418
  resources: path.join(currentDir, "schemas", "resources"),
185
419
  spec: path.join(currentDir, "spec.json"),
186
420
  webhook: path.join(currentDir, "schemas", "webhook.json"),
421
+ authentication: path.join(currentDir, "schemas", "authentication.json"),
187
422
  documentation: path.join(currentDir, "Documentation.mdx"),
188
423
  };
189
424
 
@@ -192,10 +427,11 @@ export const validateIntegrationSchemas = (currentDir) => {
192
427
 
193
428
  const spec = validateSpec(paths.spec, errors);
194
429
  validateWebhook(paths.webhook, spec, errors);
430
+ validateAuthentication(paths.authentication, errors);
195
431
 
196
432
  const baseSchema = validateBaseSchema(paths.base, errors);
197
433
  const resourceFields = baseSchema
198
- ? findResourceFieldsWithOptions(baseSchema)
434
+ ? findResourceFieldsWithOptions(baseSchema, "base.json", errors)
199
435
  : [];
200
436
 
201
437
  validateResources(paths.resources, resourceFields, errors);