@aifabrix/builder 2.40.2 → 2.42.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 (198) hide show
  1. package/.cursor/rules/docs-rules.mdc +30 -0
  2. package/README.md +7 -5
  3. package/integration/hubspot/README.md +8 -4
  4. package/integration/hubspot/application.json +54 -0
  5. package/integration/hubspot/create-hubspot.js +9 -136
  6. package/integration/hubspot/env.template +3 -4
  7. package/integration/hubspot/hubspot-datasource-company.json +343 -5
  8. package/integration/hubspot/hubspot-datasource-contact.json +413 -5
  9. package/integration/hubspot/hubspot-datasource-deal.json +341 -4
  10. package/integration/hubspot/hubspot-datasource-users.json +116 -0
  11. package/integration/hubspot/hubspot-deploy.json +1250 -108
  12. package/integration/hubspot/hubspot-system.json +15 -32
  13. package/integration/hubspot/test-dataplane-down-tests.js +17 -16
  14. package/integration/hubspot/test-dataplane-down.js +2 -2
  15. package/integration/hubspot/test.js +1 -1
  16. package/jest.config.manual.js +2 -1
  17. package/lib/api/credential.api.js +40 -0
  18. package/lib/api/dev.api.js +423 -0
  19. package/lib/api/external-test.api.js +111 -0
  20. package/lib/api/index.js +42 -19
  21. package/lib/api/pipeline.api.js +66 -120
  22. package/lib/api/types/credential.types.js +23 -0
  23. package/lib/api/types/dev.types.js +140 -0
  24. package/lib/api/types/pipeline.types.js +37 -0
  25. package/lib/api/wizard-platform.api.js +61 -0
  26. package/lib/api/wizard.api.js +34 -1
  27. package/lib/app/config.js +44 -11
  28. package/lib/app/down.js +2 -1
  29. package/lib/app/index.js +12 -1
  30. package/lib/app/prompts.js +44 -29
  31. package/lib/app/push.js +36 -12
  32. package/lib/app/readme.js +9 -6
  33. package/lib/app/run-env-compose.js +264 -0
  34. package/lib/app/run-helpers.js +121 -118
  35. package/lib/app/run.js +148 -28
  36. package/lib/app/show-display.js +1 -1
  37. package/lib/app/show.js +5 -2
  38. package/lib/build/index.js +11 -3
  39. package/lib/cli/setup-app.js +172 -15
  40. package/lib/cli/setup-credential-deployment.js +31 -6
  41. package/lib/cli/setup-dev.js +206 -16
  42. package/lib/cli/setup-environment.js +16 -6
  43. package/lib/cli/setup-external-system.js +89 -24
  44. package/lib/cli/setup-infra.js +82 -15
  45. package/lib/cli/setup-secrets.js +52 -5
  46. package/lib/cli/setup-utility.js +129 -24
  47. package/lib/commands/app-install.js +172 -0
  48. package/lib/commands/app-shell.js +75 -0
  49. package/lib/commands/app-test.js +282 -0
  50. package/lib/commands/app.js +1 -1
  51. package/lib/commands/credential-env.js +162 -0
  52. package/lib/commands/credential-list.js +17 -22
  53. package/lib/commands/credential-push.js +96 -0
  54. package/lib/commands/datasource.js +77 -6
  55. package/lib/commands/dev-cli-handlers.js +141 -0
  56. package/lib/commands/dev-down.js +114 -0
  57. package/lib/commands/dev-init.js +347 -0
  58. package/lib/commands/repair-auth-config.js +99 -0
  59. package/lib/commands/repair-datasource-keys.js +208 -0
  60. package/lib/commands/repair-datasource.js +235 -0
  61. package/lib/commands/repair-env-template.js +348 -0
  62. package/lib/commands/repair-internal.js +85 -0
  63. package/lib/commands/repair-rbac.js +158 -0
  64. package/lib/commands/repair.js +507 -0
  65. package/lib/commands/secrets-list.js +118 -0
  66. package/lib/commands/secrets-remove.js +97 -0
  67. package/lib/commands/secrets-set.js +30 -17
  68. package/lib/commands/secrets-validate.js +50 -0
  69. package/lib/commands/test-e2e-external.js +165 -0
  70. package/lib/commands/up-dataplane.js +2 -2
  71. package/lib/commands/up-miso.js +0 -25
  72. package/lib/commands/upload.js +96 -40
  73. package/lib/commands/wizard-core-helpers.js +226 -4
  74. package/lib/commands/wizard-core.js +67 -29
  75. package/lib/commands/wizard-dataplane.js +1 -1
  76. package/lib/commands/wizard-entity-selection.js +43 -0
  77. package/lib/commands/wizard-headless.js +44 -5
  78. package/lib/commands/wizard-helpers.js +7 -3
  79. package/lib/commands/wizard.js +86 -64
  80. package/lib/core/admin-secrets.js +96 -0
  81. package/lib/core/config.js +7 -1
  82. package/lib/core/secrets-ensure.js +378 -0
  83. package/lib/core/secrets-env-write.js +157 -0
  84. package/lib/core/secrets.js +176 -89
  85. package/lib/datasource/deploy.js +12 -3
  86. package/lib/datasource/field-reference-validator.js +91 -0
  87. package/lib/datasource/test-e2e.js +219 -0
  88. package/lib/datasource/test-integration.js +154 -0
  89. package/lib/datasource/validate.js +21 -3
  90. package/lib/deployment/deployer.js +7 -5
  91. package/lib/deployment/environment-config.js +137 -0
  92. package/lib/deployment/environment.js +21 -98
  93. package/lib/deployment/push.js +32 -2
  94. package/lib/external-system/download.js +188 -203
  95. package/lib/external-system/generator.js +204 -56
  96. package/lib/external-system/test-auth.js +7 -3
  97. package/lib/external-system/test-execution.js +2 -1
  98. package/lib/external-system/test-system-level.js +73 -0
  99. package/lib/external-system/test.js +56 -19
  100. package/lib/generator/external-controller-manifest.js +29 -2
  101. package/lib/generator/external-schema-utils.js +1 -1
  102. package/lib/generator/external.js +10 -3
  103. package/lib/generator/index.js +177 -25
  104. package/lib/generator/split-readme.js +1 -0
  105. package/lib/generator/split-variables.js +7 -1
  106. package/lib/generator/split.js +194 -54
  107. package/lib/generator/wizard-prompts-secondary.js +294 -0
  108. package/lib/generator/wizard-prompts.js +105 -106
  109. package/lib/generator/wizard-readme.js +88 -0
  110. package/lib/generator/wizard.js +155 -158
  111. package/lib/infrastructure/compose.js +11 -1
  112. package/lib/infrastructure/helpers.js +103 -20
  113. package/lib/infrastructure/index.js +98 -12
  114. package/lib/infrastructure/services.js +88 -22
  115. package/lib/schema/application-schema.json +32 -8
  116. package/lib/schema/external-datasource.schema.json +49 -26
  117. package/lib/schema/external-system.schema.json +509 -411
  118. package/lib/schema/wizard-config.schema.json +16 -0
  119. package/lib/utils/api.js +41 -13
  120. package/lib/utils/app-register-auth.js +25 -3
  121. package/lib/utils/auth-headers.js +8 -7
  122. package/lib/utils/cli-utils.js +20 -0
  123. package/lib/utils/compose-generator.js +77 -76
  124. package/lib/utils/compose-handlebars-helpers.js +54 -0
  125. package/lib/utils/compose-vector-helper.js +18 -0
  126. package/lib/utils/config-format-preference.js +51 -0
  127. package/lib/utils/config-format.js +36 -0
  128. package/lib/utils/config-paths.js +127 -2
  129. package/lib/utils/configuration-env-resolver.js +179 -0
  130. package/lib/utils/credential-display.js +83 -0
  131. package/lib/utils/credential-secrets-env.js +357 -0
  132. package/lib/utils/dataplane-pipeline-warning.js +28 -0
  133. package/lib/utils/deployment-validation-helpers.js +4 -4
  134. package/lib/utils/dev-ca-install.js +139 -0
  135. package/lib/utils/dev-cert-helper.js +122 -0
  136. package/lib/utils/device-code-helpers.js +224 -0
  137. package/lib/utils/device-code.js +37 -336
  138. package/lib/utils/docker-build.js +40 -8
  139. package/lib/utils/env-copy.js +103 -13
  140. package/lib/utils/env-map.js +35 -5
  141. package/lib/utils/env-template.js +6 -5
  142. package/lib/utils/error-formatters/http-status-errors.js +20 -2
  143. package/lib/utils/error-formatters/permission-errors.js +0 -1
  144. package/lib/utils/error-formatters/validation-errors.js +0 -1
  145. package/lib/utils/external-readme.js +56 -29
  146. package/lib/utils/external-system-display.js +59 -1
  147. package/lib/utils/external-system-test-helpers.js +21 -8
  148. package/lib/utils/external-system-validators.js +3 -0
  149. package/lib/utils/file-upload.js +20 -50
  150. package/lib/utils/help-builder.js +16 -2
  151. package/lib/utils/infra-status.js +80 -45
  152. package/lib/utils/local-secrets.js +7 -52
  153. package/lib/utils/mutagen-install.js +195 -0
  154. package/lib/utils/mutagen.js +146 -0
  155. package/lib/utils/paths.js +128 -37
  156. package/lib/utils/port-resolver.js +28 -16
  157. package/lib/utils/remote-dev-auth.js +38 -0
  158. package/lib/utils/remote-docker-env.js +43 -0
  159. package/lib/utils/remote-secrets-loader.js +60 -0
  160. package/lib/utils/secrets-canonical.js +93 -0
  161. package/lib/utils/secrets-generator.js +114 -6
  162. package/lib/utils/secrets-helpers.js +108 -114
  163. package/lib/utils/secrets-path.js +2 -2
  164. package/lib/utils/secrets-utils.js +52 -1
  165. package/lib/utils/secrets-validation.js +84 -0
  166. package/lib/utils/ssh-key-helper.js +116 -0
  167. package/lib/utils/test-log-writer.js +56 -0
  168. package/lib/utils/token-manager-messages.js +90 -0
  169. package/lib/utils/token-manager.js +29 -36
  170. package/lib/utils/variable-transformer.js +3 -3
  171. package/lib/validation/env-template-auth.js +157 -0
  172. package/lib/validation/env-template-kv.js +41 -0
  173. package/lib/validation/external-manifest-validator.js +25 -0
  174. package/lib/validation/external-system-auth-rules.js +86 -0
  175. package/lib/validation/validate-batch.js +149 -0
  176. package/lib/validation/validate-datasource-keys-api.js +33 -0
  177. package/lib/validation/validate-display.js +94 -16
  178. package/lib/validation/validate.js +25 -12
  179. package/lib/validation/validator.js +72 -9
  180. package/lib/validation/wizard-datasource-validation.js +50 -0
  181. package/package.json +8 -3
  182. package/scripts/install-local.js +34 -15
  183. package/templates/README.md +0 -1
  184. package/templates/applications/README.md.hbs +4 -4
  185. package/templates/applications/dataplane/application.yaml +6 -5
  186. package/templates/applications/dataplane/env.template +15 -10
  187. package/templates/applications/dataplane/rbac.yaml +2 -2
  188. package/templates/applications/keycloak/env.template +2 -0
  189. package/templates/applications/miso-controller/application.yaml +1 -0
  190. package/templates/applications/miso-controller/env.template +12 -10
  191. package/templates/external-system/README.md.hbs +65 -25
  192. package/templates/external-system/deploy.js.hbs +4 -2
  193. package/templates/external-system/external-datasource.yaml.hbs +217 -0
  194. package/templates/external-system/external-system.json.hbs +1 -18
  195. package/templates/infra/compose.yaml.hbs +6 -0
  196. package/templates/python/docker-compose.hbs +49 -23
  197. package/templates/typescript/docker-compose.hbs +48 -22
  198. package/integration/hubspot/application.yaml +0 -37
@@ -1,5 +1,5 @@
1
1
  /**
2
- * CLI developer configuration command setup (dev config, dev set-id).
2
+ * CLI developer configuration command setup (dev config, dev set-id, dev init, dev add/update/pin/delete/list).
3
3
  *
4
4
  * @fileoverview Developer command definitions for AI Fabrix Builder CLI
5
5
  * @author AI Fabrix Team
@@ -11,20 +11,38 @@ const config = require('../core/config');
11
11
  const devConfig = require('../utils/dev-config');
12
12
  const logger = require('../utils/logger');
13
13
  const { handleCommandError } = require('../utils/cli-utils');
14
+ const { runDevInit, runDevRefresh } = require('../commands/dev-init');
15
+ const {
16
+ handleDevList,
17
+ handleDevAdd,
18
+ handleDevUpdate,
19
+ handleDevPin,
20
+ handleDevDelete
21
+ } = require('../commands/dev-cli-handlers');
14
22
 
15
23
  /**
16
- * Display developer configuration
24
+ * Display developer configuration (local ports and config; remote/settings keys always shown).
25
+ * Always shows environment, controller, and remote keys (value or "(not set)").
17
26
  * @param {string} devId - Developer ID
18
27
  * @returns {Promise<void>}
19
28
  */
20
29
  async function displayDevConfig(devId) {
21
30
  const devIdNum = parseInt(devId, 10);
22
31
  const ports = devConfig.getDevPorts(devIdNum);
23
- const configVars = [
32
+ const environment = await config.getCurrentEnvironment();
33
+ const controller = await config.getControllerUrl();
34
+
35
+ const optionalConfigVars = [
36
+ { key: 'format', value: (await config.getFormat()) ?? '(not set)' },
24
37
  { key: 'aifabrix-home', value: await config.getAifabrixHomeOverride() },
25
38
  { key: 'aifabrix-secrets', value: await config.getAifabrixSecretsPath() },
26
- { key: 'aifabrix-env-config', value: await config.getAifabrixEnvConfigPath() }
27
- ].filter(v => v.value);
39
+ { key: 'aifabrix-env-config', value: await config.getAifabrixEnvConfigPath() },
40
+ { key: 'remote-server', value: await config.getRemoteServer() },
41
+ { key: 'docker-endpoint', value: await config.getDockerEndpoint() },
42
+ { key: 'user-mutagen-folder', value: await config.getUserMutagenFolder() },
43
+ { key: 'sync-ssh-user', value: await config.getSyncSshUser() },
44
+ { key: 'sync-ssh-host', value: await config.getSyncSshHost() }
45
+ ];
28
46
 
29
47
  logger.log('\n🔧 Developer Configuration\n');
30
48
  logger.log(`Developer ID: ${devId}`);
@@ -35,22 +53,30 @@ async function displayDevConfig(devId) {
35
53
  logger.log(` pgAdmin: ${ports.pgadmin}`);
36
54
  logger.log(` Redis Commander: ${ports.redisCommander}`);
37
55
 
38
- if (configVars.length > 0) {
39
- logger.log('\nConfiguration:');
40
- configVars.forEach(v => logger.log(` ${v.key}: ${v.value}`));
41
- }
56
+ logger.log('\nConfiguration:');
57
+ logger.log(` environment: '${environment}'`);
58
+ logger.log(controller ? ` controller: '${controller}'` : ' controller: (not set)');
59
+ optionalConfigVars.forEach(v => logger.log(` ${v.key}: ${v.value || '(not set)'}`));
42
60
  logger.log('');
43
61
  }
44
62
 
45
63
  /**
46
- * Sets up developer configuration commands
47
- * @param {Command} program - Commander program instance
64
+ * Handle dev set-format command
65
+ * @param {string} format - Format value (json | yaml)
66
+ * @returns {Promise<void>}
48
67
  */
49
- function setupDevCommands(program) {
50
- const dev = program
51
- .command('dev')
52
- .description('Developer configuration and isolation');
68
+ async function handleSetFormat(format) {
69
+ await config.setFormat(format);
70
+ logger.log(chalk.green(`✓ Format set to ${format.toLowerCase()}`));
71
+ const devId = await config.getDeveloperId();
72
+ await displayDevConfig(devId);
73
+ }
53
74
 
75
+ /**
76
+ * Register dev config and set-id commands.
77
+ * @param {Command} dev - dev subcommand group
78
+ */
79
+ function setupDevConfigCommands(dev) {
54
80
  dev
55
81
  .command('config')
56
82
  .description('Show or set developer configuration')
@@ -69,7 +95,6 @@ function setupDevCommands(program) {
69
95
  await displayDevConfig(setIdValue);
70
96
  return;
71
97
  }
72
-
73
98
  const devId = await config.getDeveloperId();
74
99
  await displayDevConfig(devId);
75
100
  } catch (error) {
@@ -78,6 +103,18 @@ function setupDevCommands(program) {
78
103
  }
79
104
  });
80
105
 
106
+ dev
107
+ .command('set-format <format>')
108
+ .description('Set default output format for download/convert (json | yaml); used when --format not passed')
109
+ .action(async(format) => {
110
+ try {
111
+ await handleSetFormat(format);
112
+ } catch (error) {
113
+ handleCommandError(error, 'dev set-format');
114
+ process.exit(1);
115
+ }
116
+ });
117
+
81
118
  dev
82
119
  .command('set-id <id>')
83
120
  .description('Set developer ID (convenience alias for "dev config --set-id")')
@@ -98,4 +135,157 @@ function setupDevCommands(program) {
98
135
  });
99
136
  }
100
137
 
138
+ /**
139
+ * Register dev init and refresh commands.
140
+ * @param {Command} dev - dev subcommand group
141
+ */
142
+ function setupDevInitCommand(dev) {
143
+ dev
144
+ .command('init')
145
+ .description('Onboard with Builder Server (issue certificate, fetch settings, register SSH key for Mutagen)')
146
+ .requiredOption('--developer-id <id>', 'Developer ID (same as dev add; e.g. 01)')
147
+ .requiredOption('--server <url>', 'Builder Server base URL (e.g. https://dev.aifabrix.dev)')
148
+ .requiredOption('--pin <pin>', 'One-time PIN from your admin')
149
+ .option('-y, --yes', 'Auto-install development CA without prompt when certificate is untrusted')
150
+ .option('--no-install-ca', 'Do not offer CA install; fail with manual instructions on untrusted certificate')
151
+ .action(async(options) => {
152
+ try {
153
+ await runDevInit(options);
154
+ } catch (error) {
155
+ handleCommandError(error, 'dev init');
156
+ process.exit(1);
157
+ }
158
+ });
159
+
160
+ dev
161
+ .command('refresh')
162
+ .description('Fetch settings from Builder Server and update config; refresh certificate if expiring within 14 days or --cert')
163
+ .option('--cert', 'Force certificate refresh (create PIN + issue-cert) even when cert is still valid')
164
+ .action(async(options) => {
165
+ try {
166
+ await runDevRefresh(options);
167
+ } catch (error) {
168
+ handleCommandError(error, 'dev refresh');
169
+ process.exit(1);
170
+ }
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Register dev list and add commands (remote only).
176
+ * @param {Command} dev - dev subcommand group
177
+ */
178
+ function setupDevListAddCommands(dev) {
179
+ dev
180
+ .command('list')
181
+ .description('List developer users (remote Builder Server only)')
182
+ .action(async() => {
183
+ try {
184
+ await handleDevList();
185
+ } catch (error) {
186
+ handleCommandError(error, 'dev list');
187
+ process.exit(1);
188
+ }
189
+ });
190
+
191
+ dev
192
+ .command('add')
193
+ .description('Register a new developer (remote Builder Server only; admin)')
194
+ .requiredOption('--developer-id <id>', 'Developer ID (unique, e.g. 01)')
195
+ .requiredOption('--name <name>', 'Display name')
196
+ .requiredOption('--email <email>', 'Email address')
197
+ .option('--groups <items>', 'Comma-separated groups (admin, secret-manager, developer)', 'developer')
198
+ .action(async(options) => {
199
+ try {
200
+ await handleDevAdd(options);
201
+ } catch (error) {
202
+ handleCommandError(error, 'dev add');
203
+ process.exit(1);
204
+ }
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Register dev update/pin/delete commands (remote only).
210
+ * @param {Command} dev - dev subcommand group
211
+ */
212
+ function setupDevUpdatePinDeleteCommands(dev) {
213
+ dev
214
+ .command('update [developerId]')
215
+ .description('Update a developer (name, email, groups); use --developer-id like dev add')
216
+ .option('--developer-id <id>', 'Developer ID (same as dev add)')
217
+ .option('--name <name>', 'Display name')
218
+ .option('--email <email>', 'Email address')
219
+ .option('--groups <items>', 'Comma-separated groups (admin, secret-manager, developer)')
220
+ .action(async(developerId, options) => {
221
+ try {
222
+ await handleDevUpdate(developerId, options);
223
+ } catch (error) {
224
+ handleCommandError(error, 'dev update');
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ dev
230
+ .command('pin [developerId]')
231
+ .description('Create or regenerate one-time PIN for onboarding (admin; show once to developer)')
232
+ .action(async(developerId) => {
233
+ try {
234
+ await handleDevPin(developerId);
235
+ } catch (error) {
236
+ handleCommandError(error, 'dev pin');
237
+ process.exit(1);
238
+ }
239
+ });
240
+
241
+ dev
242
+ .command('delete <developerId>')
243
+ .description('Remove a developer (remote Builder Server only; admin)')
244
+ .action(async(developerId) => {
245
+ try {
246
+ await handleDevDelete(developerId);
247
+ } catch (error) {
248
+ handleCommandError(error, 'dev delete');
249
+ process.exit(1);
250
+ }
251
+ });
252
+
253
+ dev
254
+ .command('down')
255
+ .description('Stop Mutagen sync sessions for this developer (and optionally app containers)')
256
+ .option('--apps', 'Also stop running app containers for this developer')
257
+ .action(async(options) => {
258
+ try {
259
+ const { handleDevDown } = require('../commands/dev-down');
260
+ await handleDevDown(options);
261
+ } catch (error) {
262
+ handleCommandError(error, 'dev down');
263
+ process.exit(1);
264
+ }
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Register dev list/add/update/pin/delete commands (remote only).
270
+ * @param {Command} dev - dev subcommand group
271
+ */
272
+ function setupDevUserCommands(dev) {
273
+ setupDevListAddCommands(dev);
274
+ setupDevUpdatePinDeleteCommands(dev);
275
+ }
276
+
277
+ /**
278
+ * Sets up developer configuration commands
279
+ * @param {Command} program - Commander program instance
280
+ */
281
+ function setupDevCommands(program) {
282
+ const dev = program
283
+ .command('dev')
284
+ .description('Developer configuration and isolation');
285
+
286
+ setupDevConfigCommands(dev);
287
+ setupDevInitCommand(dev);
288
+ setupDevUserCommands(dev);
289
+ }
290
+
101
291
  module.exports = { setupDevCommands };
@@ -25,28 +25,38 @@ function setupEnvironmentCommands(program) {
25
25
 
26
26
  const environment = program
27
27
  .command('environment')
28
- .description('Manage environments');
28
+ .description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
29
+
30
+ const deployExamples = `
31
+ Examples:
32
+ $ aifabrix environment deploy dev
33
+ $ aifabrix environment deploy tst --preset m
34
+ $ aifabrix environment deploy dev --config ./env-config.json --no-poll`;
29
35
 
30
36
  environment
31
37
  .command('deploy <env>')
32
- .description('Deploy/setup environment in Miso Controller')
33
- .option('--config <file>', 'Environment configuration file')
38
+ .description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
39
+ .option('--config <file>', 'Environment configuration file (optional if --preset is used)')
40
+ .option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
34
41
  .option('--skip-validation', 'Skip environment validation')
35
42
  .option('--poll', 'Poll for deployment status', true)
36
43
  .option('--no-poll', 'Do not poll for status')
44
+ .addHelpText('after', deployExamples)
37
45
  .action(deployEnvHandler);
38
46
 
39
47
  const env = program
40
48
  .command('env')
41
- .description('Environment management (alias for environment)');
49
+ .description('Deploy and manage Miso Controller environments (alias for environment)');
42
50
 
43
51
  env
44
52
  .command('deploy <env>')
45
- .description('Deploy/setup environment in Miso Controller')
46
- .option('--config <file>', 'Environment configuration file')
53
+ .description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
54
+ .option('--config <file>', 'Environment configuration file (optional if --preset is used)')
55
+ .option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
47
56
  .option('--skip-validation', 'Skip environment validation')
48
57
  .option('--poll', 'Poll for deployment status', true)
49
58
  .option('--no-poll', 'Do not poll for status')
59
+ .addHelpText('after', deployExamples)
50
60
  .action(deployEnvHandler);
51
61
  }
52
62
 
@@ -1,9 +1,17 @@
1
1
  /**
2
- * CLI external system command setup (download, delete, test, test-integration).
2
+ * CLI external system command setup (download, upload, delete, test-integration).
3
+ *
4
+ * Registers these commands on the Commander program:
5
+ * - download <system-key> – Download external system from dataplane to integration/<system-key>/
6
+ * - upload <system-key> – Upload to dataplane (validate → publish; no controller deploy)
7
+ * - delete <system-key> – Delete external system and associated datasources from dataplane
8
+ * - test-integration <app> – Run integration tests (builder: in container; external: via dataplane pipeline)
3
9
  *
4
10
  * @fileoverview External system command definitions for AI Fabrix Builder CLI
5
11
  * @author AI Fabrix Team
6
12
  * @version 2.0.0
13
+ * @see docs/commands/external-integration.md - User-facing command reference
14
+ * @see docs/external-systems.md - External systems guide and workflow
7
15
  */
8
16
 
9
17
  const { handleCommandError } = require('../utils/cli-utils');
@@ -11,11 +19,18 @@ const { handleCommandError } = require('../utils/cli-utils');
11
19
  function setupDownloadCommand(program) {
12
20
  program.command('download <system-key>')
13
21
  .description('Download external system from dataplane to local development structure')
22
+ .option('--format <format>', 'Output format: json | yaml (default: yaml or config format)')
14
23
  .option('--dry-run', 'Show what would be downloaded without actually downloading')
24
+ .option('--force', 'Overwrite existing README.md without prompting')
15
25
  .action(async(systemKey, options) => {
16
26
  try {
27
+ const config = require('../core/config');
28
+ const effectiveFormat = (options.format || (await config.getFormat()) || 'yaml').trim().toLowerCase();
29
+ if (effectiveFormat !== 'json' && effectiveFormat !== 'yaml') {
30
+ throw new Error('Option --format must be \'json\' or \'yaml\'');
31
+ }
17
32
  const download = require('../external-system/download');
18
- await download.downloadExternalSystem(systemKey, options);
33
+ await download.downloadExternalSystem(systemKey, { ...options, format: effectiveFormat });
19
34
  } catch (error) {
20
35
  handleCommandError(error, 'download');
21
36
  process.exit(1);
@@ -27,7 +42,6 @@ function setupUploadCommand(program) {
27
42
  program.command('upload <system-key>')
28
43
  .description('Upload external system to dataplane (upload → validate → publish; no controller deploy)')
29
44
  .option('--dry-run', 'Validate and build payload only; no API calls')
30
- .option('--dataplane <url>', 'Dataplane URL (default: discovered from controller)')
31
45
  .action(async(systemKey, options) => {
32
46
  try {
33
47
  const upload = require('../commands/upload');
@@ -56,35 +70,86 @@ function setupDeleteCommand(program) {
56
70
  });
57
71
  }
58
72
 
73
+ /**
74
+ * Try to run builder-style test integration when app is not in builder or has no externalIntegration.
75
+ * @param {string} appName - App or system key
76
+ * @param {Object} options - CLI options
77
+ * @returns {Promise<boolean>} True if builder test was run, false otherwise
78
+ */
79
+ async function tryBuilderTestIntegration(appName, options) {
80
+ const fsSync = require('fs');
81
+ const pathsUtil = require('../utils/paths');
82
+ const { getIntegrationPath, getBuilderPath } = pathsUtil;
83
+ const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
84
+ const { loadConfigFile } = require('../utils/config-format');
85
+ const integrationPath = getIntegrationPath(appName);
86
+ let hasExternalIntegration = false;
87
+ try {
88
+ const integrationConfig = loadConfigFile(resolveApplicationConfigPath(integrationPath));
89
+ hasExternalIntegration = !!(integrationConfig && integrationConfig.externalIntegration);
90
+ } catch {
91
+ // integration path or config missing
92
+ }
93
+ if (!hasExternalIntegration) {
94
+ const builderPath = getBuilderPath(appName);
95
+ const builderConfigPath = resolveApplicationConfigPath(builderPath);
96
+ if (fsSync.existsSync(builderPath) && fsSync.existsSync(builderConfigPath)) {
97
+ const { runAppTestIntegration } = require('../commands/app-test');
98
+ const opts = { env: options.env || options.environment || 'dev' };
99
+ await runAppTestIntegration(appName, opts);
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ }
105
+
106
+ /**
107
+ * Run external system integration test via dataplane and exit on failure.
108
+ * @param {string} appName - App or system key
109
+ * @param {Object} options - CLI options
110
+ * @returns {Promise<void>}
111
+ */
112
+ async function runExternalSystemTestIntegration(appName, options) {
113
+ const test = require('../external-system/test');
114
+ const opts = { ...options, environment: options.env || options.environment, debug: options.debug };
115
+ const results = await test.testExternalSystemIntegration(appName, opts);
116
+ test.displayIntegrationTestResults(results, options.verbose);
117
+ if (!results.success) process.exit(1);
118
+ }
119
+
120
+ /**
121
+ * Run test-integration command: builder app in container or external system via dataplane.
122
+ * @param {string} appName - App or system key
123
+ * @param {Object} options - CLI options (datasource, payload, env, verbose, timeout)
124
+ * @returns {Promise<void>}
125
+ */
126
+ async function runTestIntegrationCommand(appName, options) {
127
+ const pathsUtil = require('../utils/paths');
128
+ const appType = await pathsUtil.detectAppType(appName).catch(() => null);
129
+ if (appType && appType.baseDir === 'builder') {
130
+ const { runAppTestIntegration } = require('../commands/app-test');
131
+ const opts = { env: options.env || options.environment || 'dev' };
132
+ await runAppTestIntegration(appName, opts);
133
+ return;
134
+ }
135
+ const ranBuilder = await tryBuilderTestIntegration(appName, options);
136
+ if (ranBuilder) return;
137
+ await runExternalSystemTestIntegration(appName, options);
138
+ }
139
+
59
140
  function setupExternalSystemTestCommands(program) {
60
- program.command('test <app>')
61
- .description('Run unit tests for external system (local validation, no API calls)')
62
- .option('-d, --datasource <key>', 'Test specific datasource only')
63
- .option('-v, --verbose', 'Show detailed validation output')
64
- .action(async(appName, options) => {
65
- try {
66
- const test = require('../external-system/test');
67
- const results = await test.testExternalSystem(appName, options);
68
- test.displayTestResults(results, options.verbose);
69
- if (!results.valid) process.exit(1);
70
- } catch (error) {
71
- handleCommandError(error, 'test');
72
- process.exit(1);
73
- }
74
- });
141
+ // 'test <app>' is registered in setup-app.js and dispatches by app type (builder vs external)
75
142
  program.command('test-integration <app>')
76
- .description('Run integration tests via dataplane pipeline API')
143
+ .description('Run integration tests (builder/docker app: in container; external system: via dataplane pipeline API)')
77
144
  .option('-d, --datasource <key>', 'Test specific datasource only')
78
145
  .option('-p, --payload <file>', 'Path to custom test payload file')
79
- .option('--dataplane <url>', 'Dataplane URL (default: discovered from controller)')
146
+ .option('-e, --env <env>', 'Environment: dev, tst, or pro (default: from aifabrix auth config)')
80
147
  .option('-v, --verbose', 'Show detailed test output')
148
+ .option('--debug', 'Include debug output and write log to integration/<app>/logs/')
81
149
  .option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
82
150
  .action(async(appName, options) => {
83
151
  try {
84
- const test = require('../external-system/test');
85
- const results = await test.testExternalSystemIntegration(appName, options);
86
- test.displayIntegrationTestResults(results, options.verbose);
87
- if (!results.success) process.exit(1);
152
+ await runTestIntegrationCommand(appName, options);
88
153
  } catch (error) {
89
154
  handleCommandError(error, 'test-integration');
90
155
  process.exit(1);
@@ -12,13 +12,41 @@ const appLib = require('../app');
12
12
  const validator = require('../validation/validator');
13
13
  const config = require('../core/config');
14
14
  const logger = require('../utils/logger');
15
- const { handleCommandError } = require('../utils/cli-utils');
15
+ const { handleCommandError, isAuthenticationError } = require('../utils/cli-utils');
16
+ const { resolveControllerUrl } = require('../utils/controller-url');
17
+ const { handleLogin } = require('../commands/login');
16
18
  const { handleUpMiso } = require('../commands/up-miso');
17
19
  const { handleUpDataplane } = require('../commands/up-dataplane');
18
20
 
19
21
  /**
20
- * Runs the up-infra command: resolves developer ID, traefik, and starts infra.
21
- * @param {Object} options - Commander options (developer, traefik)
22
+ * Persists optional service flag to config when explicitly set.
23
+ * @param {Object} cfg - Config object (mutated)
24
+ * @param {string} key - Config key (traefik, pgadmin, redisCommander)
25
+ * @param {boolean} value - Value to set
26
+ * @param {string} label - Label for log message
27
+ */
28
+ async function persistOptionalServiceFlag(cfg, key, value, label) {
29
+ cfg[key] = value;
30
+ await config.saveConfig(cfg);
31
+ logger.log(chalk.green(`✓ ${label} ${value ? 'enabled' : 'disabled'} and saved to config`));
32
+ }
33
+
34
+ /**
35
+ * Resolves effective boolean from option vs config.
36
+ * @param {*} optValue - options.traefik | options.pgAdmin | options.redisAdmin
37
+ * @param {*} cfgValue - cfg.traefik | cfg.pgadmin | cfg.redisCommander
38
+ * @param {boolean} defaultWhenUndef - Default when config value is undefined
39
+ * @returns {boolean}
40
+ */
41
+ function resolveFlag(optValue, cfgValue, defaultWhenUndef = true) {
42
+ if (optValue === true) return true;
43
+ if (optValue === false) return false;
44
+ return cfgValue !== false && (cfgValue === true || defaultWhenUndef);
45
+ }
46
+
47
+ /**
48
+ * Runs the up-infra command: resolves developer ID, traefik, pgAdmin, redisAdmin, and starts infra.
49
+ * @param {Object} options - Commander options (developer, traefik, pgAdmin, redisAdmin)
22
50
  * @returns {Promise<void>}
23
51
  */
24
52
  async function runUpInfraCommand(options) {
@@ -35,23 +63,33 @@ async function runUpInfraCommand(options) {
35
63
  logger.log(chalk.green(`✓ Developer ID set to ${id}`));
36
64
  }
37
65
  const cfg = await config.getConfig();
38
- if (options.traefik === true) {
39
- cfg.traefik = true;
40
- await config.saveConfig(cfg);
41
- logger.log(chalk.green('✓ Traefik enabled and saved to config'));
42
- } else if (options.traefik === false) {
43
- cfg.traefik = false;
44
- await config.saveConfig(cfg);
45
- logger.log(chalk.green('✓ Traefik disabled and saved to config'));
66
+ const flagSpecs = [
67
+ { opt: options.traefik, key: 'traefik', label: 'Traefik' },
68
+ { opt: options.pgAdmin, key: 'pgadmin', label: 'pgAdmin' },
69
+ { opt: options.redisAdmin, key: 'redisCommander', label: 'Redis Commander' }
70
+ ];
71
+ for (const { opt, key, label } of flagSpecs) {
72
+ if (opt === true || opt === false) {
73
+ await persistOptionalServiceFlag(cfg, key, opt, label);
74
+ }
46
75
  }
47
- const useTraefik = options.traefik === true ? true : (options.traefik === false ? false : !!(cfg.traefik));
48
- await infra.startInfra(developerId, { traefik: useTraefik });
76
+ await infra.startInfra(developerId, {
77
+ traefik: resolveFlag(options.traefik, cfg.traefik, false),
78
+ pgadmin: resolveFlag(options.pgAdmin, cfg.pgadmin, true),
79
+ redisCommander: resolveFlag(options.redisAdmin, cfg.redisCommander, true),
80
+ adminPwd: options.adminPwd
81
+ });
49
82
  }
50
83
 
51
84
  function setupUpInfraCommand(program) {
52
85
  program.command('up-infra')
53
- .description('Start local infrastructure: Postgres, Redis, optional Traefik')
86
+ .description('Start local infrastructure: Postgres, Redis, optional pgAdmin, Redis Commander, Traefik')
54
87
  .option('-d, --developer <id>', 'Set developer ID and start infrastructure')
88
+ .option('--adminPwd <password>', 'Override default admin password for new install (Postgres, pgAdmin, Redis Commander)')
89
+ .option('--pgAdmin', 'Include pgAdmin web UI and save to config')
90
+ .option('--no-pgAdmin', 'Exclude pgAdmin and save to config')
91
+ .option('--redisAdmin', 'Include Redis Commander web UI and save to config')
92
+ .option('--no-redisAdmin', 'Exclude Redis Commander and save to config')
55
93
  .option('--traefik', 'Include Traefik reverse proxy and save to config')
56
94
  .option('--no-traefik', 'Exclude Traefik and save to config')
57
95
  .action(async(options) => {
@@ -75,6 +113,18 @@ function setupUpPlatformCommand(program) {
75
113
  await handleUpMiso(options);
76
114
  await handleUpDataplane(options);
77
115
  } catch (error) {
116
+ if (isAuthenticationError(error)) {
117
+ const controllerUrl = error.controllerUrl || await resolveControllerUrl();
118
+ logger.log(chalk.blue('\nAuthentication required. Running aifabrix login...\n'));
119
+ try {
120
+ await handleLogin({ method: 'device', controller: controllerUrl });
121
+ await handleUpDataplane(options);
122
+ return;
123
+ } catch (loginOrRetryError) {
124
+ handleCommandError(loginOrRetryError, 'up-platform');
125
+ process.exit(1);
126
+ }
127
+ }
78
128
  handleCommandError(error, 'up-platform');
79
129
  process.exit(1);
80
130
  }
@@ -107,6 +157,18 @@ function setupUpDataplaneCommand(program) {
107
157
  try {
108
158
  await handleUpDataplane(options);
109
159
  } catch (error) {
160
+ if (isAuthenticationError(error)) {
161
+ const controllerUrl = error.controllerUrl || await resolveControllerUrl();
162
+ logger.log(chalk.blue('\nAuthentication required. Running aifabrix login...\n'));
163
+ try {
164
+ await handleLogin({ method: 'device', controller: controllerUrl });
165
+ await handleUpDataplane(options);
166
+ return;
167
+ } catch (loginOrRetryError) {
168
+ handleCommandError(loginOrRetryError, 'up-dataplane');
169
+ process.exit(1);
170
+ }
171
+ }
110
172
  handleCommandError(error, 'up-dataplane');
111
173
  process.exit(1);
112
174
  }
@@ -148,7 +210,12 @@ function setupDoctorCommand(program) {
148
210
  }
149
211
  if (result.docker === 'ok') {
150
212
  try {
151
- const health = await infra.checkInfraHealth();
213
+ const cfg = await config.getConfig();
214
+ const health = await infra.checkInfraHealth(null, {
215
+ pgadmin: cfg.pgadmin !== false,
216
+ redisCommander: cfg.redisCommander !== false,
217
+ traefik: !!cfg.traefik
218
+ });
152
219
  logger.log('\n🏥 Infrastructure Health:');
153
220
  Object.entries(health).forEach(([service, status]) => {
154
221
  const icon = status === 'healthy' ? '✅' : status === 'unknown' ? '❓' : '❌';