@aifabrix/builder 2.39.3 → 2.40.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.
Files changed (114) hide show
  1. package/.cursor/rules/project-rules.mdc +6 -6
  2. package/README.md +2 -2
  3. package/babel.config.js +6 -0
  4. package/integration/hubspot/README.md +53 -141
  5. package/integration/hubspot/application.yaml +37 -0
  6. package/integration/hubspot/env.template +2 -11
  7. package/integration/hubspot/hubspot-deploy.json +1 -0
  8. package/integration/hubspot/test.js +5 -5
  9. package/lib/api/credentials.api.js +5 -5
  10. package/lib/api/deployments.api.js +2 -2
  11. package/lib/api/pipeline.api.js +17 -17
  12. package/lib/api/wizard.api.js +2 -2
  13. package/lib/app/config.js +11 -6
  14. package/lib/app/deploy-config.js +13 -16
  15. package/lib/app/deploy.js +29 -22
  16. package/lib/app/display.js +1 -1
  17. package/lib/app/dockerfile.js +11 -12
  18. package/lib/app/helpers.js +51 -13
  19. package/lib/app/index.js +14 -2
  20. package/lib/app/prompts.js +37 -45
  21. package/lib/app/push.js +8 -11
  22. package/lib/app/readme.js +16 -12
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/run-helpers.js +31 -22
  25. package/lib/app/run.js +44 -5
  26. package/lib/app/show-display.js +104 -44
  27. package/lib/app/show.js +123 -43
  28. package/lib/build/index.js +11 -18
  29. package/lib/cli/setup-app.js +36 -29
  30. package/lib/cli/setup-auth.js +18 -15
  31. package/lib/cli/setup-credential-deployment.js +3 -1
  32. package/lib/cli/setup-external-system.js +35 -16
  33. package/lib/cli/setup-infra.js +45 -23
  34. package/lib/cli/setup-utility.js +79 -31
  35. package/lib/commands/app-logs.js +28 -20
  36. package/lib/commands/app.js +30 -26
  37. package/lib/commands/convert.js +202 -0
  38. package/lib/commands/credential-list.js +78 -17
  39. package/lib/commands/datasource.js +24 -24
  40. package/lib/commands/deployment-list.js +13 -6
  41. package/lib/commands/up-common.js +80 -42
  42. package/lib/commands/up-dataplane.js +15 -14
  43. package/lib/commands/up-miso.js +15 -14
  44. package/lib/commands/upload.js +163 -0
  45. package/lib/commands/wizard-core.js +5 -4
  46. package/lib/core/diff.js +84 -9
  47. package/lib/core/key-generator.js +9 -12
  48. package/lib/core/secrets-docker-env.js +2 -2
  49. package/lib/core/secrets.js +3 -2
  50. package/lib/core/templates.js +2 -2
  51. package/lib/datasource/deploy.js +2 -1
  52. package/lib/deployment/deployer.js +76 -48
  53. package/lib/external-system/delete.js +0 -1
  54. package/lib/external-system/deploy-helpers.js +5 -6
  55. package/lib/external-system/deploy.js +7 -2
  56. package/lib/external-system/download-helpers.js +4 -4
  57. package/lib/external-system/download.js +11 -10
  58. package/lib/external-system/generator.js +19 -17
  59. package/lib/external-system/test.js +10 -15
  60. package/lib/generator/builders.js +1 -1
  61. package/lib/generator/external-controller-manifest.js +26 -29
  62. package/lib/generator/external-schema-utils.js +6 -18
  63. package/lib/generator/external.js +32 -27
  64. package/lib/generator/github.js +1 -1
  65. package/lib/generator/helpers.js +12 -19
  66. package/lib/generator/index.js +15 -15
  67. package/lib/generator/parse-image.js +35 -0
  68. package/lib/generator/split-readme.js +105 -0
  69. package/lib/generator/split-variables.js +149 -0
  70. package/lib/generator/split.js +86 -246
  71. package/lib/generator/wizard.js +46 -69
  72. package/lib/schema/application-schema.json +4 -4
  73. package/lib/schema/external-datasource.schema.json +5 -0
  74. package/lib/schema/external-system.schema.json +10 -0
  75. package/lib/utils/app-config-resolver.js +52 -0
  76. package/lib/utils/app-register-api.js +1 -1
  77. package/lib/utils/app-register-auth.js +1 -1
  78. package/lib/utils/app-register-config.js +16 -23
  79. package/lib/utils/app-register-validator.js +2 -2
  80. package/lib/utils/cli-utils.js +47 -3
  81. package/lib/utils/config-format.js +154 -0
  82. package/lib/utils/config-paths.js +19 -52
  83. package/lib/utils/config-tokens.js +1 -0
  84. package/lib/utils/docker-build.js +71 -94
  85. package/lib/utils/dockerfile-utils.js +1 -1
  86. package/lib/utils/env-copy.js +4 -4
  87. package/lib/utils/env-ports.js +2 -2
  88. package/lib/utils/error-formatter.js +1 -1
  89. package/lib/utils/error-formatters/validation-errors.js +1 -1
  90. package/lib/utils/external-readme.js +12 -5
  91. package/lib/utils/external-system-test-helpers.js +2 -0
  92. package/lib/utils/health-check.js +55 -66
  93. package/lib/utils/image-version.js +12 -21
  94. package/lib/utils/paths.js +39 -66
  95. package/lib/utils/port-resolver.js +8 -8
  96. package/lib/utils/schema-loader.js +22 -0
  97. package/lib/utils/schema-resolver.js +23 -33
  98. package/lib/utils/secrets-helpers.js +7 -7
  99. package/lib/utils/secrets-utils.js +10 -12
  100. package/lib/utils/template-helpers.js +13 -13
  101. package/lib/utils/token-manager.js +20 -2
  102. package/lib/utils/variable-transformer.js +2 -2
  103. package/lib/validation/validate-display.js +3 -4
  104. package/lib/validation/validate.js +33 -27
  105. package/lib/validation/validator.js +50 -30
  106. package/package.json +2 -1
  107. package/templates/README.md +1 -1
  108. package/templates/applications/README.md.hbs +3 -3
  109. package/templates/applications/miso-controller/env.template +3 -1
  110. package/templates/external-system/README.md.hbs +4 -4
  111. package/integration/hubspot/variables.yaml +0 -17
  112. /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
  113. /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
  114. /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
package/lib/app/show.js CHANGED
@@ -13,10 +13,9 @@
13
13
  'use strict';
14
14
 
15
15
  const path = require('path');
16
- const fs = require('fs');
17
- const yaml = require('js-yaml');
18
16
  const logger = require('../utils/logger');
19
- const { detectAppType } = require('../utils/paths');
17
+ const { detectAppType, resolveApplicationConfigPath } = require('../utils/paths');
18
+ const { loadConfigFile } = require('../utils/config-format');
20
19
  const generator = require('../generator');
21
20
  const { getConfig, normalizeControllerUrl } = require('../core/config');
22
21
  const { getOrRefreshDeviceToken } = require('../utils/token-manager');
@@ -24,6 +23,7 @@ const { resolveControllerUrl } = require('../utils/controller-url');
24
23
  const { resolveEnvironment } = require('../core/config');
25
24
  const { getApplication } = require('../api/applications.api');
26
25
  const {
26
+ getExternalSystem,
27
27
  getExternalSystemConfig,
28
28
  listOpenAPIFiles,
29
29
  listOpenAPIEndpoints
@@ -37,26 +37,23 @@ const { display: displayShow } = require('./show-display');
37
37
  const DEPLOYMENT_KEY_TRUNCATE_LEN = 12;
38
38
 
39
39
  /**
40
- * Load and parse variables.yaml from app path (no validation).
40
+ * Load application config from app path (no validation).
41
+ * Uses resolver + converter; supports application.yaml, application.json, or legacy variables.yaml.
41
42
  * @param {string} appPath - Application directory path
42
- * @returns {Object} Parsed variables
43
- * @throws {Error} If file not found or invalid YAML
43
+ * @returns {Object} Parsed config object
44
+ * @throws {Error} If config not found or invalid
44
45
  */
45
46
  function loadVariablesFromPath(appPath) {
46
- const variablesPath = path.join(appPath, 'variables.yaml');
47
- if (!fs.existsSync(variablesPath)) {
48
- throw new Error(`variables.yaml not found for app (path: ${variablesPath}). Use aifabrix validate to check.`);
49
- }
50
- const content = fs.readFileSync(variablesPath, 'utf8');
47
+ const configPath = resolveApplicationConfigPath(appPath);
51
48
  try {
52
- const parsed = yaml.load(content);
49
+ const parsed = loadConfigFile(configPath);
53
50
  if (!parsed || typeof parsed !== 'object') {
54
- throw new Error('variables.yaml is empty or invalid');
51
+ throw new Error('Application config is empty or invalid');
55
52
  }
56
53
  return parsed;
57
54
  } catch (error) {
58
- if (error.message.includes('variables.yaml')) throw error;
59
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
55
+ if (error.message.includes('Application config') || error.message.includes('not found')) throw error;
56
+ throw new Error(`Invalid application config: ${error.message}`);
60
57
  }
61
58
  }
62
59
 
@@ -207,9 +204,9 @@ function buildApplicationFromVariables(variables) {
207
204
  }
208
205
 
209
206
  /**
210
- * Build offline summary object from variables (for display and JSON).
211
- * @param {Object} variables - Parsed variables.yaml
212
- * @param {string} sourcePath - Path to variables.yaml for display
207
+ * Build offline summary object from application config (for display and JSON).
208
+ * @param {Object} variables - Parsed application config (application.yaml/application.json)
209
+ * @param {string} sourcePath - Path to application config for display
213
210
  * @returns {Object} Summary with application, roles, permissions, etc.
214
211
  */
215
212
  function buildOfflineSummary(variables, sourcePath) {
@@ -294,17 +291,57 @@ async function fetchOpenApiLists(dataplaneUrl, appKey, authConfig) {
294
291
  return { openapiFiles, openapiEndpoints };
295
292
  }
296
293
 
297
- function buildExternalSystemResult(configData, appKey, openapiFiles, openapiEndpoints) {
294
+ /**
295
+ * Normalize GET /api/v1/external/systems/{id} response to the entity object.
296
+ * Handles: (1) our API client shape { success, data }; (2) body wrapped in .data by dataplane.
297
+ * @param {Object} systemResponse - Raw response from getExternalSystem
298
+ * @returns {Object|null} Entity object (id, key, displayName, status, config, ...) or null
299
+ */
300
+ function normalizeExternalSystemResponse(systemResponse) {
301
+ if (!systemResponse) return null;
302
+ const body = systemResponse.data ?? systemResponse;
303
+ if (!body || typeof body !== 'object') return null;
304
+ return body.data ?? body;
305
+ }
306
+
307
+ function pickExternalDisplayName(res, system, application, appKey) {
308
+ return (res && res.displayName) || system.displayName || application.displayName || appKey;
309
+ }
310
+
311
+ function pickExternalType(res, config, system, application) {
312
+ return (res && res.type) || config.type || system.type || application.type || '—';
313
+ }
314
+
315
+ function pickExternalStatus(system, res) {
316
+ return system.status || (res && res.status) || '—';
317
+ }
318
+
319
+ function pickExternalVersion(res, config, system) {
320
+ return (res && res.version) ?? config.version ?? system.version ?? '—';
321
+ }
322
+
323
+ function resolveExternalSystemMeta(res, config, system, application, appKey) {
324
+ return {
325
+ displayName: pickExternalDisplayName(res, system, application, appKey),
326
+ type: pickExternalType(res, config, system, application),
327
+ status: pickExternalStatus(system, res),
328
+ version: pickExternalVersion(res, config, system)
329
+ };
330
+ }
331
+
332
+ function buildBaseExternalResult(configData, appKey, openapiFiles, openapiEndpoints, res) {
298
333
  const system = configData.system || configData;
299
334
  const dataSources = configData.dataSources || configData.dataSources || [];
300
335
  const application = configData.application || configData.app || {};
336
+ const config = res && res.config && typeof res.config === 'object' ? res.config : {};
337
+ const meta = resolveExternalSystemMeta(res, config, system, application, appKey);
301
338
  return {
302
339
  dataplaneUrl: null,
303
340
  systemKey: appKey,
304
- displayName: system.displayName || application.displayName || appKey,
305
- type: system.type || application.type || '—',
306
- status: system.status || '—',
307
- version: system.version || '—',
341
+ displayName: meta.displayName,
342
+ type: meta.type,
343
+ status: meta.status,
344
+ version: meta.version,
308
345
  dataSources: dataSources.map((ds) => ({
309
346
  key: ds.key,
310
347
  displayName: ds.displayName,
@@ -322,8 +359,37 @@ function buildExternalSystemResult(configData, appKey, openapiFiles, openapiEndp
322
359
  };
323
360
  }
324
361
 
362
+ function applySystemResponseToResult(result, res) {
363
+ result.dataplaneStatus = res.status;
364
+ if (res.credentialId !== undefined && res.credentialId !== null) result.credentialId = res.credentialId;
365
+ if (res.showOpenApiDocs !== undefined) result.showOpenApiDocs = res.showOpenApiDocs;
366
+ if (res.mcpServerUrl !== undefined && res.mcpServerUrl !== null) result.mcpServerUrl = res.mcpServerUrl;
367
+ if (res.apiDocumentUrl !== undefined && res.apiDocumentUrl !== null) result.apiDocumentUrl = res.apiDocumentUrl;
368
+ if (res.openApiDocsPageUrl !== undefined && res.openApiDocsPageUrl !== null) result.openApiDocsPageUrl = res.openApiDocsPageUrl;
369
+ }
370
+
371
+ /**
372
+ * Build external system result from config and optional ExternalSystemResponse.
373
+ * Dataplane GET /api/v1/external/systems/{id} returns: id, key, displayName, description,
374
+ * credentialId, config (optional type, description, dataSources), status, version,
375
+ * showOpenApiDocs, mcpServerUrl, apiDocumentUrl, openApiDocsPageUrl.
376
+ * @param {Object} configData - Config response (system, dataSources, application)
377
+ * @param {string} appKey - Application/system key
378
+ * @param {Array} openapiFiles - OpenAPI files list
379
+ * @param {Array} openapiEndpoints - OpenAPI endpoints list
380
+ * @param {Object} [systemResponse] - Optional GET /api/v1/external/systems/{id} response
381
+ * @returns {Object} Result with dataplaneStatus, credentialId, showOpenApiDocs, URLs when systemResponse present
382
+ */
383
+ function buildExternalSystemResult(configData, appKey, openapiFiles, openapiEndpoints, systemResponse) {
384
+ const res = normalizeExternalSystemResponse(systemResponse);
385
+ const result = buildBaseExternalResult(configData, appKey, openapiFiles, openapiEndpoints, res);
386
+ if (res) applySystemResponseToResult(result, res);
387
+ return result;
388
+ }
389
+
325
390
  /**
326
391
  * Fetch external system section from dataplane (for --online and type external).
392
+ * Calls getExternalSystem (ExternalSystemResponse) and getExternalSystemConfig, then merges into one result.
327
393
  * @param {string} dataplaneUrl - Dataplane URL
328
394
  * @param {string} appKey - Application key (system key)
329
395
  * @param {Object} authConfig - Auth config
@@ -331,11 +397,19 @@ function buildExternalSystemResult(configData, appKey, openapiFiles, openapiEndp
331
397
  */
332
398
  async function fetchExternalSystemFromDataplane(dataplaneUrl, appKey, authConfig) {
333
399
  try {
400
+ let systemResponse = null;
401
+ try {
402
+ systemResponse = await getExternalSystem(dataplaneUrl, appKey, authConfig);
403
+ } catch {
404
+ // optional: continue with config only
405
+ }
334
406
  const configRes = await getExternalSystemConfig(dataplaneUrl, appKey, authConfig);
335
407
  const data = configRes.data || configRes;
336
408
  const configData = data.data || data;
337
409
  const { openapiFiles, openapiEndpoints } = await fetchOpenApiLists(dataplaneUrl, appKey, authConfig);
338
- const result = buildExternalSystemResult(configData, appKey, openapiFiles, openapiEndpoints);
410
+ const result = buildExternalSystemResult(
411
+ configData, appKey, openapiFiles, openapiEndpoints, systemResponse
412
+ );
339
413
  result.dataplaneUrl = dataplaneUrl;
340
414
  return result;
341
415
  } catch (error) {
@@ -405,6 +479,7 @@ function pickAppCfg(key, app, cfg, fallback) {
405
479
  function buildApplicationFromAppCfg(app, cfg, portalInputConfigurations) {
406
480
  const deploymentKey = cfg.deploymentKey ?? app.deploymentKey;
407
481
  const truncatedDeploy = truncateDeploymentKey(deploymentKey) || (deploymentKey ?? '—');
482
+ const version = pickAppCfg('version', app, cfg, undefined);
408
483
  const application = {
409
484
  key: pickAppCfg('key', app, cfg, '—'),
410
485
  displayName: pickAppCfg('displayName', app, cfg, '—'),
@@ -425,6 +500,7 @@ function buildApplicationFromAppCfg(app, cfg, portalInputConfigurations) {
425
500
  portalInputConfigurations,
426
501
  databases: resolveDatabasesFromAppCfg(app, cfg)
427
502
  };
503
+ if (version !== undefined && version !== null) application.version = version;
428
504
  return application;
429
505
  }
430
506
 
@@ -483,11 +559,11 @@ function buildOnlineSummary(apiApp, controllerUrl, externalSystem) {
483
559
  }
484
560
 
485
561
  /**
486
- * Run show in offline mode: generate manifest (same as aifabrix json) and use it; else fall back to variables.yaml.
562
+ * Run show in offline mode: generate manifest (same as aifabrix json) and use it; else fall back to application config.
487
563
  * @param {string} appKey - Application key
488
564
  * @param {boolean} json - Output as JSON
489
565
  * @param {boolean} [permissionsOnly] - When true, output only permissions
490
- * @throws {Error} If variables.yaml not found or invalid YAML
566
+ * @throws {Error} If application config not found or invalid
491
567
  */
492
568
  async function runOffline(appKey, json, permissionsOnly) {
493
569
  let summary;
@@ -498,9 +574,9 @@ async function runOffline(appKey, json, permissionsOnly) {
498
574
  summary = buildOfflineSummaryFromDeployJson(deployment, sourcePath);
499
575
  } catch (_err) {
500
576
  const { appPath } = await detectAppType(appKey);
501
- const variablesPath = path.join(appPath, 'variables.yaml');
577
+ const configPath = resolveApplicationConfigPath(appPath);
502
578
  const variables = loadVariablesFromPath(appPath);
503
- const sourcePath = path.relative(process.cwd(), variablesPath) || variablesPath;
579
+ const sourcePath = path.relative(process.cwd(), configPath) || configPath;
504
580
  summary = buildOfflineSummary(variables, sourcePath);
505
581
  }
506
582
 
@@ -573,27 +649,29 @@ function outputOnlineJson(summary, permissionsOnly) {
573
649
  logger.log(JSON.stringify(out, null, 2));
574
650
  return;
575
651
  }
652
+ const app = summary.application;
576
653
  const out = {
577
654
  source: summary.source,
578
655
  controllerUrl: summary.controllerUrl,
579
656
  appKey: summary.appKey,
580
657
  application: {
581
- key: summary.application.key,
582
- displayName: summary.application.displayName,
583
- description: summary.application.description,
584
- type: summary.application.type,
585
- status: summary.application.status,
586
- url: summary.application.url,
587
- internalUrl: summary.application.internalUrl,
588
- port: summary.application.port,
589
- configuration: summary.application.configuration,
590
- roles: summary.application.roles,
591
- permissions: summary.application.permissions,
592
- authentication: summary.application.authentication,
593
- portalInputConfigurations: summary.application.portalInputConfigurations,
594
- databases: summary.application.databases
658
+ key: app.key,
659
+ displayName: app.displayName,
660
+ description: app.description,
661
+ type: app.type,
662
+ status: app.status,
663
+ url: app.url,
664
+ internalUrl: app.internalUrl,
665
+ port: app.port,
666
+ configuration: app.configuration,
667
+ roles: app.roles,
668
+ permissions: app.permissions,
669
+ authentication: app.authentication,
670
+ portalInputConfigurations: app.portalInputConfigurations,
671
+ databases: app.databases
595
672
  }
596
673
  };
674
+ if (app.version !== undefined && app.version !== null) out.application.version = app.version;
597
675
  if (summary.externalSystem !== undefined && summary.externalSystem !== null) {
598
676
  out.externalSystem = summary.externalSystem && summary.externalSystem.error
599
677
  ? { error: summary.externalSystem.error }
@@ -618,7 +696,9 @@ async function runOnline(appKey, json, permissionsOnly) {
618
696
  const response = await getApplication(controllerUrl, appKey, authConfig);
619
697
  const apiApp = ensureApplicationResponse(response, appKey, authResult);
620
698
  const appData = apiApp.data || apiApp;
621
- const externalSystem = appData.type === 'external'
699
+ const cfg = appData.configuration && typeof appData.configuration === 'object' ? appData.configuration : {};
700
+ const isExternalApp = appData.type === 'external' || cfg.type === 'external';
701
+ const externalSystem = isExternalApp
622
702
  ? await fetchExternalSystemForOnline(controllerUrl, appKey, authConfig)
623
703
  : null;
624
704
  const summary = buildOnlineSummary(apiApp, authResult.actualControllerUrl, externalSystem);
@@ -15,8 +15,9 @@ const fsSync = require('fs');
15
15
  const path = require('path');
16
16
  const paths = require('../utils/paths');
17
17
  const { detectAppType, getProjectRoot } = require('../utils/paths');
18
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
19
+ const { loadConfigFile } = require('../utils/config-format');
18
20
  const chalk = require('chalk');
19
- const yaml = require('js-yaml');
20
21
  const secrets = require('../core/secrets');
21
22
  const config = require('../core/config');
22
23
  const logger = require('../utils/logger');
@@ -27,30 +28,22 @@ const { buildDevImageName } = require('../utils/image-name');
27
28
  const buildHelpers = require('../utils/build-helpers');
28
29
 
29
30
  /**
30
- * Loads variables.yaml configuration for an application
31
+ * Loads application config for an application
31
32
  * @param {string} appName - Application name
32
33
  * @returns {Promise<Object>} Configuration object
33
34
  * @throws {Error} If file cannot be loaded or parsed
34
35
  */
35
36
  async function loadVariablesYaml(appName) {
36
- // Detect app type and get correct path (integration or builder)
37
37
  const { appPath } = await detectAppType(appName);
38
- const variablesPath = path.join(appPath, 'variables.yaml');
39
-
40
- if (!fsSync.existsSync(variablesPath)) {
41
- throw new Error(`Configuration not found. Run 'aifabrix create ${appName}' first.`);
42
- }
43
-
44
- const content = fsSync.readFileSync(variablesPath, 'utf8');
45
- let variables;
46
-
47
38
  try {
48
- variables = yaml.load(content);
39
+ const configPath = resolveApplicationConfigPath(appPath);
40
+ return loadConfigFile(configPath);
49
41
  } catch (error) {
50
- throw new Error(`Invalid YAML syntax in variables.yaml: ${error.message}`);
42
+ if (error.message && error.message.includes('not found')) {
43
+ throw new Error(`Configuration not found. Run 'aifabrix create ${appName}' first.`);
44
+ }
45
+ throw new Error(`Invalid application config: ${error.message}`);
51
46
  }
52
-
53
- return variables;
54
47
  }
55
48
 
56
49
  /**
@@ -115,7 +108,7 @@ function detectLanguage(appPath) {
115
108
  * @function generateDockerfile
116
109
  * @param {string} appNameOrPath - Application name or path (backward compatibility)
117
110
  * @param {string} language - Target language ('typescript', 'python')
118
- * @param {Object} config - Application configuration from variables.yaml
111
+ * @param {Object} config - Application configuration from application.yaml
119
112
  * @returns {Promise<string>} Path to generated Dockerfile
120
113
  * @throws {Error} If template generation fails
121
114
  *
@@ -326,7 +319,7 @@ function prepareBuildContext(buildConfig, devDir) {
326
319
  throw new Error(
327
320
  `Build context path does not exist: ${contextPath}\n` +
328
321
  `Expected dev directory: ${devDir}\n` +
329
- 'Please ensure files were copied correctly or update the context in variables.yaml.'
322
+ 'Please ensure files were copied correctly or update the context in application.yaml.'
330
323
  );
331
324
  }
332
325
 
@@ -91,11 +91,7 @@ async function handleCreateCommand(appName, options) {
91
91
  }
92
92
  }
93
93
 
94
- /**
95
- * Sets up application lifecycle commands
96
- * @param {Command} program - Commander program instance
97
- */
98
- function setupAppCommands(program) {
94
+ function setupCreateCommand(program) {
99
95
  program.command('create <app>')
100
96
  .description('Create new application with configuration files')
101
97
  .option('-p, --port <port>', 'Application port', '3000')
@@ -124,13 +120,10 @@ function setupAppCommands(program) {
124
120
  process.exit(1);
125
121
  }
126
122
  });
123
+ }
127
124
 
128
- program.command('wizard [appName]')
129
- .description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
130
- .option('-a, --app <app>', 'Application name (synonym for positional appName)')
131
- .option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
132
- .option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
133
- .addHelpText('after', `
125
+ function setupWizardCommand(program) {
126
+ const wizardHelp = `
134
127
  Examples:
135
128
  $ aifabrix wizard Run interactively (mode first, then prompts)
136
129
  $ aifabrix wizard my-integration Load wizard.yaml if present → show summary → "Run with saved config?" or start from step 1
@@ -141,7 +134,13 @@ Examples:
141
134
  Config path: When appName is provided, integration/<appName>/wizard.yaml is used for load/save and error.log.
142
135
  To change settings after a run, edit that file and run "aifabrix wizard <app>" again.
143
136
  Headless config must include: appName, mode (create-system|add-datasource), source (type + filePath/url/platform).
144
- See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
137
+ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`;
138
+ program.command('wizard [appName]')
139
+ .description('Create or extend external systems (OpenAPI, MCP, or known platforms like HubSpot) via guided steps or a config file')
140
+ .option('-a, --app <app>', 'Application name (synonym for positional appName)')
141
+ .option('--config <file>', 'Run headless using a wizard.yaml file (appName, mode, source, credential, preferences)')
142
+ .option('--silent', 'Run with saved integration/<app>/wizard.yaml only; no prompts (requires app name and existing wizard.yaml)')
143
+ .addHelpText('after', wizardHelp)
145
144
  .action(async(positionalAppName, options) => {
146
145
  try {
147
146
  const appName = positionalAppName || options.app;
@@ -153,12 +152,14 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
153
152
  process.exit(1);
154
153
  }
155
154
  });
155
+ }
156
156
 
157
+ function setupBuildRunLogsDownCommands(program) {
157
158
  program.command('build <app>')
158
159
  .description('Build container image (auto-detects runtime)')
159
160
  .option('-l, --language <lang>', 'Override language detection')
160
161
  .option('-f, --force-template', 'Force rebuild from template')
161
- .option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in variables.yaml to match for deploy.')
162
+ .option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in application.yaml to match for deploy.')
162
163
  .action(async(appName, options) => {
163
164
  try {
164
165
  const imageTag = await app.buildApp(appName, options);
@@ -173,7 +174,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
173
174
  .description('Run application locally')
174
175
  .option('-p, --port <port>', 'Override local port')
175
176
  .option('-d, --debug', 'Enable debug output with detailed container information')
176
- .option('-t, --tag <tag>', 'Image tag to run (e.g. v1.0.0); overrides variables.yaml image.tag')
177
+ .option('-t, --tag <tag>', 'Image tag to run (e.g. v1.0.0); overrides application.yaml image.tag')
177
178
  .action(async(appName, options) => {
178
179
  try {
179
180
  await app.runApp(appName, options);
@@ -193,11 +194,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
193
194
  const { runAppLogs } = require('../commands/app-logs');
194
195
  const tailNum = parseInt(options.tail, 10);
195
196
  const level = options.level !== undefined && options.level !== null && options.level !== '' ? String(options.level).trim() : undefined;
196
- await runAppLogs(appName, {
197
- follow: options.f,
198
- tail: Number.isNaN(tailNum) ? 100 : tailNum,
199
- level
200
- });
197
+ await runAppLogs(appName, { follow: options.f, tail: Number.isNaN(tailNum) ? 100 : tailNum, level });
201
198
  } catch (error) {
202
199
  handleCommandError(error, 'logs');
203
200
  process.exit(1);
@@ -216,10 +213,12 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
216
213
  process.exit(1);
217
214
  }
218
215
  });
216
+ }
219
217
 
218
+ function setupPushDeployDockerfileCommands(program) {
220
219
  program.command('push <app>')
221
220
  .description('Push image to Azure Container Registry')
222
- .option('-r, --registry <registry>', 'ACR registry URL (overrides variables.yaml)')
221
+ .option('-r, --registry <registry>', 'ACR registry URL (overrides application.yaml)')
223
222
  .option('-t, --tag <tag>', 'Image tag(s) - comma-separated for multiple (default: latest)')
224
223
  .action(async(appName, options) => {
225
224
  try {
@@ -232,21 +231,18 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
232
231
 
233
232
  program.command('deploy <app>')
234
233
  .description('Deploy to Azure via Miso Controller')
235
- .option('--deployment <target>', 'Deployment target: \'local\' (send manifest to controller, then run app locally) or \'cloud\' (deploy via Miso Controller only)', 'cloud')
236
- .option('--type <type>', 'Application type: external to deploy from integration/<app> (no app register needed)')
234
+ .option('--local', 'Send manifest to controller then run app locally (app: same as aifabrix run <app>; external: restart dataplane)')
237
235
  .option('--client-id <id>', 'Client ID (overrides config)')
238
236
  .option('--client-secret <secret>', 'Client Secret (overrides config)')
239
237
  .option('--poll', 'Poll for deployment status', true)
240
238
  .option('--no-poll', 'Do not poll for status')
241
239
  .action(async(appName, options) => {
242
240
  try {
243
- const target = (options.deployment || 'cloud').toLowerCase();
244
- if (target !== 'local' && target !== 'cloud') {
245
- throw new Error('Deployment target must be \'local\' or \'cloud\'');
246
- }
247
- await app.deployApp(appName, options);
248
- if (target === 'local') {
249
- await app.runApp(appName, options);
241
+ const opts = { ...options, local: !!options.local };
242
+ const outcome = await app.deployApp(appName, opts);
243
+ if (opts.local && outcome) {
244
+ if (outcome.usedExternalDeploy) await app.restartApp('dataplane');
245
+ else await app.runApp(appName, opts);
250
246
  }
251
247
  } catch (error) {
252
248
  handleCommandError(error, 'deploy');
@@ -270,4 +266,15 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
270
266
  });
271
267
  }
272
268
 
269
+ /**
270
+ * Sets up application lifecycle commands
271
+ * @param {Command} program - Commander program instance
272
+ */
273
+ function setupAppCommands(program) {
274
+ setupCreateCommand(program);
275
+ setupWizardCommand(program);
276
+ setupBuildRunLogsDownCommands(program);
277
+ setupPushDeployDockerfileCommands(program);
278
+ }
279
+
273
280
  module.exports = { setupAppCommands };
@@ -14,11 +14,7 @@ const { handleLogout } = require('../commands/logout');
14
14
  const { handleAuthStatus } = require('../commands/auth-status');
15
15
  const { handleAuthConfig } = require('../commands/auth-config');
16
16
 
17
- /**
18
- * Sets up authentication commands
19
- * @param {Command} program - Commander program instance
20
- */
21
- function setupAuthCommands(program) {
17
+ function setupLoginCommand(program) {
22
18
  program.command('login')
23
19
  .description('Authenticate with Miso Controller')
24
20
  .option('-c, --controller <url>', 'Controller URL (default: from config or developer ID, e.g. http://localhost:3000)')
@@ -37,7 +33,9 @@ function setupAuthCommands(program) {
37
33
  process.exit(1);
38
34
  }
39
35
  });
36
+ }
40
37
 
38
+ function setupLogoutCommand(program) {
41
39
  program.command('logout')
42
40
  .description('Clear authentication tokens')
43
41
  .option('-c, --controller <url>', 'Clear device tokens for specific controller')
@@ -51,7 +49,9 @@ function setupAuthCommands(program) {
51
49
  process.exit(1);
52
50
  }
53
51
  });
52
+ }
54
53
 
54
+ function setupAuthSubcommands(program) {
55
55
  const authStatusHandler = async(options) => {
56
56
  try {
57
57
  await handleAuthStatus(options);
@@ -60,18 +60,11 @@ function setupAuthCommands(program) {
60
60
  process.exit(1);
61
61
  }
62
62
  };
63
-
64
- const auth = program
65
- .command('auth')
66
- .description('Authentication commands');
67
-
68
- auth
69
- .command('status')
63
+ const auth = program.command('auth').description('Authentication commands');
64
+ auth.command('status')
70
65
  .description('Display authentication status for current controller and environment')
71
66
  .action(authStatusHandler);
72
-
73
- auth
74
- .command('config')
67
+ auth.command('config')
75
68
  .description('Configure authentication settings (controller, environment)')
76
69
  .option('--set-controller <url>', 'Set default controller URL')
77
70
  .option('--set-environment <env>', 'Set default environment')
@@ -85,4 +78,14 @@ function setupAuthCommands(program) {
85
78
  });
86
79
  }
87
80
 
81
+ /**
82
+ * Sets up authentication commands
83
+ * @param {Command} program - Commander program instance
84
+ */
85
+ function setupAuthCommands(program) {
86
+ setupLoginCommand(program);
87
+ setupLogoutCommand(program);
88
+ setupAuthSubcommands(program);
89
+ }
90
+
88
91
  module.exports = { setupAuthCommands };
@@ -24,14 +24,16 @@ function setupCredentialDeploymentCommands(program) {
24
24
 
25
25
  credential
26
26
  .command('list')
27
- .description('List credentials from controller/dataplane (GET /api/v1/credential)')
27
+ .description('List credentials from Dataplane (GET /api/v1/credential). Controller does not expose this endpoint.')
28
28
  .option('--controller <url>', 'Controller URL (default: from config)')
29
+ .option('--dataplane <url>', 'Dataplane URL (default: resolved from controller + environment)')
29
30
  .option('--active-only', 'List only active credentials')
30
31
  .option('--page-size <n>', 'Items per page', '50')
31
32
  .action(async(options) => {
32
33
  try {
33
34
  const opts = {
34
35
  controller: options.controller,
36
+ dataplane: options.dataplane,
35
37
  activeOnly: options.activeOnly,
36
38
  pageSize: parseInt(options.pageSize, 10) || 50
37
39
  };