@aifabrix/builder 2.47.4 → 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 (93) hide show
  1. package/jest.isolated-projects.js +7 -0
  2. package/jest.projects.js +16 -0
  3. package/lib/api/applications.api.js +23 -0
  4. package/lib/api/dev.api.js +1 -1
  5. package/lib/api/index.js +5 -2
  6. package/lib/api/types/dev.types.js +4 -3
  7. package/lib/app/deploy-config.js +6 -4
  8. package/lib/app/register.js +21 -19
  9. package/lib/app/rotate-secret.js +39 -16
  10. package/lib/app/run-container-start.js +0 -9
  11. package/lib/app/run-helpers.js +8 -4
  12. package/lib/app/run-reload-sync.js +16 -18
  13. package/lib/app/run-stop-containers.js +48 -5
  14. package/lib/app/run.js +6 -3
  15. package/lib/app/show.js +2 -2
  16. package/lib/build/index.js +12 -49
  17. package/lib/build/integration-docker-staging.js +35 -0
  18. package/lib/build/standard-docker-build.js +113 -0
  19. package/lib/cli/infra-guided-auth.js +42 -6
  20. package/lib/cli/infra-guided-footers.js +164 -0
  21. package/lib/cli/infra-guided-spinners.js +57 -0
  22. package/lib/cli/infra-guided.js +41 -179
  23. package/lib/cli/setup-app.js +5 -1
  24. package/lib/cli/setup-dev.js +3 -2
  25. package/lib/cli/setup-infra.js +3 -2
  26. package/lib/cli/setup-platform.js +3 -0
  27. package/lib/commands/dev-init.js +18 -0
  28. package/lib/commands/login-device.js +6 -2
  29. package/lib/commands/login.js +4 -1
  30. package/lib/commands/setup-modes.js +19 -3
  31. package/lib/commands/setup-platform-auth.js +25 -0
  32. package/lib/commands/setup-prompts-admin.js +9 -0
  33. package/lib/commands/setup-prompts-platform-mode.js +11 -1
  34. package/lib/commands/setup.js +60 -7
  35. package/lib/commands/up-dataplane-bootstrap.js +149 -0
  36. package/lib/commands/up-dataplane-credentials.js +104 -6
  37. package/lib/commands/up-dataplane.js +74 -11
  38. package/lib/core/secrets-env-declarative-expand.js +35 -23
  39. package/lib/core/setup-platform-mode.js +59 -9
  40. package/lib/deployment/deployer-status.js +26 -3
  41. package/lib/deployment/deployer.js +7 -14
  42. package/lib/external-system/download-helpers.js +10 -1
  43. package/lib/infrastructure/compose.js +40 -12
  44. package/lib/infrastructure/services.js +15 -3
  45. package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
  46. package/lib/parameters/infra-kv-discovery.js +4 -3
  47. package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
  48. package/lib/utils/app-bootstrap-api.js +53 -0
  49. package/lib/utils/app-run-containers.js +14 -3
  50. package/lib/utils/applications-config-defaults.js +19 -3
  51. package/lib/utils/compose-generate-docker-compose.js +18 -6
  52. package/lib/utils/compose-generator.js +34 -15
  53. package/lib/utils/compose-traefik-ingress-base.js +13 -14
  54. package/lib/utils/config-paths.js +8 -3
  55. package/lib/utils/dev-ca-docker-augment.js +54 -0
  56. package/lib/utils/dev-user-groups.js +1 -0
  57. package/lib/utils/docker-daemon-tls-ca.js +363 -0
  58. package/lib/utils/docker-reload-mount.js +12 -1
  59. package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
  60. package/lib/utils/external-env-template.js +53 -18
  61. package/lib/utils/health-check-poll-ui.js +84 -0
  62. package/lib/utils/health-check-url.js +54 -34
  63. package/lib/utils/health-check.js +43 -16
  64. package/lib/utils/infra-optional-service-flags.js +31 -2
  65. package/lib/utils/mutagen.js +7 -4
  66. package/lib/utils/platform-controller-url.js +23 -1
  67. package/lib/utils/promote-dev-docker-image.js +170 -0
  68. package/lib/utils/refresh-remote-settings.js +67 -0
  69. package/lib/utils/remote-docker-env.js +22 -7
  70. package/lib/utils/remote-host-for-front-door.js +43 -0
  71. package/lib/utils/run-cli-flags.js +37 -1
  72. package/lib/utils/secrets-helpers.js +9 -14
  73. package/lib/utils/secrets-kv-refs.js +47 -3
  74. package/lib/utils/secrets-missing-error.js +1 -9
  75. package/lib/utils/token-manager-client-credentials.js +11 -1
  76. package/lib/utils/url-declarative-public-base.js +11 -4
  77. package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
  78. package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
  79. package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
  80. package/lib/utils/url-declarative-resolve.js +2 -0
  81. package/lib/utils/urls-local-registry-scan.js +95 -10
  82. package/lib/utils/urls-local-registry.js +24 -1
  83. package/lib/utils/worker-sidecars.js +28 -1
  84. package/lib/validation/external-system-auth-rules.js +45 -7
  85. package/package.json +2 -1
  86. package/scripts/test-dataplane-bootstrap.js +331 -0
  87. package/templates/applications/dataplane/env.template +22 -5
  88. package/templates/applications/dataplane/rbac.yaml +11 -0
  89. package/templates/applications/miso-controller/application.yaml +1 -1
  90. package/templates/applications/miso-controller/env.template +3 -0
  91. package/templates/infra/compose.yaml.hbs +13 -9
  92. package/templates/python/docker-compose.hbs +3 -0
  93. package/templates/typescript/docker-compose.hbs +3 -0
@@ -1,6 +1,5 @@
1
1
  const {
2
2
  formatSuccessLine,
3
- formatSuccessParagraph,
4
3
  formatProgress,
5
4
  formatWarningLine,
6
5
  sectionTitle,
@@ -31,12 +30,10 @@ const secrets = require('../core/secrets');
31
30
  const config = require('../core/config');
32
31
  const logger = require('../utils/logger');
33
32
  const dockerfileUtils = require('../utils/dockerfile-utils');
34
- const dockerBuild = require('../utils/docker-build');
35
33
  const buildCopy = require('../utils/build-copy');
36
34
  const { resolveBuildDockerTagNames } = require('../utils/build-resolve-image');
37
35
  const buildHelpers = require('../utils/build-helpers');
38
- const { ensureRunSecretsForApp } = require('../app/run-env-compose');
39
- const { resolveDockerBuildArgsForApp } = require('./docker-build-args');
36
+ const { runStandardDockerBuild } = require('./standard-docker-build');
40
37
 
41
38
  /**
42
39
  * Loads application config for an application
@@ -353,18 +350,6 @@ function prepareBuildContext(buildConfig, devDir) {
353
350
  return contextPath;
354
351
  }
355
352
 
356
- /**
357
- * Handle Dockerfile generation
358
- * @async
359
- * @param {string} appName - Application name
360
- * @param {Object} params - Parameters
361
- * @param {string} params.devDir - Developer directory
362
- * @param {Object} params.buildConfig - Build configuration
363
- * @param {string} params.contextPath - Build context path
364
- * @param {Object} params.appConfig - Application configuration
365
- * @param {Object} options - Build options
366
- * @returns {Promise<string>} Dockerfile path
367
- */
368
353
  async function handleDockerfileGeneration(appName, params, options, buildHelpers) {
369
354
  const { devDir, buildConfig, contextPath, appConfig } = params;
370
355
  const appDockerfilePath = path.join(devDir, 'Dockerfile');
@@ -393,38 +378,6 @@ async function handleDockerfileGeneration(appName, params, options, buildHelpers
393
378
  }, generateDockerfile);
394
379
  }
395
380
 
396
- /**
397
- * Standard Docker build path after CLI header is printed (non-external apps).
398
- * @param {string} appName
399
- * @param {Object} options
400
- * @returns {Promise<string>} Built image ref e.g. name:tag
401
- */
402
- async function runStandardDockerBuild(appName, options) {
403
- const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
404
- const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
405
- const { appPath } = await detectAppType(appName);
406
- emitManifestMetadataLineIfTTY(logger, { appKey: appName, appPath, envOnly: false, json: false });
407
- const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
408
- const contextPath = prepareBuildContext(buildConfig, devDir);
409
- const dockerfilePath = await handleDockerfileGeneration(appName, {
410
- devDir,
411
- buildConfig,
412
- contextPath,
413
- appConfig
414
- }, options, buildHelpers);
415
- await ensureRunSecretsForApp(appName);
416
- const buildArgs = await resolveDockerBuildArgsForApp(appName, contextPath);
417
- const buildOptions = { ...options, buildArgs };
418
- const tag = options.tag || 'latest';
419
- if (typeof tag === 'string' && tag.includes(',')) {
420
- throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
421
- }
422
- await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
423
- await postBuildTasks(appName, buildConfig);
424
- logger.log(formatSuccessParagraph('Build completed successfully!'));
425
- return `${effectiveImageName}:${tag}`;
426
- }
427
-
428
381
  /**
429
382
  * Builds a container image for the specified application
430
383
  * Auto-detects runtime and generates Dockerfile if needed
@@ -458,4 +411,14 @@ async function buildApp(appName, options = {}) {
458
411
  throw new Error(`Build failed: ${error.message}`);
459
412
  }
460
413
  }
461
- module.exports = { loadVariablesYaml, resolveContextPath, detectLanguage, generateDockerfile, buildApp, postBuildTasks };
414
+ module.exports = {
415
+ loadVariablesYaml,
416
+ resolveContextPath,
417
+ detectLanguage,
418
+ generateDockerfile,
419
+ buildApp,
420
+ postBuildTasks,
421
+ prepareDevDirectory,
422
+ prepareBuildContext,
423
+ handleDockerfileGeneration
424
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pre-build integration manifest staging for Docker context.
3
+ * @fileoverview Runs scripts/stage_integration_for_docker.py when present in build context
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const fsSync = require('fs');
9
+ const path = require('path');
10
+ const { promisify } = require('util');
11
+ const { exec } = require('child_process');
12
+ const logger = require('../utils/logger');
13
+ const {
14
+ formatProgress,
15
+ formatSuccessLine
16
+ } = require('../utils/cli-test-layout-chalk');
17
+
18
+ const run = promisify(exec);
19
+
20
+ /**
21
+ * When repo ships scripts/stage_integration_for_docker.py, run it before docker build.
22
+ * @param {string} contextPath
23
+ * @returns {Promise<void>}
24
+ */
25
+ async function maybeStageIntegrationForDockerBuild(contextPath) {
26
+ const scriptPath = path.join(contextPath, 'scripts/stage_integration_for_docker.py');
27
+ if (!fsSync.existsSync(scriptPath)) {
28
+ return;
29
+ }
30
+ logger.log(formatProgress('Staging integration runtime manifests for Docker…'));
31
+ await run('python3 scripts/stage_integration_for_docker.py', { cwd: contextPath });
32
+ logger.log(formatSuccessLine('Integration staging ready'));
33
+ }
34
+
35
+ module.exports = { maybeStageIntegrationForDockerBuild };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Standard Docker build orchestration (dev-promote shortcut + full build).
3
+ * @fileoverview Extracted from lib/build/index.js for file-size limits
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const chalk = require('chalk');
9
+ const { detectAppType } = require('../utils/paths');
10
+ const logger = require('../utils/logger');
11
+ const dockerBuild = require('../utils/docker-build');
12
+ const buildHelpers = require('../utils/build-helpers');
13
+ const { ensureRunSecretsForApp } = require('../app/run-env-compose');
14
+ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
15
+ const { resolveDockerBuildArgsForApp } = require('./docker-build-args');
16
+ const { maybeStageIntegrationForDockerBuild } = require('./integration-docker-staging');
17
+
18
+ /**
19
+ * Lazy require breaks circular dependency with lib/build/index.js.
20
+ * @returns {Object}
21
+ */
22
+ function getBuildIndexDeps() {
23
+ return require('./index');
24
+ }
25
+
26
+ /**
27
+ * When --from-dev --base: tag dev image as base and skip docker build when possible.
28
+ * @param {string} appName
29
+ * @param {Object} options
30
+ * @returns {Promise<string|null>} image ref when promote completed; null to continue full build
31
+ */
32
+ async function tryCompleteDevPromoteBuild(appName, options) {
33
+ const { shouldAttemptDevPromote, tryPromoteDevImageToBase } = require('../utils/promote-dev-docker-image');
34
+ if (!shouldAttemptDevPromote(options)) {
35
+ return null;
36
+ }
37
+ const promotedRef = await tryPromoteDevImageToBase(appName, options);
38
+ if (!promotedRef) {
39
+ logger.log(chalk.blue('No local dev-scoped image found; building base image...'));
40
+ return null;
41
+ }
42
+ const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
43
+ const { postBuildTasks } = getBuildIndexDeps();
44
+ await postBuildTasks(appName, buildConfig);
45
+ logger.log(formatSuccessParagraph('Build completed successfully! (promoted from dev image)'));
46
+ return promotedRef;
47
+ }
48
+
49
+ /**
50
+ * Run docker build after workspace prep (shared by standard build path).
51
+ * @param {string} appName
52
+ * @param {Object} options
53
+ * @param {Object} buildConfig
54
+ * @returns {Promise<string>}
55
+ */
56
+ async function executeDockerImageBuild(appName, options, buildConfig) {
57
+ const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
58
+ const {
59
+ prepareDevDirectory,
60
+ prepareBuildContext,
61
+ handleDockerfileGeneration,
62
+ postBuildTasks
63
+ } = getBuildIndexDeps();
64
+ const { appPath } = await detectAppType(appName);
65
+ emitManifestMetadataLineIfTTY(logger, { appKey: appName, appPath, envOnly: false, json: false });
66
+ const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(
67
+ appName,
68
+ buildConfig,
69
+ options
70
+ );
71
+ const contextPath = prepareBuildContext(buildConfig, devDir);
72
+ await maybeStageIntegrationForDockerBuild(contextPath);
73
+ const dockerfilePath = await handleDockerfileGeneration(
74
+ appName,
75
+ { devDir, buildConfig, contextPath, appConfig },
76
+ options,
77
+ buildHelpers
78
+ );
79
+ await ensureRunSecretsForApp(appName);
80
+ const buildArgs = await resolveDockerBuildArgsForApp(appName, contextPath);
81
+ const tag = options.tag || 'latest';
82
+ if (typeof tag === 'string' && tag.includes(',')) {
83
+ throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
84
+ }
85
+ await dockerBuild.executeDockerBuildWithTag(
86
+ effectiveImageName,
87
+ imageName,
88
+ dockerfilePath,
89
+ contextPath,
90
+ tag,
91
+ { ...options, buildArgs }
92
+ );
93
+ await postBuildTasks(appName, buildConfig);
94
+ logger.log(formatSuccessParagraph('Build completed successfully!'));
95
+ return `${effectiveImageName}:${tag}`;
96
+ }
97
+
98
+ /**
99
+ * Standard Docker build path after CLI header is printed (non-external apps).
100
+ * @param {string} appName
101
+ * @param {Object} options
102
+ * @returns {Promise<string>} Built image ref e.g. name:tag
103
+ */
104
+ async function runStandardDockerBuild(appName, options) {
105
+ const promotedRef = await tryCompleteDevPromoteBuild(appName, options);
106
+ if (promotedRef) {
107
+ return promotedRef;
108
+ }
109
+ const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
110
+ return executeDockerImageBuild(appName, options, buildConfig);
111
+ }
112
+
113
+ module.exports = { runStandardDockerBuild };
@@ -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
+ };