@aifabrix/builder 2.36.1 → 2.36.2

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.
@@ -728,7 +728,7 @@ async function runCommandWithErrorHandling(command, args, options, appName) {
728
728
  const errorOutput = `${result.stdout}\n${result.stderr}`;
729
729
  if (appName && (errorOutput.includes('not found') ||
730
730
  (errorOutput.includes('External system') && errorOutput.includes('not found')))) {
731
- logWarn(`System ${appName} not deployed, skipping download test`);
731
+ logInfo(`System ${appName} not deployed on dataplane; download step omitted (optional).`);
732
732
  return { skipped: true };
733
733
  }
734
734
  throw new Error(`${command} failed: ${result.stderr || result.stdout}`);
@@ -81,9 +81,33 @@ async function getPipelineHealth(controllerUrl, envKey) {
81
81
  return await client.get(`/api/v1/pipeline/${envKey}/health`);
82
82
  }
83
83
 
84
+ /**
85
+ * Publish one external system via dataplane pipeline endpoint
86
+ * POST /api/v1/pipeline/publish
87
+ * Request body: external system JSON (external-system.schema.json). Optional field in body:
88
+ * generateMcpContract (boolean, default true). Optional: generateOpenApiContract (boolean).
89
+ * Do not use query parameters for MCP; use the field in the body only.
90
+ *
91
+ * @async
92
+ * @function publishSystemViaPipeline
93
+ * @param {string} dataplaneUrl - Dataplane base URL
94
+ * @param {Object} authConfig - Authentication configuration
95
+ * @param {Object} systemConfig - External system configuration (conforms to external-system.schema.json)
96
+ * @returns {Promise<Object>} Published external system response
97
+ * @throws {Error} If publish fails
98
+ */
99
+ async function publishSystemViaPipeline(dataplaneUrl, authConfig, systemConfig) {
100
+ const client = new ApiClient(dataplaneUrl, authConfig);
101
+ return await client.post('/api/v1/pipeline/publish', {
102
+ body: systemConfig
103
+ });
104
+ }
105
+
84
106
  /**
85
107
  * Publish datasource via dataplane pipeline endpoint
86
108
  * POST /api/v1/pipeline/{systemKey}/publish
109
+ * No generateMcpContract for this endpoint; dataplane always uses default (MCP generated).
110
+ *
87
111
  * @async
88
112
  * @function publishDatasourceViaPipeline
89
113
  * @param {string} dataplaneUrl - Dataplane base URL
@@ -169,11 +193,15 @@ async function deployDatasourceViaPipeline(dataplaneUrl, systemKey, authConfig,
169
193
  /**
170
194
  * Upload application configuration via dataplane pipeline endpoint
171
195
  * POST /api/v1/pipeline/upload
196
+ * Body: { version, application, dataSources }. Include application.generateMcpContract
197
+ * and/or application.generateOpenApiContract to control contract generation when
198
+ * publishing this upload (publish reads from stored config; no query param on publish).
199
+ *
172
200
  * @async
173
201
  * @function uploadApplicationViaPipeline
174
202
  * @param {string} dataplaneUrl - Dataplane base URL
175
203
  * @param {Object} authConfig - Authentication configuration
176
- * @param {Object} applicationSchema - Application schema configuration
204
+ * @param {Object} applicationSchema - { version, application, dataSources }; application may include generateMcpContract, generateOpenApiContract
177
205
  * @returns {Promise<Object>} Upload response with uploadId
178
206
  * @throws {Error} If upload fails
179
207
  */
@@ -203,20 +231,22 @@ async function validateUploadViaPipeline(dataplaneUrl, uploadId, authConfig) {
203
231
  /**
204
232
  * Publish upload via dataplane pipeline endpoint
205
233
  * POST /api/v1/pipeline/upload/{uploadId}/publish
234
+ * No body or query parameters. MCP/OpenAPI generation is taken from the application config
235
+ * that was uploaded (application.generateMcpContract, application.generateOpenApiContract).
236
+ * To control MCP/OpenAPI, include those fields in the application object when calling
237
+ * uploadApplicationViaPipeline.
238
+ *
206
239
  * @async
207
240
  * @function publishUploadViaPipeline
208
241
  * @param {string} dataplaneUrl - Dataplane base URL
209
242
  * @param {string} uploadId - Upload ID
210
243
  * @param {Object} authConfig - Authentication configuration
211
- * @param {Object} [options] - Publish options
212
- * @param {boolean} [options.generateMcpContract] - Generate MCP contract (default: true)
213
244
  * @returns {Promise<Object>} Publish response
214
245
  * @throws {Error} If publish fails
215
246
  */
216
- async function publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig, options = {}) {
247
+ async function publishUploadViaPipeline(dataplaneUrl, uploadId, authConfig) {
217
248
  const client = new ApiClient(dataplaneUrl, authConfig);
218
- const generateMcpContract = options.generateMcpContract !== false; // Default to true
219
- return await client.post(`/api/v1/pipeline/upload/${uploadId}/publish?generateMcpContract=${generateMcpContract}`);
249
+ return await client.post(`/api/v1/pipeline/upload/${uploadId}/publish`);
220
250
  }
221
251
 
222
252
  module.exports = {
@@ -224,6 +254,7 @@ module.exports = {
224
254
  deployPipeline,
225
255
  getPipelineDeployment,
226
256
  getPipelineHealth,
257
+ publishSystemViaPipeline,
227
258
  publishDatasourceViaPipeline,
228
259
  testDatasourceViaPipeline,
229
260
  deployExternalSystemViaPipeline,
package/lib/app/list.js CHANGED
@@ -168,7 +168,7 @@ function displayApplications(applications, environment, controllerUrl) {
168
168
  const urlAndPort = formatUrlAndPort(app);
169
169
  logger.log(`${hasPipeline} ${chalk.cyan(app.key)} - ${app.displayName} (${app.status || 'unknown'})${urlAndPort}`);
170
170
  });
171
- logger.log('');
171
+ logger.log(chalk.gray(' To show details for an app: aifabrix app show <appKey>\n'));
172
172
  }
173
173
 
174
174
  /**
@@ -197,15 +197,15 @@ function displayTokenInfo(tokenInfo) {
197
197
  const statusIcon = tokenInfo.authenticated ? chalk.green('✓') : chalk.red('✗');
198
198
  const statusText = tokenInfo.authenticated ? 'Authenticated' : 'Not authenticated';
199
199
 
200
- logger.log(`Status: ${statusIcon} ${statusText}`);
201
- logger.log(`Token Type: ${chalk.cyan(tokenInfo.type)}`);
200
+ logger.log(` Status: ${statusIcon} ${statusText}`);
201
+ logger.log(` Token Type: ${chalk.cyan(tokenInfo.type)}`);
202
202
 
203
203
  if (tokenInfo.appName) {
204
- logger.log(`Application: ${chalk.cyan(tokenInfo.appName)}`);
204
+ logger.log(` Application: ${chalk.cyan(tokenInfo.appName)}`);
205
205
  }
206
206
 
207
207
  if (tokenInfo.expiresAt) {
208
- logger.log(`Expires: ${chalk.gray(formatExpiration(tokenInfo.expiresAt))}`);
208
+ logger.log(` Expires: ${chalk.gray(formatExpiration(tokenInfo.expiresAt))}`);
209
209
  }
210
210
 
211
211
  if (tokenInfo.error) {
@@ -241,14 +241,43 @@ async function resolveDataplaneUrlSilent(controllerUrl, environment, authConfig)
241
241
  * @param {boolean} dataplaneConnected - Whether dataplane health check passed
242
242
  */
243
243
  function displayDataplaneSection(dataplaneUrl, dataplaneConnected) {
244
+ logger.log('');
244
245
  if (dataplaneUrl) {
245
246
  logger.log(`Dataplane: ${chalk.cyan(dataplaneUrl)}`);
246
247
  const statusIcon = dataplaneConnected ? chalk.green('✓') : chalk.red('✗');
247
248
  const statusText = dataplaneConnected ? 'Connected' : 'Not reachable';
248
- logger.log(`Status: ${statusIcon} ${statusText}`);
249
+ displayOpenApiDocs(null, dataplaneUrl);
250
+ logger.log('');
251
+ logger.log(` Status: ${statusIcon} ${statusText}`);
249
252
  } else {
250
253
  logger.log(`Dataplane: ${chalk.gray('—')}`);
251
- logger.log(`Status: ${chalk.gray('Not discovered')}`);
254
+ logger.log('');
255
+ logger.log(` Status: ${chalk.gray('Not discovered')}`);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Normalize base URL (no trailing slash) for docs path
261
+ * @param {string} url - Base URL
262
+ * @returns {string} URL without trailing slash
263
+ */
264
+ function normalizeBaseUrl(url) {
265
+ return (url || '').replace(/\/$/, '');
266
+ }
267
+
268
+ /**
269
+ * Display Open API documentation links (Controller and Dataplane)
270
+ * @param {string} controllerUrl - Controller URL
271
+ * @param {string|null} dataplaneUrl - Dataplane URL or null
272
+ */
273
+ function displayOpenApiDocs(controllerUrl, dataplaneUrl) {
274
+ const controllerBase = normalizeBaseUrl(controllerUrl);
275
+ if (controllerBase) {
276
+ logger.log(` Open API docs: ${chalk.cyan(controllerBase + '/api/docs')}`);
277
+ }
278
+ if (dataplaneUrl) {
279
+ const dataplaneBase = normalizeBaseUrl(dataplaneUrl);
280
+ logger.log(` Open API docs: ${chalk.cyan(dataplaneBase + '/api/docs')}`);
252
281
  }
253
282
  }
254
283
 
@@ -264,11 +293,12 @@ function displayDataplaneSection(dataplaneUrl, dataplaneConnected) {
264
293
  function displayStatus(controllerUrl, environment, tokenInfo, dataplaneInfo) {
265
294
  logger.log(chalk.bold('\n🔐 Authentication Status\n'));
266
295
  logger.log(`Controller: ${chalk.cyan(controllerUrl)}`);
267
- logger.log(`Environment: ${chalk.cyan(environment || 'Not specified')}\n`);
296
+ displayOpenApiDocs(controllerUrl, null);
297
+ logger.log(` Environment: ${chalk.cyan(environment || 'Not specified')}\n`);
268
298
 
269
299
  if (!tokenInfo) {
270
- logger.log(`Status: ${chalk.red('✗ Not authenticated')}`);
271
- logger.log(`Token Type: ${chalk.gray('None')}\n`);
300
+ logger.log(` Status: ${chalk.red('✗ Not authenticated')}`);
301
+ logger.log(` Token Type: ${chalk.gray('None')}\n`);
272
302
  logger.log(chalk.yellow('💡 Run "aifabrix login" to authenticate\n'));
273
303
  return;
274
304
  }
@@ -7,7 +7,7 @@
7
7
  "key": "external-system-schema",
8
8
  "name": "External System Configuration Schema",
9
9
  "description": "JSON schema for validating ExternalSystem configuration files",
10
- "version": "1.1.0",
10
+ "version": "1.2.0",
11
11
  "type": "schema",
12
12
  "category": "integration",
13
13
  "author": "AI Fabrix Team",
@@ -27,6 +27,15 @@
27
27
  ],
28
28
  "dependencies": [],
29
29
  "changelog": [
30
+ {
31
+ "version": "1.2.0",
32
+ "date": "2025-02-01T00:00:00Z",
33
+ "changes": [
34
+ "Added generateMcpContract (boolean, default true): config-only control for MCP contract generation on publish",
35
+ "Added generateOpenApiContract (boolean, default true): reserved for future use"
36
+ ],
37
+ "breaking": false
38
+ },
30
39
  {
31
40
  "version": "1.1.0",
32
41
  "date": "2025-12-01T00:00:00Z",
@@ -407,6 +416,20 @@
407
416
  "type": "boolean",
408
417
  "description": "Master switch for all endpoints in this system. If false, no endpoints are registered regardless of individual endpoint active flags.",
409
418
  "default": true
419
+ },
420
+ "credentialIdOrKey": {
421
+ "type": "string",
422
+ "description": "Credential identifier (ID or key) to use for authenticating with this external system."
423
+ },
424
+ "generateMcpContract": {
425
+ "type": "boolean",
426
+ "description": "Whether to generate MCP contract on publish. Config only (no query parameter); default true when absent.",
427
+ "default": true
428
+ },
429
+ "generateOpenApiContract": {
430
+ "type": "boolean",
431
+ "description": "Reserved: whether to generate or expose OpenAPI contract on publish. Not yet implemented.",
432
+ "default": true
410
433
  }
411
434
  },
412
435
  "additionalProperties": false
@@ -251,9 +251,8 @@ async function tryClientTokenAuth(environment, appName, controllerUrl) {
251
251
  controller: clientToken.controller
252
252
  };
253
253
  }
254
- } catch (error) {
255
- // Client token unavailable, continue to credentials
256
- logger.warn(`Client token unavailable: ${error.message}`);
254
+ } catch {
255
+ // Client token unavailable; getDeploymentAuth will try client credentials next (no warning here to avoid misleading output when env credentials succeed)
257
256
  }
258
257
  return null;
259
258
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.36.1",
3
+ "version": "2.36.2",
4
4
  "description": "AI Fabrix Local Fabric & Deployment SDK",
5
5
  "main": "lib/index.js",
6
6
  "bin": {