@aifabrix/builder 2.48.0 → 2.49.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 (90) hide show
  1. package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
  2. package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
  3. package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
  4. package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
  5. package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
  6. package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
  7. package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
  8. package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
  9. package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
  10. package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
  11. package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
  12. package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
  13. package/.temp/validation/summary.txt +5 -0
  14. package/jest.isolated-projects.js +3 -0
  15. package/jest.projects.js +12 -0
  16. package/lib/api/applications.api.js +23 -0
  17. package/lib/api/dev.api.js +1 -1
  18. package/lib/api/index.js +5 -2
  19. package/lib/api/types/dev.types.js +3 -2
  20. package/lib/app/deploy-config.js +6 -4
  21. package/lib/app/register.js +1 -1
  22. package/lib/app/run-container-start.js +0 -9
  23. package/lib/app/run-helpers.js +8 -4
  24. package/lib/app/run-reload-sync.js +16 -18
  25. package/lib/app/run-stop-containers.js +48 -5
  26. package/lib/app/run.js +6 -3
  27. package/lib/app/show.js +2 -2
  28. package/lib/cli/infra-guided-auth.js +42 -6
  29. package/lib/cli/infra-guided-footers.js +164 -0
  30. package/lib/cli/infra-guided-spinners.js +57 -0
  31. package/lib/cli/infra-guided.js +41 -179
  32. package/lib/cli/setup-app.js +1 -1
  33. package/lib/cli/setup-dev.js +3 -2
  34. package/lib/cli/setup-infra.js +3 -2
  35. package/lib/cli/setup-platform.js +3 -0
  36. package/lib/commands/login-device.js +6 -2
  37. package/lib/commands/login.js +4 -1
  38. package/lib/commands/setup-modes.js +19 -3
  39. package/lib/commands/setup-platform-auth.js +25 -0
  40. package/lib/commands/setup-prompts-admin.js +9 -0
  41. package/lib/commands/setup.js +60 -8
  42. package/lib/commands/up-dataplane-bootstrap.js +149 -0
  43. package/lib/commands/up-dataplane-credentials.js +19 -4
  44. package/lib/commands/up-dataplane.js +74 -11
  45. package/lib/core/secrets-env-declarative-expand.js +35 -23
  46. package/lib/core/setup-platform-mode.js +43 -0
  47. package/lib/deployment/deployer-status.js +26 -3
  48. package/lib/deployment/deployer.js +7 -14
  49. package/lib/external-system/download-helpers.js +10 -1
  50. package/lib/infrastructure/compose.js +40 -12
  51. package/lib/infrastructure/services.js +15 -3
  52. package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
  53. package/lib/utils/app-bootstrap-api.js +53 -0
  54. package/lib/utils/app-run-containers.js +14 -3
  55. package/lib/utils/applications-config-defaults.js +19 -3
  56. package/lib/utils/compose-generate-docker-compose.js +16 -5
  57. package/lib/utils/compose-generator.js +34 -15
  58. package/lib/utils/compose-traefik-ingress-base.js +13 -14
  59. package/lib/utils/config-paths.js +6 -1
  60. package/lib/utils/dev-user-groups.js +1 -0
  61. package/lib/utils/docker-daemon-tls-ca.js +94 -57
  62. package/lib/utils/docker-reload-mount.js +12 -1
  63. package/lib/utils/external-env-template.js +53 -18
  64. package/lib/utils/health-check-poll-ui.js +84 -0
  65. package/lib/utils/health-check-url.js +54 -34
  66. package/lib/utils/health-check.js +43 -16
  67. package/lib/utils/infra-optional-service-flags.js +31 -2
  68. package/lib/utils/mutagen.js +7 -4
  69. package/lib/utils/platform-controller-url.js +23 -1
  70. package/lib/utils/refresh-remote-settings.js +67 -0
  71. package/lib/utils/remote-host-for-front-door.js +43 -0
  72. package/lib/utils/run-cli-flags.js +37 -1
  73. package/lib/utils/url-declarative-public-base.js +11 -4
  74. package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
  75. package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
  76. package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
  77. package/lib/utils/url-declarative-resolve.js +2 -0
  78. package/lib/utils/urls-local-registry-scan.js +95 -10
  79. package/lib/utils/urls-local-registry.js +24 -1
  80. package/lib/utils/worker-sidecars.js +2 -2
  81. package/lib/validation/external-system-auth-rules.js +45 -7
  82. package/package.json +2 -1
  83. package/scripts/test-dataplane-bootstrap.js +331 -0
  84. package/templates/applications/dataplane/env.template +22 -5
  85. package/templates/applications/dataplane/rbac.yaml +11 -0
  86. package/templates/applications/miso-controller/application.yaml +1 -1
  87. package/templates/applications/miso-controller/env.template +3 -0
  88. package/templates/infra/compose.yaml.hbs +13 -9
  89. package/templates/python/docker-compose.hbs +3 -0
  90. package/templates/typescript/docker-compose.hbs +3 -0
@@ -16,6 +16,7 @@ const { withMutedLogger } = require('../utils/with-muted-logger');
16
16
  const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
17
17
  const { isAuthenticationError } = require('../utils/cli-utils');
18
18
  const { resolveControllerUrl, isPlatformAuthValidForController } = require('../utils/controller-url');
19
+ const { canUseBootstrapProvision } = require('../commands/up-dataplane-bootstrap');
19
20
 
20
21
  /**
21
22
  * @param {Object} authOpts
@@ -59,7 +60,10 @@ async function runGuidedDeviceLogin(handleLoginFn, controllerUrl, opts = {}) {
59
60
  compact: true,
60
61
  skipControllerUrlPersist: Boolean(opts.tokenControllerUrl),
61
62
  skipDataplaneVersionRefresh: opts.skipDataplaneVersionRefresh === true,
62
- exitOnFailure: false
63
+ exitOnFailure: false,
64
+ guidedInstall: opts.guidedInstall === true,
65
+ onProgressText: opts.onProgressText,
66
+ suppressSuccessLine: opts.suppressSuccessLine === true
63
67
  });
64
68
  }
65
69
 
@@ -74,12 +78,17 @@ async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
74
78
  const spin = spinners.startSpinner('Authenticating...');
75
79
  const { apiUrl, tokenUrl } = await resolveGuidedAuthControllerUrls(authOpts);
76
80
  const loginOpts = { skipDataplaneVersionRefresh: true, tokenControllerUrl: tokenUrl };
81
+ const bootstrapEligible = await canUseBootstrapProvision({ skipInfraCheck: true });
77
82
  const skipLogin =
83
+ bootstrapEligible ||
78
84
  authOpts.skipLoginIfAuthenticated === true ||
79
85
  (await isPlatformAuthValidForController(tokenUrl));
80
86
  try {
81
87
  if (skipLogin) {
82
- spinners.stopSpinnerSuccess(spin, 'Already authenticated');
88
+ spinners.stopSpinnerSuccess(
89
+ spin,
90
+ bootstrapEligible ? 'Bootstrap provision (no device login)' : 'Already authenticated'
91
+ );
83
92
  return;
84
93
  }
85
94
  await runGuidedDeviceLogin(handleLoginFn, apiUrl, loginOpts);
@@ -103,9 +112,17 @@ async function runGuidedAuthStep(handleLoginFn, authOpts, spinners) {
103
112
  * @param {Function} handleUpDataplane
104
113
  * @param {Function} handleLoginFn
105
114
  * @param {string} controllerUrl
115
+ * @param {{ onSpinnerText?: (text: string) => void }} [ui]
106
116
  * @returns {Promise<void>}
107
117
  */
108
- async function runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, handleLoginFn, controllerUrl) {
118
+ async function runGuidedDataplaneWithAuthRecovery(
119
+ options,
120
+ handleUpDataplane,
121
+ handleLoginFn,
122
+ controllerUrl,
123
+ ui = {}
124
+ ) {
125
+ const onSpinnerText = typeof ui.onSpinnerText === 'function' ? ui.onSpinnerText : null;
109
126
  try {
110
127
  await withMutedLogger(() =>
111
128
  handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
@@ -114,10 +131,29 @@ async function runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, ha
114
131
  if (!isAuthenticationError(error)) {
115
132
  throw error;
116
133
  }
134
+ const bootstrapEligible = await canUseBootstrapProvision({
135
+ skipInfraCheck: true,
136
+ allowNonDev: options.allowNonDev
137
+ });
138
+ if (bootstrapEligible) {
139
+ throw error;
140
+ }
117
141
  const loginUrl = error.controllerUrl || controllerUrl || (await resolveControllerUrl());
118
- logger.log('');
119
- logger.log(chalk.yellow('Device login required to register and deploy Dataplane.'));
120
- await runGuidedDeviceLogin(handleLoginFn, loginUrl, { skipDataplaneVersionRefresh: false });
142
+ if (onSpinnerText) {
143
+ onSpinnerText('Authenticating...');
144
+ } else {
145
+ logger.log('');
146
+ logger.log(chalk.yellow('Device login required to register and deploy Dataplane.'));
147
+ }
148
+ await runGuidedDeviceLogin(handleLoginFn, loginUrl, {
149
+ skipDataplaneVersionRefresh: false,
150
+ compact: true,
151
+ guidedInstall: true,
152
+ onProgressText: onSpinnerText || undefined
153
+ });
154
+ if (onSpinnerText) {
155
+ onSpinnerText('Validating Dataplane...');
156
+ }
121
157
  await withMutedLogger(() =>
122
158
  handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
123
159
  );
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @fileoverview Footer and header layout for guided infra/platform CLI flows.
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 { computeAppBaseUrl, resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
11
+ const { successGlyph } = require('../utils/cli-test-layout-chalk');
12
+
13
+ const SEPARATOR = '────────────────────────────────────────';
14
+
15
+ function title(text) {
16
+ return chalk.bold(text);
17
+ }
18
+
19
+ function logSection(label, value) {
20
+ logger.log(title(label));
21
+ logger.log(` ${value}`);
22
+ logger.log('');
23
+ }
24
+
25
+ function logFooterStart(label) {
26
+ logger.log('');
27
+ logger.log(SEPARATOR);
28
+ logger.log(title(label));
29
+ logger.log(SEPARATOR);
30
+ logger.log('');
31
+ }
32
+
33
+ function logFooterEnd() {
34
+ logger.log(SEPARATOR);
35
+ }
36
+
37
+ /**
38
+ * @async
39
+ * @returns {Promise<string>}
40
+ */
41
+ async function resolvePlatformControllerUrlForDisplay() {
42
+ const stored = await config.getPlatformControllerUrl();
43
+ if (stored) {
44
+ return stored;
45
+ }
46
+ return resolvePlatformControllerUrl();
47
+ }
48
+
49
+ async function logPlatformReadyFooter() {
50
+ const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
51
+ resolvePlatformControllerUrlForDisplay(),
52
+ computeAppBaseUrl('dataplane'),
53
+ computeAppBaseUrl('keycloak')
54
+ ]);
55
+ const cfg = await config.getConfig();
56
+ const env = cfg && cfg.environment ? cfg.environment : 'dev';
57
+
58
+ logFooterStart('Platform Ready');
59
+ logSection('Environment', env);
60
+ logSection('Miso Controller', controllerUrl);
61
+ logSection('Dataplane (DEV)', dataplaneUrl);
62
+ logSection('Keycloak', keycloakUrl);
63
+ logSection('API Documentation', `${dataplaneUrl}/api/docs`);
64
+ logSection('Knowledge Base', 'https://docs.aifabrix.ai');
65
+ logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
66
+ logFooterEnd();
67
+ }
68
+
69
+ async function logMisoReadyFooter() {
70
+ const [controllerUrl, keycloakUrl] = await Promise.all([
71
+ resolvePlatformControllerUrlForDisplay(),
72
+ computeAppBaseUrl('keycloak')
73
+ ]);
74
+ logFooterStart('Miso Ready');
75
+ logSection('Miso Controller', controllerUrl);
76
+ logSection('Keycloak', keycloakUrl);
77
+ logFooterEnd();
78
+ }
79
+
80
+ async function logDataplaneReadyFooter() {
81
+ const dataplaneUrl = await computeAppBaseUrl('dataplane');
82
+ const cfg = await config.getConfig();
83
+ const env = cfg && cfg.environment ? cfg.environment : 'dev';
84
+ logFooterStart('Dataplane Ready');
85
+ logSection('Environment', env);
86
+ logSection('Dataplane (DEV)', dataplaneUrl);
87
+ logSection('API Documentation', `${dataplaneUrl}/api/docs`);
88
+ logFooterEnd();
89
+ }
90
+
91
+ /**
92
+ * @param {'platform'|'dataplane'} [scope]
93
+ */
94
+ function logPlatformSetupDurationHint(scope = 'platform') {
95
+ const line =
96
+ scope === 'dataplane'
97
+ ? 'Installation may take a few minutes. Authenticating and starting Dataplane — please wait.'
98
+ : 'Installation may take a few minutes. Keycloak, Miso Controller, and Dataplane are starting — please wait.';
99
+ logger.log(chalk.yellow(`⚠ ${line}`));
100
+ }
101
+
102
+ function logGuidedPlatformSetupHeader() {
103
+ logger.log('');
104
+ logger.log(title('AI Fabrix Platform Setup'));
105
+ logger.log(SEPARATOR);
106
+ logger.log('');
107
+ logPlatformSetupDurationHint('platform');
108
+ logger.log('');
109
+ }
110
+
111
+ /**
112
+ * @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
113
+ * @param {string[]} cleanedApps
114
+ */
115
+ function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
116
+ const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
117
+ if (isSetupQuietOutput()) return;
118
+ const check = successGlyph();
119
+ logger.log(chalk.bold('Clean installation files'));
120
+ logger.log(
121
+ chalk.gray(
122
+ ` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
123
+ )
124
+ );
125
+ logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
126
+ logger.log(
127
+ chalk.gray(
128
+ ` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
129
+ )
130
+ );
131
+ for (const appName of cleanedApps || []) {
132
+ logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
133
+ }
134
+ logger.log('');
135
+ }
136
+
137
+ /**
138
+ * @param {Object} cfg
139
+ */
140
+ function logStoppedServices(cfg) {
141
+ logger.log(title('Stopped'));
142
+ logger.log(' postgres');
143
+ logger.log(' redis');
144
+ if (cfg.pgadmin !== false) logger.log(' pgadmin');
145
+ if (cfg.redisCommander !== false) logger.log(' redis-commander');
146
+ if (cfg.traefik) logger.log(' traefik');
147
+ logger.log('');
148
+ }
149
+
150
+ module.exports = {
151
+ SEPARATOR,
152
+ title,
153
+ logSection,
154
+ logFooterStart,
155
+ logFooterEnd,
156
+ resolvePlatformControllerUrlForDisplay,
157
+ logPlatformReadyFooter,
158
+ logMisoReadyFooter,
159
+ logDataplaneReadyFooter,
160
+ logPlatformSetupDurationHint,
161
+ logGuidedPlatformSetupHeader,
162
+ logUpPlatformForceCleanSummary,
163
+ logStoppedServices
164
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @fileoverview Spinner helpers for guided infra/platform CLI flows.
3
+ */
4
+
5
+ 'use strict';
6
+
7
+ const ora = require('ora');
8
+ const logger = require('../utils/logger');
9
+ const { formatProgress, formatSuccessLine } = require('../utils/cli-test-layout-chalk');
10
+
11
+ function shouldUseSpinner() {
12
+ return Boolean(process && process.stdout && process.stdout.isTTY);
13
+ }
14
+
15
+ /**
16
+ * @param {string} text
17
+ * @returns {import('ora').Ora|null}
18
+ */
19
+ function startSpinner(text) {
20
+ if (!shouldUseSpinner()) {
21
+ logger.log(formatProgress(text));
22
+ return null;
23
+ }
24
+ return ora({ text, spinner: 'dots' }).start();
25
+ }
26
+
27
+ /**
28
+ * @param {import('ora').Ora|null} spinner
29
+ * @param {string} text
30
+ */
31
+ function stopSpinnerSuccess(spinner, text) {
32
+ if (!spinner) {
33
+ logger.log(formatSuccessLine(text));
34
+ return;
35
+ }
36
+ spinner.succeed(text);
37
+ }
38
+
39
+ /**
40
+ * @param {import('ora').Ora|null} spinner
41
+ * @returns {(text: string) => void}
42
+ */
43
+ function createSpinnerTextUpdater(spinner) {
44
+ return (text) => {
45
+ if (spinner) {
46
+ spinner.text = text;
47
+ return;
48
+ }
49
+ logger.log(formatProgress(text));
50
+ };
51
+ }
52
+
53
+ module.exports = {
54
+ startSpinner,
55
+ stopSpinnerSuccess,
56
+ createSpinnerTextUpdater
57
+ };
@@ -10,10 +10,7 @@ const config = require('../core/config');
10
10
  const devConfig = require('../utils/dev-config');
11
11
  const pathsUtil = require('../utils/paths');
12
12
  const { loadConfigFile } = require('../utils/config-format');
13
- const { computeAppBaseUrl, resolvePlatformControllerUrl } = require('../utils/platform-controller-url');
14
13
  const { withMutedLogger } = require('../utils/with-muted-logger');
15
- const { formatProgress, formatSuccessLine, successGlyph } = require('../utils/cli-test-layout-chalk');
16
- const ora = require('ora');
17
14
  const infra = require('../infrastructure');
18
15
  const { execWithDockerEnv } = require('../utils/docker-exec');
19
16
  const { getLogLevel, passesLevelFilter } = require('../commands/app-logs');
@@ -23,6 +20,26 @@ const {
23
20
  runGuidedAuthStep,
24
21
  runGuidedDataplaneWithAuthRecovery
25
22
  } = require('./infra-guided-auth');
23
+ const {
24
+ startSpinner,
25
+ stopSpinnerSuccess,
26
+ createSpinnerTextUpdater
27
+ } = require('./infra-guided-spinners');
28
+ const {
29
+ SEPARATOR,
30
+ title,
31
+ logSection,
32
+ logFooterStart,
33
+ logFooterEnd,
34
+ resolvePlatformControllerUrlForDisplay,
35
+ logPlatformReadyFooter,
36
+ logMisoReadyFooter,
37
+ logDataplaneReadyFooter,
38
+ logPlatformSetupDurationHint,
39
+ logGuidedPlatformSetupHeader,
40
+ logUpPlatformForceCleanSummary,
41
+ logStoppedServices
42
+ } = require('./infra-guided-footers');
26
43
  const healthCheck = require('../utils/health-check');
27
44
  const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
28
45
  const { assertDevInfraUp } = require('../commands/dev-infra-gate');
@@ -30,32 +47,6 @@ const {
30
47
  emitSystemBuilderAppManifestLineIfTTY
31
48
  } = require('../utils/manifest-source-emit');
32
49
 
33
- const SEPARATOR = '────────────────────────────────────────';
34
-
35
- function title(text) {
36
- return chalk.bold(text);
37
- }
38
-
39
- function shouldUseSpinner() {
40
- return Boolean(process && process.stdout && process.stdout.isTTY);
41
- }
42
-
43
- function startSpinner(text) {
44
- if (!shouldUseSpinner()) {
45
- logger.log(formatProgress(text));
46
- return null;
47
- }
48
- return ora({ text, spinner: 'dots' }).start();
49
- }
50
-
51
- function stopSpinnerSuccess(spinner, text) {
52
- if (!spinner) {
53
- logger.log(formatSuccessLine(text));
54
- return;
55
- }
56
- spinner.succeed(text);
57
- }
58
-
59
50
  async function getRunningContainerNameForApp(appName) {
60
51
  const apps = await infra.getAppStatus();
61
52
  const hit = (apps || []).find(a => a && a.name === appName && a.container);
@@ -124,84 +115,10 @@ function resolveInfraHost(remoteServer) {
124
115
  const u = new URL(withScheme);
125
116
  return u.hostname || 'localhost';
126
117
  } catch {
127
- // Fallback for non-URL strings.
128
118
  return raw.replace(/^https?:\/\//i, '').split('/')[0].split(':')[0] || 'localhost';
129
119
  }
130
120
  }
131
121
 
132
- /**
133
- * Prefer stored platform controller URL, then compute from routing rules.
134
- * @async
135
- * @returns {Promise<string>}
136
- */
137
- async function resolvePlatformControllerUrlForDisplay() {
138
- const stored = await config.getPlatformControllerUrl();
139
- if (stored) {
140
- return stored;
141
- }
142
- return resolvePlatformControllerUrl();
143
- }
144
-
145
- function logSection(label, value) {
146
- logger.log(title(label));
147
- logger.log(` ${value}`);
148
- logger.log('');
149
- }
150
-
151
- function logFooterStart(label) {
152
- logger.log('');
153
- logger.log(SEPARATOR);
154
- logger.log(title(label));
155
- logger.log(SEPARATOR);
156
- logger.log('');
157
- }
158
-
159
- function logFooterEnd() {
160
- logger.log(SEPARATOR);
161
- }
162
-
163
- async function logPlatformReadyFooter() {
164
- const [controllerUrl, dataplaneUrl, keycloakUrl] = await Promise.all([
165
- resolvePlatformControllerUrlForDisplay(),
166
- computeAppBaseUrl('dataplane'),
167
- computeAppBaseUrl('keycloak')
168
- ]);
169
- const cfg = await config.getConfig();
170
- const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
171
-
172
- logFooterStart('Platform Ready');
173
- logSection('Environment', env);
174
- logSection('Miso Controller', controllerUrl);
175
- logSection('Dataplane (DEV)', dataplaneUrl);
176
- logSection('Keycloak', keycloakUrl);
177
- logSection('API Documentation', `${dataplaneUrl}/api/docs`);
178
- logSection('Knowledge Base', 'https://docs.aifabrix.ai');
179
- logSection('Getting Started', 'https://docs.aifabrix.ai/get-started');
180
- logFooterEnd();
181
- }
182
-
183
- async function logMisoReadyFooter() {
184
- const [controllerUrl, keycloakUrl] = await Promise.all([
185
- resolvePlatformControllerUrlForDisplay(),
186
- computeAppBaseUrl('keycloak')
187
- ]);
188
- logFooterStart('Miso Ready');
189
- logSection('Miso Controller', controllerUrl);
190
- logSection('Keycloak', keycloakUrl);
191
- logFooterEnd();
192
- }
193
-
194
- async function logDataplaneReadyFooter() {
195
- const dataplaneUrl = await computeAppBaseUrl('dataplane');
196
- const cfg = await config.getConfig();
197
- const env = (cfg && cfg.environment) ? cfg.environment : 'dev';
198
- logFooterStart('Dataplane Ready');
199
- logSection('Environment', env);
200
- logSection('Dataplane (DEV)', dataplaneUrl);
201
- logSection('API Documentation', `${dataplaneUrl}/api/docs`);
202
- logFooterEnd();
203
- }
204
-
205
122
  async function getInfraHostAndPorts() {
206
123
  const developerId = await config.getDeveloperId();
207
124
  const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
@@ -269,17 +186,12 @@ async function runGuidedUpMiso(options, handleUpMiso) {
269
186
  await validateAppErrorLogs(['miso-controller']);
270
187
  }
271
188
 
272
- /**
273
- * Infra must be checked before auth: device login talks to the controller, which needs
274
- * Postgres/Redis when the controller runs locally — a down infra looks like "network error".
275
- */
276
189
  async function assertDevInfraForGuidedDataplane() {
277
190
  const infraSpin = startSpinner('Checking infrastructure...');
278
191
  try {
279
192
  await assertDevInfraUp({ quietSuccess: true });
280
193
  stopSpinnerSuccess(infraSpin, 'Infrastructure is up');
281
194
  } catch (infraErr) {
282
- // Match up-miso: one error block from handleCommandError only (no spinner.fail / extra hints).
283
195
  if (infraSpin) infraSpin.stop();
284
196
  throw infraErr;
285
197
  }
@@ -293,17 +205,18 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
293
205
  logPlatformSetupDurationHint('dataplane');
294
206
  logger.log('');
295
207
  await assertDevInfraForGuidedDataplane();
296
- // Avoid an outer long-running spinner here: up-dataplane has multiple internal
297
- // phases and may print some output. An outer spinner causes "spinner over spinner"
298
- // rendering artifacts. Instead, do the same single auth step as up-platform, then
299
- // show one spinner for the dataplane install/run phase.
300
208
  await runGuidedAuthStep(handleLogin, {}, { startSpinner, stopSpinnerSuccess });
301
209
 
302
210
  logger.log('');
303
211
  emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
304
212
  const dpSpin = startSpinner('Starting Dataplane...');
305
- await withMutedLogger(() =>
306
- handleUpDataplane({ ...options, platformInstall: true, skipInfraCheck: true })
213
+ const controllerUrl = await resolveGuidedAuthControllerUrl({});
214
+ await runGuidedDataplaneWithAuthRecovery(
215
+ { ...options, platformInstall: true, skipInfraCheck: true },
216
+ handleUpDataplane,
217
+ handleLogin,
218
+ controllerUrl,
219
+ { onSpinnerText: createSpinnerTextUpdater(dpSpin) }
307
220
  );
308
221
  stopSpinnerSuccess(dpSpin, 'Dataplane ready');
309
222
  await refreshDataplaneVersionAfterPlatformInstall();
@@ -311,54 +224,6 @@ async function runGuidedUpDataplane(options, handleUpDataplane) {
311
224
  await logDataplaneReadyFooter();
312
225
  }
313
226
 
314
- /**
315
- * Guided-mode summary after `up-platform --force` (printed under the platform header).
316
- * @param {{ deviceCleared: number, clientCleared: number, environment: string, defaultControllerUrl: string }} forceSummary
317
- * @param {string[]} cleanedApps
318
- */
319
- function logUpPlatformForceCleanSummary(forceSummary, cleanedApps) {
320
- const { isSetupQuietOutput } = require('../utils/setup-quiet-output');
321
- if (isSetupQuietOutput()) return;
322
- const check = successGlyph();
323
- logger.log(chalk.bold('Clean installation files'));
324
- logger.log(
325
- chalk.gray(
326
- ` ${check} Cleared ${forceSummary.deviceCleared} device token(s) and ${forceSummary.clientCleared} client token(s)`
327
- )
328
- );
329
- logger.log(chalk.gray(` ${check} Environment set to ${forceSummary.environment}`));
330
- logger.log(
331
- chalk.gray(
332
- ` ${check} Default controller set to ${forceSummary.defaultControllerUrl} (run aifabrix login after platform is up)`
333
- )
334
- );
335
- for (const appName of cleanedApps || []) {
336
- logger.log(chalk.gray(` ${check} Cleaned builder/${appName}`));
337
- }
338
- logger.log('');
339
- }
340
-
341
- /**
342
- * Yellow notice before long-running platform/dataplane install steps (setup and guided up-*).
343
- * @param {'platform'|'dataplane'} [scope]
344
- */
345
- function logPlatformSetupDurationHint(scope = 'platform') {
346
- const line =
347
- scope === 'dataplane'
348
- ? 'Installation may take a few minutes. Authenticating and starting Dataplane — please wait.'
349
- : 'Installation may take a few minutes. Keycloak, Miso Controller, and Dataplane are starting — please wait.';
350
- logger.log(chalk.yellow(`⚠ ${line}`));
351
- }
352
-
353
- function logGuidedPlatformSetupHeader() {
354
- logger.log('');
355
- logger.log(title('AI Fabrix Platform Setup'));
356
- logger.log(SEPARATOR);
357
- logger.log('');
358
- logPlatformSetupDurationHint('platform');
359
- logger.log('');
360
- }
361
-
362
227
  async function refreshDataplaneVersionAfterPlatformInstall() {
363
228
  try {
364
229
  const { tryRefreshDataplaneVersionAfterLogin } = require('../commands/auth-status-dataplane-version');
@@ -386,20 +251,27 @@ async function runGuidedDataplaneInstallPhase(options, handleUpDataplane, handle
386
251
  logger.log('');
387
252
  emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
388
253
  const dpSpin = startSpinner('Starting Dataplane...');
254
+ const onSpinnerText = createSpinnerTextUpdater(dpSpin);
389
255
  const platformMode = await config.getSetupPlatformMode();
390
256
  if (platformMode === 'full') {
391
- // Full platform: three dataplane containers + per-env registrations.
392
257
  const envKeys = ['pro', 'dev', 'tst'];
393
258
  for (const envKey of envKeys) {
394
259
  await runGuidedDataplaneWithAuthRecovery(
395
260
  { ...options, envKey, runEnvKey: envKey, allowNonDev: true },
396
261
  handleUpDataplane,
397
262
  handleLoginFn,
398
- controllerUrl
263
+ controllerUrl,
264
+ { onSpinnerText }
399
265
  );
400
266
  }
401
267
  } else {
402
- await runGuidedDataplaneWithAuthRecovery(options, handleUpDataplane, handleLoginFn, controllerUrl);
268
+ await runGuidedDataplaneWithAuthRecovery(
269
+ options,
270
+ handleUpDataplane,
271
+ handleLoginFn,
272
+ controllerUrl,
273
+ { onSpinnerText }
274
+ );
403
275
  }
404
276
  stopSpinnerSuccess(dpSpin, 'Dataplane ready');
405
277
  await refreshDataplaneVersionAfterPlatformInstall();
@@ -423,6 +295,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
423
295
  const realmSpin = startSpinner('Waiting for Keycloak realm (aifabrix)...');
424
296
  await waitForKeycloakRealmReady({ timeoutSeconds: 180 });
425
297
  stopSpinnerSuccess(realmSpin, 'Keycloak realm ready');
298
+ logger.log('');
426
299
  }
427
300
  await runGuidedAuthStep(handleLogin, authOpts, { startSpinner, stopSpinnerSuccess });
428
301
  const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
@@ -431,17 +304,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
431
304
  await logPlatformReadyFooter();
432
305
  }
433
306
 
434
- function logStoppedServices(cfg) {
435
- logger.log(title('Stopped'));
436
- logger.log(' postgres');
437
- logger.log(' redis');
438
- if (cfg.pgadmin !== false) logger.log(' pgadmin');
439
- if (cfg.redisCommander !== false) logger.log(' redis-commander');
440
- if (cfg.traefik) logger.log(' traefik');
441
- logger.log('');
442
- }
443
-
444
- async function runGuidedDownInfra(appName, options, infra, appLib) {
307
+ async function runGuidedDownInfra(appName, options, infraMod, appLib) {
445
308
  logger.log('');
446
309
  logger.log(title('AI Fabrix Shutdown'));
447
310
  logger.log(SEPARATOR);
@@ -453,8 +316,8 @@ async function runGuidedDownInfra(appName, options, infra, appLib) {
453
316
  await appLib.downApp(appName, { volumes: !!options.volumes });
454
317
  return;
455
318
  }
456
- if (options.volumes) await infra.stopInfraWithVolumes();
457
- else await infra.stopInfra();
319
+ if (options.volumes) await infraMod.stopInfraWithVolumes();
320
+ else await infraMod.stopInfra();
458
321
  });
459
322
  stopSpinnerSuccess(spin, 'Infrastructure stopped');
460
323
 
@@ -479,4 +342,3 @@ module.exports = {
479
342
  runGuidedUpPlatform,
480
343
  runGuidedDownInfra
481
344
  };
482
-
@@ -297,7 +297,7 @@ Examples:
297
297
  .option('--reload', 'In dev: mount workspace into container (Mutagen only if docker-endpoint is a remote host)')
298
298
  .option(
299
299
  '--proxy',
300
- 'Use Traefik/front-door public URL hints when infra has Traefik and application.yaml enables frontDoorRouting (default: on)',
300
+ 'Use Traefik/front-door public URL hints when infra has Traefik (ignored when traefik: false in config)',
301
301
  true
302
302
  )
303
303
  .option(
@@ -39,6 +39,7 @@ Examples:
39
39
  $ aifabrix dev add --developer-id 02 --name "Jane Doe" --email jane@example.com
40
40
  $ aifabrix dev add --developer-id 03 --name "Build admin" --email admin@example.com --groups admin,developer
41
41
  $ aifabrix dev add --developer-id 04 --name "CI user" --email ci@example.com --groups secret-manager,developer
42
+ $ aifabrix dev add --developer-id 05 --name "Integrator" --email integrator@example.com --groups developer,secret-reader
42
43
 
43
44
  Requires a configured remote Builder Server and an admin client certificate (same machine setup as dev list / dev pin). After add, run dev pin <id> once to create a PIN for aifabrix dev init.
44
45
  `;
@@ -274,7 +275,7 @@ function setupDevListAddCommands(dev) {
274
275
  .requiredOption('--developer-id <id>', 'Unique id, digits only (e.g. 02); used with dev init --developer-id')
275
276
  .requiredOption('--name <name>', 'Display name')
276
277
  .requiredOption('--email <email>', 'Email address')
277
- .option('--groups <items>', 'Comma-separated roles: admin, secret-manager, developer, docker', 'developer')
278
+ .option('--groups <items>', 'Comma-separated roles: admin, secret-manager, secret-reader, developer, docker', 'developer')
278
279
  .addHelpText('after', DEV_ADD_HELP_AFTER)
279
280
  .action(async(options) => {
280
281
  try {
@@ -297,7 +298,7 @@ function setupDevUpdatePinDeleteCommands(dev) {
297
298
  .option('--developer-id <id>', 'Developer ID (same as dev add)')
298
299
  .option('--name <name>', 'Display name')
299
300
  .option('--email <email>', 'Email address')
300
- .option('--groups <items>', 'Comma-separated groups (admin, secret-manager, developer, docker)')
301
+ .option('--groups <items>', 'Comma-separated groups (admin, secret-manager, secret-reader, developer, docker)')
301
302
  .addHelpText('after', DEV_UPDATE_HELP_AFTER)
302
303
  .action(async(developerId, options) => {
303
304
  try {
@@ -34,7 +34,8 @@ const {
34
34
 
35
35
  const {
36
36
  computeEffectiveInfraOptionalFlags,
37
- persistMissingInfraOptionalServiceFlags
37
+ persistMissingInfraOptionalServiceFlags,
38
+ resolveEffectiveTlsEnabled
38
39
  } = require('../utils/infra-optional-service-flags');
39
40
 
40
41
  const UP_INFRA_HELP_AFTER = `
@@ -137,7 +138,7 @@ async function runUpInfraCommand(options) {
137
138
  const { passwordBundleFromCliOptions } = require('../core/admin-secrets-bundle');
138
139
  const bundle = passwordBundleFromCliOptions(options);
139
140
  const adminPass = options.adminPassword || options.adminPwd;
140
- const tlsEnabledEffective = cfg.tlsEnabled === true;
141
+ const tlsEnabledEffective = resolveEffectiveTlsEnabled(cfg, options);
141
142
  const effective = computeEffectiveInfraOptionalFlags(cfg, options);
142
143
  await infra.startInfra(developerId, {
143
144
  traefik: effective.traefik,