@aifabrix/builder 2.44.4 → 2.44.6

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 (214) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +68 -17
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/types/wizard.types.js +2 -1
  17. package/lib/api/validation-runner.js +46 -25
  18. package/lib/app/deploy-config.js +11 -1
  19. package/lib/app/deploy-status-display.js +3 -3
  20. package/lib/app/deploy.js +36 -14
  21. package/lib/app/display.js +15 -11
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +1 -1
  24. package/lib/app/restart-display.js +95 -0
  25. package/lib/app/rotate-secret.js +1 -1
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +44 -12
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +99 -73
  32. package/lib/build/index.js +75 -45
  33. package/lib/cli/doctor-check.js +117 -0
  34. package/lib/cli/index.js +8 -2
  35. package/lib/cli/infra-guided.js +445 -0
  36. package/lib/cli/setup-app.help.js +1 -1
  37. package/lib/cli/setup-app.js +20 -2
  38. package/lib/cli/setup-app.test-commands.js +9 -5
  39. package/lib/cli/setup-auth.js +26 -0
  40. package/lib/cli/setup-dev-path-commands.js +50 -3
  41. package/lib/cli/setup-infra.js +138 -61
  42. package/lib/cli/setup-integration-client.js +182 -0
  43. package/lib/cli/setup-parameters.js +21 -2
  44. package/lib/cli/setup-platform.js +102 -0
  45. package/lib/cli/setup-secrets.js +18 -6
  46. package/lib/cli/setup-utility.js +97 -33
  47. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  48. package/lib/commands/datasource-capability-output.js +29 -0
  49. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  50. package/lib/commands/datasource-capability.js +411 -0
  51. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  52. package/lib/commands/datasource.js +53 -13
  53. package/lib/commands/dev-down.js +3 -3
  54. package/lib/commands/dev-infra-gate.js +32 -0
  55. package/lib/commands/dev-init.js +13 -7
  56. package/lib/commands/dimension-value.js +179 -0
  57. package/lib/commands/dimension.js +330 -0
  58. package/lib/commands/integration-client.js +430 -0
  59. package/lib/commands/login-device.js +65 -30
  60. package/lib/commands/login.js +21 -10
  61. package/lib/commands/parameters-validate.js +78 -13
  62. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  63. package/lib/commands/repair-datasource-keys.js +10 -5
  64. package/lib/commands/repair-datasource.js +19 -7
  65. package/lib/commands/repair-env-template.js +4 -1
  66. package/lib/commands/repair-openapi-sync.js +172 -0
  67. package/lib/commands/repair-persist.js +102 -0
  68. package/lib/commands/repair-rbac-extract.js +27 -0
  69. package/lib/commands/repair-rbac-migrate.js +186 -0
  70. package/lib/commands/repair-rbac.js +225 -19
  71. package/lib/commands/repair-system-alignment.js +246 -0
  72. package/lib/commands/repair-system-permissions.js +168 -0
  73. package/lib/commands/repair.js +120 -354
  74. package/lib/commands/secure.js +1 -1
  75. package/lib/commands/setup-modes.js +455 -0
  76. package/lib/commands/setup-prompts.js +388 -0
  77. package/lib/commands/setup.js +149 -0
  78. package/lib/commands/teardown.js +228 -0
  79. package/lib/commands/test-e2e-external.js +4 -3
  80. package/lib/commands/up-common.js +97 -12
  81. package/lib/commands/up-dataplane.js +33 -11
  82. package/lib/commands/up-miso.js +7 -11
  83. package/lib/commands/upload.js +109 -23
  84. package/lib/commands/wizard-core-helpers.js +14 -11
  85. package/lib/commands/wizard-core.js +58 -15
  86. package/lib/commands/wizard-dataplane.js +2 -2
  87. package/lib/commands/wizard-entity-selection.js +72 -14
  88. package/lib/commands/wizard-headless.js +7 -3
  89. package/lib/commands/wizard-helpers.js +13 -1
  90. package/lib/commands/wizard.js +210 -61
  91. package/lib/constants/infra-compose-service-names.js +40 -0
  92. package/lib/core/env-reader.js +16 -3
  93. package/lib/core/secrets-admin-env.js +101 -0
  94. package/lib/core/secrets-ensure-infra.js +34 -1
  95. package/lib/core/secrets-ensure.js +88 -66
  96. package/lib/core/secrets-env-content.js +432 -0
  97. package/lib/core/secrets-env-write.js +27 -1
  98. package/lib/core/secrets-load.js +248 -0
  99. package/lib/core/secrets-names.js +32 -0
  100. package/lib/core/secrets.js +17 -757
  101. package/lib/datasource/capability/basic-exposure.js +76 -0
  102. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  103. package/lib/datasource/capability/capability-key.js +34 -0
  104. package/lib/datasource/capability/capability-resolve.js +172 -0
  105. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  106. package/lib/datasource/capability/copy-operations.js +348 -0
  107. package/lib/datasource/capability/copy-test-payload.js +139 -0
  108. package/lib/datasource/capability/create-operations.js +235 -0
  109. package/lib/datasource/capability/dimension-operations.js +151 -0
  110. package/lib/datasource/capability/dimension-validate.js +219 -0
  111. package/lib/datasource/capability/json-pointer.js +31 -0
  112. package/lib/datasource/capability/reference-rewrite.js +51 -0
  113. package/lib/datasource/capability/relate-operations.js +325 -0
  114. package/lib/datasource/capability/relate-validate.js +219 -0
  115. package/lib/datasource/capability/remove-operations.js +275 -0
  116. package/lib/datasource/capability/run-capability-copy.js +152 -0
  117. package/lib/datasource/capability/run-capability-diff.js +135 -0
  118. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  119. package/lib/datasource/capability/run-capability-edit.js +377 -0
  120. package/lib/datasource/capability/run-capability-relate.js +193 -0
  121. package/lib/datasource/capability/run-capability-remove.js +105 -0
  122. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  123. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  124. package/lib/datasource/list.js +136 -23
  125. package/lib/datasource/log-viewer.js +2 -4
  126. package/lib/datasource/unified-validation-run.js +51 -16
  127. package/lib/datasource/validate.js +53 -1
  128. package/lib/deployment/deploy-poll-ui.js +60 -0
  129. package/lib/deployment/deployer-status.js +29 -3
  130. package/lib/deployment/deployer.js +48 -30
  131. package/lib/deployment/environment.js +7 -2
  132. package/lib/deployment/poll-interval.js +72 -0
  133. package/lib/deployment/push.js +11 -9
  134. package/lib/external-system/deploy.js +4 -1
  135. package/lib/external-system/download.js +61 -32
  136. package/lib/external-system/sync-deploy-manifest.js +33 -0
  137. package/lib/generator/wizard-prompts.js +7 -1
  138. package/lib/generator/wizard.js +34 -0
  139. package/lib/infrastructure/index.js +49 -19
  140. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  141. package/lib/parameters/infra-kv-discovery.js +29 -4
  142. package/lib/parameters/infra-parameter-catalog.js +6 -3
  143. package/lib/parameters/infra-parameter-validate.js +67 -19
  144. package/lib/resolvers/datasource-resolver.js +53 -0
  145. package/lib/resolvers/dimension-file.js +52 -0
  146. package/lib/resolvers/manifest-resolver.js +133 -0
  147. package/lib/schema/external-datasource.schema.json +183 -53
  148. package/lib/schema/external-system.schema.json +23 -10
  149. package/lib/schema/infra.parameter.yaml +26 -11
  150. package/lib/schema/wizard-config.schema.json +2 -2
  151. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  152. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  153. package/lib/utils/app-run-containers.js +2 -2
  154. package/lib/utils/bash-secret-env.js +59 -0
  155. package/lib/utils/cli-secrets-error-format.js +78 -0
  156. package/lib/utils/cli-test-layout-chalk.js +31 -9
  157. package/lib/utils/cli-utils.js +4 -36
  158. package/lib/utils/datasource-test-run-display.js +8 -0
  159. package/lib/utils/dev-hosts-helper.js +3 -2
  160. package/lib/utils/dev-init-ssh-merge.js +2 -1
  161. package/lib/utils/docker-build.js +17 -9
  162. package/lib/utils/docker-reload-mount.js +127 -0
  163. package/lib/utils/external-readme.js +117 -4
  164. package/lib/utils/external-system-local-test-tty.js +3 -2
  165. package/lib/utils/external-system-readiness-core.js +45 -12
  166. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  167. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  168. package/lib/utils/external-system-readiness-display.js +10 -1
  169. package/lib/utils/file-upload.js +40 -3
  170. package/lib/utils/health-check-db-init.js +107 -0
  171. package/lib/utils/health-check-public-warn.js +69 -0
  172. package/lib/utils/health-check-url.js +19 -4
  173. package/lib/utils/health-check.js +135 -105
  174. package/lib/utils/help-builder.js +5 -1
  175. package/lib/utils/image-name.js +34 -7
  176. package/lib/utils/integration-file-backup.js +74 -0
  177. package/lib/utils/mutagen-install.js +30 -3
  178. package/lib/utils/paths.js +108 -25
  179. package/lib/utils/postgres-wipe.js +212 -0
  180. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  181. package/lib/utils/remote-dev-auth.js +21 -5
  182. package/lib/utils/remote-docker-env.js +9 -1
  183. package/lib/utils/remote-secrets-loader.js +42 -3
  184. package/lib/utils/resolve-docker-image-ref.js +9 -3
  185. package/lib/utils/secrets-ancestor-paths.js +47 -0
  186. package/lib/utils/secrets-helpers.js +17 -10
  187. package/lib/utils/secrets-kv-refs.js +42 -0
  188. package/lib/utils/secrets-kv-scope.js +19 -2
  189. package/lib/utils/secrets-materialize-local.js +134 -0
  190. package/lib/utils/secrets-path.js +24 -10
  191. package/lib/utils/secrets-utils.js +2 -2
  192. package/lib/utils/system-builder-root.js +34 -0
  193. package/lib/utils/url-declarative-resolve-build.js +6 -1
  194. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  195. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  196. package/lib/utils/urls-local-registry.js +73 -20
  197. package/lib/utils/validation-poll-ui.js +81 -0
  198. package/lib/utils/validation-run-poll.js +29 -5
  199. package/lib/utils/with-muted-logger.js +53 -0
  200. package/package.json +1 -1
  201. package/templates/applications/dataplane/application.yaml +1 -1
  202. package/templates/applications/dataplane/rbac.yaml +10 -10
  203. package/templates/applications/keycloak/env.template +8 -6
  204. package/templates/applications/miso-controller/application.yaml +7 -0
  205. package/templates/applications/miso-controller/env.template +7 -7
  206. package/templates/applications/miso-controller/rbac.yaml +9 -9
  207. package/templates/external-system/README.md.hbs +89 -102
  208. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  209. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  210. package/.nyc_output/processinfo/index.json +0 -1
  211. package/lib/api/service-users.api.js +0 -150
  212. package/lib/api/types/service-users.types.js +0 -65
  213. package/lib/cli/setup-service-user.js +0 -187
  214. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,445 @@
1
+ /**
2
+ * @fileoverview Guided (non-verbose) UX for infra/platform commands.
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const chalk = require('chalk');
8
+ const logger = require('../utils/logger');
9
+ const config = require('../core/config');
10
+ const devConfig = require('../utils/dev-config');
11
+ const pathsUtil = require('../utils/paths');
12
+ const { loadConfigFile } = require('../utils/config-format');
13
+ const { resolveControllerUrl } = require('../utils/controller-url');
14
+ const { withMutedLogger } = require('../utils/with-muted-logger');
15
+ const { formatBlockingError, formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
16
+ const { computePublicUrlBaseString } = require('../utils/url-declarative-public-base');
17
+ const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('../utils/health-check-url');
18
+ const ora = require('ora');
19
+ const infra = require('../infrastructure');
20
+ const { execWithDockerEnv } = require('../utils/docker-exec');
21
+ const { getLogLevel, passesLevelFilter } = require('../commands/app-logs');
22
+ const { handleLogin } = require('../commands/login');
23
+ const healthCheck = require('../utils/health-check');
24
+ const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
25
+ const { assertDevInfraUp } = require('../commands/dev-infra-gate');
26
+
27
+ const SEPARATOR = '────────────────────────────────────────';
28
+
29
+ function title(text) {
30
+ return chalk.bold(text);
31
+ }
32
+
33
+ function shouldUseSpinner() {
34
+ return Boolean(process && process.stdout && process.stdout.isTTY);
35
+ }
36
+
37
+ function startSpinner(text) {
38
+ if (!shouldUseSpinner()) {
39
+ logger.log(formatProgress(text));
40
+ return null;
41
+ }
42
+ return ora({ text, spinner: 'dots' }).start();
43
+ }
44
+
45
+ function stopSpinnerSuccess(spinner, text) {
46
+ if (!spinner) {
47
+ logger.log(formatSuccessLine(text));
48
+ return;
49
+ }
50
+ spinner.succeed(text);
51
+ }
52
+
53
+ async function getRunningContainerNameForApp(appName) {
54
+ const apps = await infra.getAppStatus();
55
+ const hit = (apps || []).find(a => a && a.name === appName && a.container);
56
+ return hit ? hit.container : null;
57
+ }
58
+
59
+ async function hasErrorLogs(appName, opts = {}) {
60
+ const tailLines = Number.isFinite(opts.tailLines) ? opts.tailLines : 200;
61
+ const container = await getRunningContainerNameForApp(appName);
62
+ if (!container) return false;
63
+ const { stdout } = await execWithDockerEnv(`docker logs --tail ${tailLines} ${container}`);
64
+ const lines = String(stdout || '').split('\n');
65
+ return lines.some(line => passesLevelFilter(getLogLevel(line), 'error'));
66
+ }
67
+
68
+ async function validateAppErrorLogs(appNames) {
69
+ const names = Array.isArray(appNames) ? appNames : [];
70
+ const results = await Promise.all(
71
+ names.map(async(name) => {
72
+ try {
73
+ const hasErrors = await hasErrorLogs(name, { tailLines: 300 });
74
+ return { name, hasErrors };
75
+ } catch {
76
+ return { name, hasErrors: false };
77
+ }
78
+ })
79
+ );
80
+ const bad = results.filter(r => r && r.hasErrors);
81
+ if (bad.length === 0) return;
82
+
83
+ logger.log(chalk.yellow('⚠ Some services reported error logs'));
84
+ for (const r of bad) {
85
+ logger.log(chalk.gray(` Run: aifabrix logs ${r.name} -l error`));
86
+ }
87
+ logger.log('');
88
+ }
89
+
90
+ async function waitForAppReady(appName, opts = {}) {
91
+ const timeoutSeconds = Number.isFinite(opts.timeoutSeconds) ? opts.timeoutSeconds : 90;
92
+ const builderPath = pathsUtil.getBuilderPath(appName);
93
+ const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
94
+ const appConfig = loadConfigFile(configPath) || {};
95
+
96
+ const apps = await infra.getAppStatus();
97
+ const hit = (apps || []).find(a => a && a.name === appName && typeof a.url === 'string');
98
+ const portMatch = hit && hit.url ? hit.url.match(/:(\d+)$/) : null;
99
+ const hostPort = portMatch ? parseInt(portMatch[1], 10) : null;
100
+ if (!hostPort) {
101
+ throw new Error(`Could not determine host port for ${appName}`);
102
+ }
103
+
104
+ const userCfg = await config.getConfig();
105
+ const healthRunOpts = { profile: 'docker' };
106
+ if (userCfg && userCfg.traefik === true) {
107
+ healthRunOpts.traefikEnabled = true;
108
+ }
109
+ await healthCheck.waitForHealthCheck(appName, timeoutSeconds, hostPort, appConfig, false, healthRunOpts);
110
+ }
111
+
112
+ function resolveInfraHost(remoteServer) {
113
+ const raw = String(remoteServer || '').trim().replace(/\/+$/, '');
114
+ if (!raw) return 'localhost';
115
+ try {
116
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
117
+ const u = new URL(withScheme);
118
+ return u.hostname || 'localhost';
119
+ } catch {
120
+ // Fallback for non-URL strings.
121
+ return raw.replace(/^https?:\/\//i, '').split('/')[0].split(':')[0] || 'localhost';
122
+ }
123
+ }
124
+
125
+ async function computeAppBaseUrl(appName) {
126
+ const builderPath = pathsUtil.getBuilderPath(appName);
127
+ const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
128
+ const variables = loadConfigFile(configPath) || {};
129
+ const basePort = Number(variables.port || 3000);
130
+ const developerIdRaw = await config.getDeveloperId();
131
+ const developerIdNum = typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
132
+ const userCfg = await config.getConfig();
133
+ const remoteServer = await config.getRemoteServer();
134
+ const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
135
+
136
+ const fd = variables.frontDoorRouting || null;
137
+ const frontDoorEnabled = Boolean(fd && fd.enabled === true);
138
+ const traefikOn = Boolean(userCfg && userCfg.traefik);
139
+ const pathActive = Boolean(traefikOn && frontDoorEnabled);
140
+
141
+ const publicBase = computePublicUrlBaseString({
142
+ traefik: traefikOn,
143
+ pathActive,
144
+ hostTemplate: fd ? fd.host : null,
145
+ tls: fd ? fd.tls : true,
146
+ developerIdRaw,
147
+ remoteServer,
148
+ // These guided bootstrap commands start services via Docker Compose, so we want the
149
+ // docker-published host port (no workstation +10 offset).
150
+ profile: 'docker',
151
+ listenPort: basePort,
152
+ developerIdNum,
153
+ infraTlsEnabled
154
+ });
155
+
156
+ if (pathActive) {
157
+ const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
158
+ return joinUrlPath(publicBase, mount);
159
+ }
160
+
161
+ // No Traefik front-door routing: publicBase already includes correct scheme/host/port (localhost or remote-server).
162
+ return String(publicBase).replace(/\/+$/, '');
163
+ }
164
+
165
+ function logSection(label, value) {
166
+ logger.log(title(label));
167
+ logger.log(` ${value}`);
168
+ logger.log('');
169
+ }
170
+
171
+ function logFooterStart(label) {
172
+ logger.log('');
173
+ logger.log(SEPARATOR);
174
+ logger.log(title(label));
175
+ logger.log(SEPARATOR);
176
+ logger.log('');
177
+ }
178
+
179
+ function logFooterEnd() {
180
+ logger.log(SEPARATOR);
181
+ }
182
+
183
+ async function logPlatformReadyFooter() {
184
+ const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
185
+ resolveControllerUrl(),
186
+ computeAppBaseUrl('dataplane'),
187
+ computeAppBaseUrl('keycloak')
188
+ ]);
189
+ const cfg = await config.getConfig();
190
+ const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
191
+
192
+ logFooterStart('Platform Ready');
193
+ logSection('Environment', env);
194
+ logSection('Miso Controller', controllerUrl);
195
+ logSection('Dataplane (DEV)', dataplaneUrl);
196
+ logSection('Keycloak', keycloakUrl);
197
+ logSection('API Documentation', `${dataplaneUrl}/api/docs`);
198
+ logSection('Knowledge Base', 'https://docs.aifabrix.ai');
199
+ logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
200
+ logFooterEnd();
201
+ }
202
+
203
+ async function logMisoReadyFooter() {
204
+ const [controllerUrl, keycloakUrl] = await Promise.all([
205
+ resolveControllerUrl(),
206
+ computeAppBaseUrl('keycloak')
207
+ ]);
208
+ logFooterStart('Miso Ready');
209
+ logSection('Miso Controller', controllerUrl);
210
+ logSection('Keycloak', keycloakUrl);
211
+ logFooterEnd();
212
+ }
213
+
214
+ async function logDataplaneReadyFooter() {
215
+ const dataplaneUrl = await computeAppBaseUrl('dataplane');
216
+ const cfg = await config.getConfig();
217
+ const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
218
+ logFooterStart('Dataplane Ready');
219
+ logSection('Environment', env);
220
+ logSection('Dataplane (DEV)', dataplaneUrl);
221
+ logSection('API Documentation', `${dataplaneUrl}/api/docs`);
222
+ logFooterEnd();
223
+ }
224
+
225
+ async function getInfraHostAndPorts() {
226
+ const developerId = await config.getDeveloperId();
227
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
228
+ const ports = devConfig.getDevPorts(idNum);
229
+ const remoteServer = await config.getRemoteServer();
230
+ const host = resolveInfraHost(remoteServer);
231
+ return { idNum, ports, host };
232
+ }
233
+
234
+ async function runGuidedUpInfra(options, runUpInfraCommand) {
235
+ logger.log('');
236
+ logger.log(title('AI Fabrix Infrastructure Setup'));
237
+ logger.log(SEPARATOR);
238
+ logger.log('');
239
+ const spin = startSpinner('Starting infrastructure services...');
240
+ await withMutedLogger(() => runUpInfraCommand(options), {
241
+ allow: (msg) => {
242
+ if (typeof msg !== 'string') return false;
243
+ return (
244
+ msg.includes('✔ Developer ID set to') ||
245
+ msg.includes('✔ Traefik enabled and saved to config') ||
246
+ msg.includes('✔ Traefik disabled and saved to config') ||
247
+ msg.includes('✔ pgAdmin enabled and saved to config') ||
248
+ msg.includes('✔ pgAdmin disabled and saved to config') ||
249
+ msg.includes('✔ Redis Commander enabled and saved to config') ||
250
+ msg.includes('✔ Redis Commander disabled and saved to config')
251
+ );
252
+ }
253
+ });
254
+ stopSpinnerSuccess(spin, 'Infrastructure ready');
255
+
256
+ const [{ idNum, ports, host }, cfg] = await Promise.all([
257
+ getInfraHostAndPorts(),
258
+ config.getConfig()
259
+ ]);
260
+
261
+ logFooterStart('Infra Ready');
262
+ logSection('Developer', `dev${String(idNum).padStart(2, '0')} (id: ${idNum})`);
263
+ logSection('Postgres', `${host}:${ports.postgres}`);
264
+ logSection('Redis', `${host}:${ports.redis}`);
265
+ if (cfg.pgadmin !== false) logSection('pgAdmin', `http://${host}:${ports.pgadmin}`);
266
+ if (cfg.redisCommander !== false) logSection('Redis Commander', `http://${host}:${ports.redisCommander}`);
267
+ if (cfg.traefik) logSection('Traefik', `http://${host}:${ports.traefikHttp}`);
268
+ if (cfg.tlsEnabled === true) logSection('TLS mode', 'on');
269
+ logFooterEnd();
270
+ }
271
+
272
+ async function runGuidedUpMiso(options, handleUpMiso) {
273
+ logger.log('');
274
+ logger.log(title('AI Fabrix Platform Setup'));
275
+ logger.log(SEPARATOR);
276
+ logger.log('');
277
+ const kcSpin = startSpinner('Starting Keycloak...');
278
+ await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
279
+ stopSpinnerSuccess(kcSpin, 'Keycloak ready');
280
+ logger.log('');
281
+ const mcSpin = startSpinner('Starting Miso Controller...');
282
+ await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
283
+ stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
284
+ await logMisoReadyFooter();
285
+ await validateAppErrorLogs(['miso-controller']);
286
+ }
287
+
288
+ async function runGuidedUpDataplane(options, handleUpDataplane) {
289
+ logger.log('');
290
+ logger.log(title('AI Fabrix Dataplane Setup'));
291
+ logger.log(SEPARATOR);
292
+ logger.log('');
293
+ // Infra must be checked before auth: device login talks to the controller, which needs
294
+ // Postgres/Redis when the controller runs locally — a down infra looks like "network error".
295
+ const infraSpin = startSpinner('Checking infrastructure...');
296
+ try {
297
+ await assertDevInfraUp({ quietSuccess: true });
298
+ stopSpinnerSuccess(infraSpin, 'Infrastructure is up');
299
+ } catch (infraErr) {
300
+ // Match up-miso: one error block from handleCommandError only (no spinner.fail / extra hints).
301
+ if (infraSpin) infraSpin.stop();
302
+ throw infraErr;
303
+ }
304
+ // Avoid an outer long-running spinner here: up-dataplane has multiple internal
305
+ // phases and may print some output. An outer spinner causes "spinner over spinner"
306
+ // rendering artifacts. Instead, do the same single auth step as up-platform, then
307
+ // show one spinner for the dataplane install/run phase.
308
+ await runGuidedAuthStep(handleLogin);
309
+
310
+ logger.log('');
311
+ const dpSpin = startSpinner('Starting Dataplane...');
312
+ await withMutedLogger(() =>
313
+ handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
314
+ );
315
+ stopSpinnerSuccess(dpSpin, 'Dataplane ready');
316
+ await validateAppErrorLogs(['dataplane']);
317
+ await logDataplaneReadyFooter();
318
+ }
319
+
320
+ /**
321
+ * Guided-mode summary after `up-platform --force` (printed under the platform header).
322
+ * @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
323
+ * @param {string[]} cleanedApps
324
+ */
325
+ function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
326
+ const check = successGlyph();
327
+ logger.log(chalk.bold('Clean installation files'));
328
+ logger.log(
329
+ chalk.gray(
330
+ ` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
331
+ )
332
+ );
333
+ logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
334
+ logger.log(
335
+ chalk.gray(
336
+ ` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
337
+ )
338
+ );
339
+ for (const appName of cleanedApps || []) {
340
+ logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
341
+ }
342
+ logger.log('');
343
+ }
344
+
345
+ function logGuidedPlatformSetupHeader() {
346
+ logger.log('');
347
+ logger.log(title('AI Fabrix Platform Setup'));
348
+ logger.log(SEPARATOR);
349
+ logger.log('');
350
+ }
351
+
352
+ async function runGuidedAuthStep(handleLogin) {
353
+ const spin = startSpinner('Authenticating...');
354
+ try {
355
+ const controllerUrl = await resolveControllerUrl();
356
+ await handleLogin({ method: 'device', controller: controllerUrl, environment: 'dev', compact: true });
357
+ } catch (authErr) {
358
+ if (spin) spin.fail('Authentication failed');
359
+ logger.error(formatBlockingError('Authentication failed'));
360
+ logger.log(chalk.gray('\nRun:\n af login\n\nThen retry:\n af up-platform\n'));
361
+ throw authErr;
362
+ }
363
+ // Compact device-login already prints a success line; don't duplicate it.
364
+ if (spin) spin.stop();
365
+ }
366
+
367
+ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, handleLogin, forceCleanSummary = null) {
368
+ logGuidedPlatformSetupHeader();
369
+ if (forceCleanSummary && forceCleanSummary.forceSummary) {
370
+ logUpPlatformForceCleanSummary(forceCleanSummary.forceSummary, forceCleanSummary.cleanedApps);
371
+ }
372
+
373
+ await withMutedLogger(() => prepareUrlsLocalRegistryForUpPlatform());
374
+
375
+ const kcSpin = startSpinner('Starting Keycloak...');
376
+ await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
377
+ stopSpinnerSuccess(kcSpin, 'Keycloak ready');
378
+ logger.log('');
379
+
380
+ const mcSpin = startSpinner('Starting Miso Controller...');
381
+ await waitForAppReady('miso-controller', { timeoutSeconds: 120 });
382
+ stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
383
+ logger.log('');
384
+
385
+ await runGuidedAuthStep(handleLogin);
386
+
387
+ logger.log('');
388
+ const dpSpin = startSpinner('Starting Dataplane...');
389
+ await withMutedLogger(() =>
390
+ handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
391
+ );
392
+ stopSpinnerSuccess(dpSpin, 'Dataplane ready');
393
+ await validateAppErrorLogs(['miso-controller', 'dataplane']);
394
+ await logPlatformReadyFooter();
395
+ }
396
+
397
+ function logStoppedServices(cfg) {
398
+ logger.log(title('Stopped'));
399
+ logger.log(' postgres');
400
+ logger.log(' redis');
401
+ if (cfg.pgadmin !== false) logger.log(' pgadmin');
402
+ if (cfg.redisCommander !== false) logger.log(' redis-commander');
403
+ if (cfg.traefik) logger.log(' traefik');
404
+ logger.log('');
405
+ }
406
+
407
+ async function runGuidedDownInfra(appName, options, infra, appLib) {
408
+ logger.log('');
409
+ logger.log(title('AI Fabrix Shutdown'));
410
+ logger.log(SEPARATOR);
411
+ logger.log('');
412
+
413
+ const spin = startSpinner('Stopping infrastructure...');
414
+ await withMutedLogger(async() => {
415
+ if (typeof appName === 'string' && appName.trim().length > 0) {
416
+ await appLib.downApp(appName, { volumes: !!options.volumes });
417
+ return;
418
+ }
419
+ if (options.volumes) await infra.stopInfraWithVolumes();
420
+ else await infra.stopInfra();
421
+ });
422
+ stopSpinnerSuccess(spin, 'Infrastructure stopped');
423
+
424
+ const developerId = await config.getDeveloperId();
425
+ const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
426
+ const cfg = await config.getConfig();
427
+
428
+ logFooterStart('Stopped');
429
+ logSection('Developer', `dev${String(idNum).padStart(2, '0')} (id: ${idNum})`);
430
+ logStoppedServices(cfg);
431
+ if (options.volumes) {
432
+ logger.log(chalk.yellow('⚠ Volumes removed: all local data deleted'));
433
+ logger.log('');
434
+ }
435
+ logFooterEnd();
436
+ }
437
+
438
+ module.exports = {
439
+ runGuidedUpInfra,
440
+ runGuidedUpMiso,
441
+ runGuidedUpDataplane,
442
+ runGuidedUpPlatform,
443
+ runGuidedDownInfra
444
+ };
445
+
@@ -56,7 +56,7 @@ Examples:
56
56
  Notes:
57
57
  - To run E2E for one datasource, use:
58
58
  aifabrix datasource test-e2e <datasourceKey>
59
- - Optional --sync publishes local files to the dataplane first (external integration only).
59
+ - External integration: local system and datasource files are published to the dataplane before E2E (same as upload). Use --no-sync to exercise only config already deployed. The old --sync flag is a no-op (kept for scripts).
60
60
  `;
61
61
 
62
62
  module.exports = {
@@ -31,6 +31,19 @@ Example:
31
31
  $ aifabrix push myapp -t v1.0.0
32
32
  `;
33
33
 
34
+ const LOGS_HELP_AFTER = `
35
+ Examples:
36
+ $ aifabrix logs miso-controller -l error
37
+ $ aifabrix logs miso-controller -f
38
+ $ aifabrix logs dataplane -l warn
39
+ $ aifabrix logs dataplane -f -t 100
40
+ $ aifabrix logs keycloak -f -t 0
41
+
42
+ Notes:
43
+ - Platform apps use names such as miso-controller, dataplane, keycloak (see aifabrix run).
44
+ - Use -t 0 (--tail 0) for the full buffered log; default is 100 lines.
45
+ `;
46
+
34
47
  /**
35
48
  * Normalize options for external system creation
36
49
  * @param {Object} options - Raw CLI options
@@ -188,11 +201,12 @@ See integration/hubspot-test/wizard-hubspot-e2e.yaml for an example.`;
188
201
 
189
202
  function registerRunCommand(program) {
190
203
  const runHelp = `
191
- In dev: use --reload for sync and mount (requires remote server with Mutagen, or local Docker).
204
+ In dev: use --reload for live code in the container. Before start, the CLI prints a "Reload (dev)" section: direct bind mount when the Docker engine is on this machine, or Mutagen when the engine is remote.
192
205
  Examples:
193
206
  $ aifabrix run myapp
194
207
  $ aifabrix run myapp --env tst
195
208
  $ aifabrix run myapp --tag v1.0.0
209
+ $ aifabrix run myapp --base
196
210
  $ aifabrix run myapp --reload`;
197
211
  program.command('run <app>')
198
212
  .description('Run app locally or on remote Docker host')
@@ -200,7 +214,8 @@ Examples:
200
214
  .option('-d, --debug', 'Enable debug output with detailed container information')
201
215
  .option('-t, --tag <tag>', 'Image tag to run (e.g. v1.0.0); overrides application.yaml image.tag')
202
216
  .option('-e, --env <env>', 'Environment: dev (default), tst, or pro', 'dev')
203
- .option('--reload', 'In dev: use sync and mount (requires remote server; Mutagen or local Docker)')
217
+ .option('--base', 'Use manifest base image only (skip local developer-scoped tag preference)')
218
+ .option('--reload', 'In dev: mount workspace into container (Mutagen only if docker-endpoint is a remote host)')
204
219
  .addHelpText('after', runHelp)
205
220
  .action(async(appName, options) => {
206
221
  try {
@@ -219,6 +234,7 @@ function setupBuildRunLogsDownCommands(program) {
219
234
  .option('-f, --force-template', 'Force rebuild from template')
220
235
  .option('--no-cache', 'Full Docker rebuild (disable layer cache); use after Dockerfile or context fixes')
221
236
  .option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in application.yaml to match for deploy.')
237
+ .option('--base', 'Also tag the manifest base image name (after developer-scoped build when developer id > 0)')
222
238
  .action(async(appName, options) => {
223
239
  try {
224
240
  const imageTag = await app.buildApp(appName, options);
@@ -236,6 +252,7 @@ function setupBuildRunLogsDownCommands(program) {
236
252
  .option('-f', 'Follow log stream')
237
253
  .option('-t, --tail <lines>', 'Number of lines (default: 100); 0 = full list', '100')
238
254
  .option('-l, --level <level>', 'Show only logs at this level or above (debug|info|warn|error)')
255
+ .addHelpText('after', LOGS_HELP_AFTER)
239
256
  .action(async(appName, options) => {
240
257
  try {
241
258
  const { runAppLogs } = require('../commands/app-logs');
@@ -328,6 +345,7 @@ function setupPushDeployDockerfileCommands(program) {
328
345
  .option('--local', 'Send manifest to controller then run app locally (app: same as aifabrix run <app>; external: restart dataplane)')
329
346
  .option('--client-id <id>', 'Client ID (overrides config)')
330
347
  .option('--client-secret <secret>', 'Client Secret (overrides config)')
348
+ .option('--repository-url <url>', 'Repository URL for pipeline validation (default: application.yaml repository.repositoryUrl, else https://github.com/aifabrix/<appKey>)')
331
349
  .option('--poll', 'Poll for deployment status', true)
332
350
  .option('--no-poll', 'Do not poll for status')
333
351
  .option('--probe', 'After external deploy, run dataplane runtime checks (validation/run); slower')
@@ -91,7 +91,7 @@ async function runExternalIntegrationE2EAndCertSync(appName, options) {
91
91
  debug: options.debug,
92
92
  verbose: options.verbose,
93
93
  async: options.async !== false,
94
- sync: options.sync === true
94
+ noSync: options.noSync === true
95
95
  });
96
96
  const { displayIntegrationTestResults } = require('../utils/external-system-display');
97
97
  const datasourceResults = results.map(r => ({
@@ -121,10 +121,10 @@ async function runExternalIntegrationE2EAndCertSync(appName, options) {
121
121
  async function runTestE2ECommand(appName, options) {
122
122
  const pathsUtil = require('../utils/paths');
123
123
  const appType = await pathsUtil.detectAppType(appName).catch(() => null);
124
- if (options.sync === true && appType && appType.baseDir === 'builder') {
124
+ if (options.noSync === true && appType && appType.baseDir === 'builder') {
125
125
  throw new Error(
126
- 'Option --sync applies only to external integration E2E (integration/<systemKey>/). ' +
127
- 'Remove --sync for builder app E2E, or use aifabrix upload from the integration folder first.'
126
+ 'Option --no-sync applies only to external integration E2E (integration/<systemKey>/). ' +
127
+ 'Remove --no-sync for builder app E2E.'
128
128
  );
129
129
  }
130
130
  if (appType && appType.baseDir === 'integration') {
@@ -166,7 +166,11 @@ function setupTestE2eCommand(program) {
166
166
  .option('-d, --debug', 'Include debug output and write log to integration/<systemKey>/logs/')
167
167
  .option(
168
168
  '--sync',
169
- 'Publish local system and datasource files to the dataplane before running E2E (same as aifabrix upload <systemKey>; external integration only)'
169
+ '(Deprecated; no-op.) Local integration files are published before E2E by default; use --no-sync to skip.'
170
+ )
171
+ .option(
172
+ '--no-sync',
173
+ 'Skip publishing local integration files; E2E uses the system config already on the dataplane (external integration only)'
170
174
  )
171
175
  .option('--warnings-as-errors', 'Treat aggregate warn as failure (exit 1)')
172
176
  .option('--require-cert', 'Require certification passed on every datasource (exit 2 if not)')
@@ -23,6 +23,9 @@ Examples:
23
23
  const AUTH_HELP_AFTER = `
24
24
  Without options: show auth status (same as: aifabrix auth status).
25
25
  With --set-controller or --set-environment: write defaults to config.yaml.
26
+ Aliases:
27
+ aifabrix auth set-controller <url>
28
+ aifabrix auth set-environment <env>
26
29
  Subcommand: auth status [--validate] for CI/scripts.
27
30
  `;
28
31
 
@@ -95,6 +98,29 @@ function setupAuthSubcommands(program) {
95
98
  process.exit(1);
96
99
  }
97
100
  });
101
+
102
+ auth.command('set-controller <url>')
103
+ .description('Set default controller URL in config (alias for auth --set-controller)')
104
+ .action(async(url) => {
105
+ try {
106
+ await handleAuthConfig({ setController: url });
107
+ } catch (error) {
108
+ handleCommandError(error, 'auth set-controller');
109
+ process.exit(1);
110
+ }
111
+ });
112
+
113
+ auth.command('set-environment <env>')
114
+ .description('Set default environment in config (alias for auth --set-environment)')
115
+ .action(async(env) => {
116
+ try {
117
+ await handleAuthConfig({ setEnvironment: env });
118
+ } catch (error) {
119
+ handleCommandError(error, 'auth set-environment');
120
+ process.exit(1);
121
+ }
122
+ });
123
+
98
124
  auth.command('status')
99
125
  .description('Show tokens/session for current controller and environment')
100
126
  .option('--validate', 'Exit with code 1 when not authenticated (for scripted use, e.g. manual test setup)')
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dev subcommands: set-home, set-work, print-home, print-work, set-format.
2
+ * Dev subcommands: set-home, set-work, print-home, print-work, shell-env, set-format.
3
3
  *
4
4
  * @fileoverview Path and format CLI registration for `aifabrix dev`
5
5
  * @author AI Fabrix Team
@@ -10,18 +10,35 @@
10
10
  const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
11
11
 
12
12
  const chalk = require('chalk');
13
+ const path = require('path');
13
14
  const config = require('../core/config');
14
15
  const logger = require('../utils/logger');
15
16
  const paths = require('../utils/paths');
16
- const { registerAifabrixShellEnvFromConfig } = require('../utils/register-aifabrix-shell-env');
17
+ const {
18
+ registerAifabrixShellEnvFromConfig,
19
+ buildShellEnvExportsFromConfig
20
+ } = require('../utils/register-aifabrix-shell-env');
17
21
  const { handleCommandError } = require('../utils/cli-utils');
18
22
 
19
23
  async function runShellEnvRegistration() {
20
24
  try {
21
25
  await registerAifabrixShellEnvFromConfig(config.getConfig);
22
26
  logger.log(
23
- chalk.gray(' User/shell environment updated. Open a new terminal for AIFABRIX_HOME / AIFABRIX_WORK.')
27
+ chalk.gray(' User/shell environment updated. New terminals pick up AIFABRIX_HOME / AIFABRIX_WORK.')
24
28
  );
29
+ if (process.platform !== 'win32') {
30
+ logger.log(
31
+ chalk.gray(' This terminal: ') +
32
+ chalk.cyan('eval "$(aifabrix dev shell-env)"') +
33
+ chalk.gray(' (or source the aifabrix-shell-env.sh next to config.yaml).')
34
+ );
35
+ } else {
36
+ logger.log(
37
+ chalk.gray(' This PowerShell session: ') +
38
+ chalk.cyan('aifabrix dev shell-env') +
39
+ chalk.gray(' then run the printed lines.')
40
+ );
41
+ }
25
42
  } catch (regErr) {
26
43
  throw new Error(`Config saved but environment registration failed: ${regErr.message}`);
27
44
  }
@@ -92,6 +109,36 @@ function addPrintHomeWorkCommands(dev) {
92
109
  process.exit(1);
93
110
  }
94
111
  });
112
+
113
+ dev
114
+ .command('shell-env')
115
+ .description(
116
+ 'Print export lines for AIFABRIX_HOME / AIFABRIX_WORK (stdout only). POSIX: eval "$(aifabrix dev shell-env)"'
117
+ )
118
+ .action(async() => {
119
+ try {
120
+ if (process.platform === 'win32') {
121
+ const cfg = await config.getConfig();
122
+ const home = (cfg['aifabrix-home'] && String(cfg['aifabrix-home']).trim())
123
+ ? paths.resolveAifabrixHomeLikePath(String(cfg['aifabrix-home']).trim())
124
+ : null;
125
+ const work = (cfg['aifabrix-work'] && String(cfg['aifabrix-work']).trim())
126
+ ? path.resolve(String(cfg['aifabrix-work']).trim())
127
+ : null;
128
+ const esc = (s) => String(s).replace(/'/g, '\'\'');
129
+ const lines = ['# Paste into PowerShell for this session only.'];
130
+ if (home) lines.push(`$env:AIFABRIX_HOME = '${esc(home)}'`);
131
+ if (work) lines.push(`$env:AIFABRIX_WORK = '${esc(work)}'`);
132
+ process.stdout.write(`${lines.join('\n')}\n`);
133
+ return;
134
+ }
135
+ const body = await buildShellEnvExportsFromConfig(config.getConfig);
136
+ process.stdout.write(body);
137
+ } catch (error) {
138
+ handleCommandError(error, 'dev shell-env');
139
+ process.exit(1);
140
+ }
141
+ });
95
142
  }
96
143
 
97
144
  function addSetFormatCommand(dev, handleSetFormat) {