@aifabrix/builder 2.39.2 → 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 (116) 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 +3 -3
  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 +38 -28
  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 +165 -10
  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/deployment-rules.yaml +0 -4
  74. package/lib/schema/external-datasource.schema.json +5 -0
  75. package/lib/schema/external-system.schema.json +10 -0
  76. package/lib/utils/app-config-resolver.js +52 -0
  77. package/lib/utils/app-register-api.js +1 -1
  78. package/lib/utils/app-register-auth.js +1 -1
  79. package/lib/utils/app-register-config.js +16 -23
  80. package/lib/utils/app-register-display.js +22 -3
  81. package/lib/utils/app-register-validator.js +2 -2
  82. package/lib/utils/cli-utils.js +47 -3
  83. package/lib/utils/config-format.js +154 -0
  84. package/lib/utils/config-paths.js +19 -52
  85. package/lib/utils/config-tokens.js +1 -0
  86. package/lib/utils/docker-build.js +71 -94
  87. package/lib/utils/dockerfile-utils.js +1 -1
  88. package/lib/utils/env-copy.js +4 -4
  89. package/lib/utils/env-ports.js +2 -2
  90. package/lib/utils/error-formatter.js +1 -1
  91. package/lib/utils/error-formatters/validation-errors.js +1 -1
  92. package/lib/utils/external-readme.js +12 -5
  93. package/lib/utils/external-system-test-helpers.js +2 -0
  94. package/lib/utils/health-check.js +55 -66
  95. package/lib/utils/image-version.js +12 -21
  96. package/lib/utils/paths.js +39 -66
  97. package/lib/utils/port-resolver.js +8 -8
  98. package/lib/utils/schema-loader.js +22 -0
  99. package/lib/utils/schema-resolver.js +23 -33
  100. package/lib/utils/secrets-helpers.js +7 -7
  101. package/lib/utils/secrets-utils.js +10 -12
  102. package/lib/utils/template-helpers.js +13 -13
  103. package/lib/utils/token-manager.js +20 -2
  104. package/lib/utils/variable-transformer.js +2 -2
  105. package/lib/validation/validate-display.js +3 -4
  106. package/lib/validation/validate.js +33 -27
  107. package/lib/validation/validator.js +50 -30
  108. package/package.json +2 -1
  109. package/templates/README.md +1 -1
  110. package/templates/applications/README.md.hbs +3 -3
  111. package/templates/applications/miso-controller/env.template +3 -1
  112. package/templates/external-system/README.md.hbs +4 -4
  113. package/integration/hubspot/variables.yaml +0 -17
  114. /package/templates/applications/dataplane/{variables.yaml → application.yaml} +0 -0
  115. /package/templates/applications/keycloak/{variables.yaml → application.yaml} +0 -0
  116. /package/templates/applications/miso-controller/{variables.yaml → application.yaml} +0 -0
@@ -8,11 +8,7 @@
8
8
 
9
9
  const { handleCommandError } = require('../utils/cli-utils');
10
10
 
11
- /**
12
- * Sets up external system commands
13
- * @param {Command} program - Commander program instance
14
- */
15
- function setupExternalSystemCommands(program) {
11
+ function setupDownloadCommand(program) {
16
12
  program.command('download <system-key>')
17
13
  .description('Download external system from dataplane to local development structure')
18
14
  .option('--dry-run', 'Show what would be downloaded without actually downloading')
@@ -25,17 +21,32 @@ function setupExternalSystemCommands(program) {
25
21
  process.exit(1);
26
22
  }
27
23
  });
24
+ }
25
+
26
+ function setupUploadCommand(program) {
27
+ program.command('upload <system-key>')
28
+ .description('Upload external system to dataplane (upload → validate → publish; no controller deploy)')
29
+ .option('--dry-run', 'Validate and build payload only; no API calls')
30
+ .option('--dataplane <url>', 'Dataplane URL (default: discovered from controller)')
31
+ .action(async(systemKey, options) => {
32
+ try {
33
+ const upload = require('../commands/upload');
34
+ await upload.uploadExternalSystem(systemKey, options);
35
+ } catch (error) {
36
+ handleCommandError(error, 'upload');
37
+ process.exit(1);
38
+ }
39
+ });
40
+ }
28
41
 
42
+ function setupDeleteCommand(program) {
29
43
  program.command('delete <system-key>')
30
44
  .description('Delete external system from dataplane (also deletes all associated datasources)')
31
- .option('--type <type>', 'Application type (external) - required for external systems')
45
+ .option('--type <type>', 'Application type (default: external; use "external" to target integration/<app>)')
32
46
  .option('--yes', 'Skip confirmation prompt')
33
47
  .option('--force', 'Skip confirmation prompt (alias for --yes)')
34
48
  .action(async(systemKey, options) => {
35
49
  try {
36
- if (options.type !== 'external') {
37
- throw new Error('Delete command for external systems requires --type external');
38
- }
39
50
  const externalDelete = require('../external-system/delete');
40
51
  await externalDelete.deleteExternalSystem(systemKey, options);
41
52
  } catch (error) {
@@ -43,7 +54,9 @@ function setupExternalSystemCommands(program) {
43
54
  process.exit(1);
44
55
  }
45
56
  });
57
+ }
46
58
 
59
+ function setupExternalSystemTestCommands(program) {
47
60
  program.command('test <app>')
48
61
  .description('Run unit tests for external system (local validation, no API calls)')
49
62
  .option('-d, --datasource <key>', 'Test specific datasource only')
@@ -53,15 +66,12 @@ function setupExternalSystemCommands(program) {
53
66
  const test = require('../external-system/test');
54
67
  const results = await test.testExternalSystem(appName, options);
55
68
  test.displayTestResults(results, options.verbose);
56
- if (!results.valid) {
57
- process.exit(1);
58
- }
69
+ if (!results.valid) process.exit(1);
59
70
  } catch (error) {
60
71
  handleCommandError(error, 'test');
61
72
  process.exit(1);
62
73
  }
63
74
  });
64
-
65
75
  program.command('test-integration <app>')
66
76
  .description('Run integration tests via dataplane pipeline API')
67
77
  .option('-d, --datasource <key>', 'Test specific datasource only')
@@ -74,9 +84,7 @@ function setupExternalSystemCommands(program) {
74
84
  const test = require('../external-system/test');
75
85
  const results = await test.testExternalSystemIntegration(appName, options);
76
86
  test.displayIntegrationTestResults(results, options.verbose);
77
- if (!results.success) {
78
- process.exit(1);
79
- }
87
+ if (!results.success) process.exit(1);
80
88
  } catch (error) {
81
89
  handleCommandError(error, 'test-integration');
82
90
  process.exit(1);
@@ -84,4 +92,15 @@ function setupExternalSystemCommands(program) {
84
92
  });
85
93
  }
86
94
 
95
+ /**
96
+ * Sets up external system commands
97
+ * @param {Command} program - Commander program instance
98
+ */
99
+ function setupExternalSystemCommands(program) {
100
+ setupDownloadCommand(program);
101
+ setupUploadCommand(program);
102
+ setupDeleteCommand(program);
103
+ setupExternalSystemTestCommands(program);
104
+ }
105
+
87
106
  module.exports = { setupExternalSystemCommands };
@@ -48,11 +48,7 @@ async function runUpInfraCommand(options) {
48
48
  await infra.startInfra(developerId, { traefik: useTraefik });
49
49
  }
50
50
 
51
- /**
52
- * Sets up infrastructure commands
53
- * @param {Command} program - Commander program instance
54
- */
55
- function setupInfraCommands(program) {
51
+ function setupUpInfraCommand(program) {
56
52
  program.command('up-infra')
57
53
  .description('Start local infrastructure: Postgres, Redis, optional Traefik')
58
54
  .option('-d, --developer <id>', 'Set developer ID and start infrastructure')
@@ -66,7 +62,9 @@ function setupInfraCommands(program) {
66
62
  process.exit(1);
67
63
  }
68
64
  });
65
+ }
69
66
 
67
+ function setupUpPlatformCommand(program) {
70
68
  program.command('up-platform')
71
69
  .description('Start platform (Keycloak, Miso Controller, Dataplane) from community images; infra must be up')
72
70
  .option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
@@ -81,7 +79,9 @@ function setupInfraCommands(program) {
81
79
  process.exit(1);
82
80
  }
83
81
  });
82
+ }
84
83
 
84
+ function setupUpMisoCommand(program) {
85
85
  program.command('up-miso')
86
86
  .description('Install keycloak and miso-controller from images (no build). Infra must be up. For dataplane use up-dataplane. Uses auto-generated secrets for testing.')
87
87
  .option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
@@ -95,7 +95,9 @@ function setupInfraCommands(program) {
95
95
  process.exit(1);
96
96
  }
97
97
  });
98
+ }
98
99
 
100
+ function setupUpDataplaneCommand(program) {
99
101
  program.command('up-dataplane')
100
102
  .description('Register, deploy, then run dataplane app locally in dev (always local deployment; requires login, environment must be dev)')
101
103
  .option('-r, --registry <url>', 'Override registry for dataplane image')
@@ -109,7 +111,9 @@ function setupInfraCommands(program) {
109
111
  process.exit(1);
110
112
  }
111
113
  });
114
+ }
112
115
 
116
+ function setupDownInfraCommand(program) {
113
117
  program.command('down-infra [app]')
114
118
  .description('Stop and remove local infrastructure services or a specific application')
115
119
  .option('-v, --volumes', 'Remove volumes (deletes all data)')
@@ -118,34 +122,30 @@ function setupInfraCommands(program) {
118
122
  if (typeof appName === 'string' && appName.trim().length > 0) {
119
123
  await appLib.downApp(appName, { volumes: !!options.volumes });
120
124
  } else {
121
- if (options.volumes) {
122
- await infra.stopInfraWithVolumes();
123
- } else {
124
- await infra.stopInfra();
125
- }
125
+ if (options.volumes) await infra.stopInfraWithVolumes();
126
+ else await infra.stopInfra();
126
127
  }
127
128
  } catch (error) {
128
129
  handleCommandError(error, 'down-infra');
129
130
  process.exit(1);
130
131
  }
131
132
  });
133
+ }
132
134
 
135
+ function setupDoctorCommand(program) {
133
136
  program.command('doctor')
134
137
  .description('Check environment and configuration')
135
138
  .action(async() => {
136
139
  try {
137
140
  const result = await validator.checkEnvironment();
138
141
  logger.log('\n🔍 AI Fabrix Environment Check\n');
139
-
140
142
  logger.log(`Docker: ${result.docker === 'ok' ? '✅ Running' : '❌ Not available'}`);
141
143
  logger.log(`Ports: ${result.ports === 'ok' ? '✅ Available' : '⚠️ Some ports in use'}`);
142
144
  logger.log(`Secrets: ${result.secrets === 'ok' ? '✅ Configured' : '❌ Missing'}`);
143
-
144
145
  if (result.recommendations.length > 0) {
145
146
  logger.log('\n📋 Recommendations:');
146
147
  result.recommendations.forEach(rec => logger.log(` • ${rec}`));
147
148
  }
148
-
149
149
  if (result.docker === 'ok') {
150
150
  try {
151
151
  const health = await infra.checkInfraHealth();
@@ -158,37 +158,35 @@ function setupInfraCommands(program) {
158
158
  logger.log('\n🏥 Infrastructure: Not running');
159
159
  }
160
160
  }
161
-
162
161
  logger.log('');
163
162
  } catch (error) {
164
163
  handleCommandError(error, 'doctor');
165
164
  process.exit(1);
166
165
  }
167
166
  });
167
+ }
168
168
 
169
+ function setupStatusCommand(program) {
169
170
  program.command('status')
170
171
  .description('Show detailed infrastructure service status and running applications')
171
172
  .action(async() => {
172
173
  try {
173
174
  const status = await infra.getInfraStatus();
174
175
  logger.log('\n📊 Infrastructure Status\n');
175
-
176
176
  Object.entries(status).forEach(([service, info]) => {
177
- const normalizedStatus = String(info.status).trim().toLowerCase();
178
- const icon = normalizedStatus === 'running' ? '✅' : '❌';
177
+ const icon = String(info.status).trim().toLowerCase() === 'running' ? '✅' : '❌';
179
178
  logger.log(`${icon} ${service}:`);
180
179
  logger.log(` Status: ${info.status}`);
181
180
  logger.log(` Port: ${info.port}`);
182
181
  logger.log(` URL: ${info.url}`);
183
182
  logger.log('');
184
183
  });
185
-
186
184
  const apps = await infra.getAppStatus();
187
185
  if (apps.length > 0) {
188
186
  logger.log('📱 Running Applications\n');
189
187
  apps.forEach((appInfo) => {
190
- const normalizedStatus = String(appInfo.status).trim().toLowerCase();
191
- const icon = normalizedStatus.includes('running') || normalizedStatus.includes('up') ? '✅' : '❌';
188
+ const s = String(appInfo.status).trim().toLowerCase();
189
+ const icon = s.includes('running') || s.includes('up') ? '✅' : '❌';
192
190
  logger.log(`${icon} ${appInfo.name}:`);
193
191
  logger.log(` Container: ${appInfo.container}`);
194
192
  logger.log(` Port: ${appInfo.port}`);
@@ -202,13 +200,22 @@ function setupInfraCommands(program) {
202
200
  process.exit(1);
203
201
  }
204
202
  });
203
+ }
205
204
 
205
+ const INFRA_SERVICES = ['postgres', 'redis', 'pgadmin', 'redis-commander', 'traefik'];
206
+
207
+ function setupRestartCommand(program) {
206
208
  program.command('restart <service>')
207
- .description('Restart a specific infrastructure service')
209
+ .description('Restart an infrastructure service or a Docker application (builder/<app>)')
208
210
  .action(async(service) => {
209
211
  try {
210
- await infra.restartService(service);
211
- logger.log(`✅ ${service} service restarted successfully`);
212
+ if (INFRA_SERVICES.includes(service)) {
213
+ await infra.restartService(service);
214
+ logger.log(`✅ ${service} service restarted successfully`);
215
+ } else {
216
+ await appLib.restartApp(service);
217
+ logger.log(`✅ ${service} restarted successfully`);
218
+ }
212
219
  } catch (error) {
213
220
  handleCommandError(error, 'restart');
214
221
  process.exit(1);
@@ -216,4 +223,19 @@ function setupInfraCommands(program) {
216
223
  });
217
224
  }
218
225
 
226
+ /**
227
+ * Sets up infrastructure commands
228
+ * @param {Command} program - Commander program instance
229
+ */
230
+ function setupInfraCommands(program) {
231
+ setupUpInfraCommand(program);
232
+ setupUpPlatformCommand(program);
233
+ setupUpMisoCommand(program);
234
+ setupUpDataplaneCommand(program);
235
+ setupDownInfraCommand(program);
236
+ setupDoctorCommand(program);
237
+ setupStatusCommand(program);
238
+ setupRestartCommand(program);
239
+ }
240
+
219
241
  module.exports = { setupInfraCommands };
@@ -12,9 +12,21 @@ const chalk = require('chalk');
12
12
  const secrets = require('../core/secrets');
13
13
  const generator = require('../generator');
14
14
  const logger = require('../utils/logger');
15
- const { handleCommandError } = require('../utils/cli-utils');
15
+ const { handleCommandError, logOfflinePathWhenType } = require('../utils/cli-utils');
16
16
  const { detectAppType, getDeployJsonPath } = require('../utils/paths');
17
17
 
18
+ /**
19
+ * Resolve app path and type for split-json (integration first, then builder).
20
+ *
21
+ * @param {string} appName - Application name
22
+ * @param {Object} [_options] - Command options (reserved)
23
+ * @returns {Promise<{appPath: string, appType: string}>}
24
+ */
25
+ async function resolveSplitJsonApp(appName, _options) {
26
+ const { appPath, appType } = await detectAppType(appName);
27
+ return { appPath, appType };
28
+ }
29
+
18
30
  /**
19
31
  * Handles split-json command logic
20
32
  * @async
@@ -23,15 +35,22 @@ const { detectAppType, getDeployJsonPath } = require('../utils/paths');
23
35
  * @returns {Promise<Object>} Paths to generated files
24
36
  */
25
37
  async function handleSplitJsonCommand(appName, options) {
26
- const { appPath, appType } = await detectAppType(appName, options);
38
+ const { appPath, appType } = await resolveSplitJsonApp(appName, options);
39
+ logOfflinePathWhenType(appPath);
27
40
 
28
41
  const outputDir = options.output || appPath;
29
42
  if (appType === 'external') {
43
+ const deployJsonPath = getDeployJsonPath(appName, 'external', true);
44
+ if (fs.existsSync(deployJsonPath)) {
45
+ return generator.splitDeployJson(deployJsonPath, outputDir);
46
+ }
30
47
  const schemaPath = path.join(appPath, 'application-schema.json');
31
- if (!fs.existsSync(schemaPath)) {
32
- throw new Error(`application-schema.json not found: ${schemaPath}`);
48
+ if (fs.existsSync(schemaPath)) {
49
+ return generator.splitExternalApplicationSchema(schemaPath, outputDir);
33
50
  }
34
- return generator.splitExternalApplicationSchema(schemaPath, outputDir);
51
+ throw new Error(
52
+ `No deployment or schema file found. Expected one of:\n • ${deployJsonPath}\n • ${schemaPath}\n\nRun "aifabrix json ${appName}" to generate the deploy JSON, or provide application-schema.json.`
53
+ );
35
54
  }
36
55
 
37
56
  const deployJsonPath = getDeployJsonPath(appName, appType, true);
@@ -50,18 +69,20 @@ async function handleSplitJsonCommand(appName, options) {
50
69
  function logSplitJsonResult(result) {
51
70
  logger.log(chalk.green('\n✓ Successfully split deployment JSON into component files:'));
52
71
  logger.log(` • env.template: ${result.envTemplate}`);
53
- logger.log(` • variables.yaml: ${result.variables}`);
72
+ logger.log(` • application.yaml: ${result.variables}`);
73
+ if (result.systemFile) {
74
+ logger.log(` • system: ${result.systemFile}`);
75
+ }
76
+ if (result.datasourceFiles && result.datasourceFiles.length > 0) {
77
+ result.datasourceFiles.forEach(filePath => logger.log(` • datasource: ${filePath}`));
78
+ }
54
79
  if (result.rbac) {
55
80
  logger.log(` • rbac.yml: ${result.rbac}`);
56
81
  }
57
82
  logger.log(` • README.md: ${result.readme}`);
58
83
  }
59
84
 
60
- /**
61
- * Sets up utility commands
62
- * @param {Command} program - Commander program instance
63
- */
64
- function setupUtilityCommands(program) {
85
+ function setupResolveCommand(program) {
65
86
  program.command('resolve <app>')
66
87
  .description('Generate .env file from template and validate application files')
67
88
  .option('-f, --force', 'Generate missing secret keys in secrets file')
@@ -70,7 +91,6 @@ function setupUtilityCommands(program) {
70
91
  try {
71
92
  const envPath = await secrets.generateEnvFile(appName, undefined, 'docker', options.force);
72
93
  logger.log(`✓ Generated .env file: ${envPath}`);
73
-
74
94
  if (!options.skipValidation) {
75
95
  const validate = require('../validation/validate');
76
96
  const result = await validate.validateAppOrFile(appName);
@@ -85,26 +105,24 @@ function setupUtilityCommands(program) {
85
105
  process.exit(1);
86
106
  }
87
107
  });
108
+ }
88
109
 
110
+ function setupJsonCommand(program) {
89
111
  program.command('json <app>')
90
112
  .description('Generate deployment JSON to disk (<app>-deploy.json). Use before commit so version control has the correct file.')
91
- .option('--type <type>', 'Application type (external) - if set, only checks integration folder')
92
113
  .action(async(appName, options) => {
93
114
  try {
94
115
  const result = await generator.generateDeployJsonWithValidation(appName, options);
95
116
  if (result.success) {
96
117
  const fileName = result.path.includes('application-schema.json') ? 'application-schema.json' : 'deployment JSON';
97
118
  logger.log(`✓ Generated ${fileName}: ${result.path}`);
98
-
99
119
  if (result.validation.warnings && result.validation.warnings.length > 0) {
100
120
  logger.log('\n⚠️ Warnings:');
101
- result.validation.warnings.forEach(warning => logger.log(` • ${warning}`));
121
+ result.validation.warnings.forEach(w => logger.log(` • ${w}`));
102
122
  }
103
123
  } else {
104
124
  logger.log('❌ Validation failed:');
105
- if (result.validation.errors && result.validation.errors.length > 0) {
106
- result.validation.errors.forEach(error => logger.log(` • ${error}`));
107
- }
125
+ (result.validation.errors || []).forEach(e => logger.log(` • ${e}`));
108
126
  process.exit(1);
109
127
  }
110
128
  } catch (error) {
@@ -112,21 +130,41 @@ function setupUtilityCommands(program) {
112
130
  process.exit(1);
113
131
  }
114
132
  });
133
+ }
115
134
 
135
+ function setupSplitJsonConvertShowCommands(program) {
116
136
  program.command('split-json <app>')
117
- .description('Split deployment JSON into component files (env.template, variables.yaml, rbac.yml, README.md)')
137
+ .description('Split deployment JSON into component files (env.template, application.yaml, rbac.yml, README.md)')
118
138
  .option('-o, --output <dir>', 'Output directory for component files (defaults to same directory as JSON)')
119
- .option('--type <type>', 'Application type (external) - if set, only checks integration folder')
120
139
  .action(async(appName, options) => {
121
140
  try {
122
- const result = await handleSplitJsonCommand(appName, options);
123
- logSplitJsonResult(result);
141
+ logSplitJsonResult(await handleSplitJsonCommand(appName, options));
124
142
  } catch (error) {
125
143
  handleCommandError(error, 'split-json');
126
144
  process.exit(1);
127
145
  }
128
146
  });
129
147
 
148
+ program.command('convert <app>')
149
+ .description('Convert integration/external system and datasource config files between JSON and YAML')
150
+ .option('--format <format>', 'Target format: json | yaml (required)')
151
+ .option('-f, --force', 'Skip confirmation prompt')
152
+ .action(async(appName, options) => {
153
+ try {
154
+ const { runConvert } = require('../commands/convert');
155
+ const { converted, deleted } = await runConvert(appName, { format: options.format, force: options.force });
156
+ logger.log(chalk.green('\n✓ Convert complete.'));
157
+ converted.forEach(p => logger.log(` • ${p}`));
158
+ if (deleted.length > 0) {
159
+ logger.log(chalk.gray(' Removed old files:'));
160
+ deleted.forEach(p => logger.log(chalk.gray(` ${p}`)));
161
+ }
162
+ } catch (error) {
163
+ handleCommandError(error, 'convert');
164
+ process.exit(1);
165
+ }
166
+ });
167
+
130
168
  program.command('show <appKey>')
131
169
  .description('Show application info from local builder/ or integration/ (offline) or from controller (--online)')
132
170
  .option('--online', 'Fetch application data from the controller')
@@ -140,18 +178,17 @@ function setupUtilityCommands(program) {
140
178
  process.exit(1);
141
179
  }
142
180
  });
181
+ }
143
182
 
183
+ function setupValidateDiffCommands(program) {
144
184
  program.command('validate <appOrFile>')
145
185
  .description('Validate application or external integration file')
146
- .option('--type <type>', 'Application type (external) - if set, only checks integration folder')
147
186
  .action(async(appOrFile, options) => {
148
187
  try {
149
188
  const validate = require('../validation/validate');
150
189
  const result = await validate.validateAppOrFile(appOrFile, options);
151
190
  validate.displayValidationResults(result);
152
- if (!result.valid) {
153
- process.exit(1);
154
- }
191
+ if (!result.valid) process.exit(1);
155
192
  } catch (error) {
156
193
  handleCommandError(error, 'validate');
157
194
  process.exit(1);
@@ -160,14 +197,14 @@ function setupUtilityCommands(program) {
160
197
 
161
198
  program.command('diff <file1> <file2>')
162
199
  .description('Compare two configuration files (for deployment pipeline)')
163
- .action(async(file1, file2) => {
200
+ .option('--no-validate', 'Skip schema validation (type check still applied)')
201
+ .action(async(file1, file2, cmd) => {
164
202
  try {
165
203
  const diff = require('../core/diff');
166
- const result = await diff.compareFiles(file1, file2);
204
+ const opts = cmd.opts();
205
+ const result = await diff.compareFiles(file1, file2, { validate: opts.validate !== false });
167
206
  diff.formatDiffOutput(result);
168
- if (!result.identical) {
169
- process.exit(1);
170
- }
207
+ if (!result.identical) process.exit(1);
171
208
  } catch (error) {
172
209
  handleCommandError(error, 'diff');
173
210
  process.exit(1);
@@ -175,4 +212,15 @@ function setupUtilityCommands(program) {
175
212
  });
176
213
  }
177
214
 
215
+ /**
216
+ * Sets up utility commands
217
+ * @param {Command} program - Commander program instance
218
+ */
219
+ function setupUtilityCommands(program) {
220
+ setupResolveCommand(program);
221
+ setupJsonCommand(program);
222
+ setupSplitJsonConvertShowCommands(program);
223
+ setupValidateDiffCommands(program);
224
+ }
225
+
178
226
  module.exports = { setupUtilityCommands };