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