@aifabrix/builder 2.44.5 → 2.44.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +48 -2
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/validation-runner.js +46 -25
  17. package/lib/app/deploy-config.js +11 -1
  18. package/lib/app/deploy-status-display.js +3 -3
  19. package/lib/app/deploy.js +36 -14
  20. package/lib/app/display.js +15 -11
  21. package/lib/app/push.js +46 -23
  22. package/lib/app/register.js +1 -1
  23. package/lib/app/restart-display.js +95 -0
  24. package/lib/app/rotate-secret.js +1 -1
  25. package/lib/app/run-container-start.js +12 -6
  26. package/lib/app/run-env-compose.js +30 -1
  27. package/lib/app/run-helpers.js +44 -12
  28. package/lib/app/run-reload-sync.js +148 -0
  29. package/lib/app/run-resolve-image.js +51 -1
  30. package/lib/app/run.js +99 -73
  31. package/lib/build/index.js +75 -45
  32. package/lib/cli/doctor-check.js +117 -0
  33. package/lib/cli/index.js +8 -2
  34. package/lib/cli/infra-guided.js +445 -0
  35. package/lib/cli/setup-app.js +20 -2
  36. package/lib/cli/setup-auth.js +26 -0
  37. package/lib/cli/setup-dev-path-commands.js +50 -3
  38. package/lib/cli/setup-infra.js +134 -61
  39. package/lib/cli/setup-integration-client.js +182 -0
  40. package/lib/cli/setup-parameters.js +21 -2
  41. package/lib/cli/setup-platform.js +102 -0
  42. package/lib/cli/setup-secrets.js +18 -6
  43. package/lib/cli/setup-utility.js +78 -33
  44. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  45. package/lib/commands/datasource-capability-output.js +29 -0
  46. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  47. package/lib/commands/datasource-capability.js +411 -0
  48. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  49. package/lib/commands/datasource.js +53 -13
  50. package/lib/commands/dev-down.js +3 -3
  51. package/lib/commands/dev-infra-gate.js +32 -0
  52. package/lib/commands/dev-init.js +13 -7
  53. package/lib/commands/dimension-value.js +179 -0
  54. package/lib/commands/dimension.js +330 -0
  55. package/lib/commands/integration-client.js +430 -0
  56. package/lib/commands/login-device.js +65 -30
  57. package/lib/commands/login.js +21 -10
  58. package/lib/commands/parameters-validate.js +78 -13
  59. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  60. package/lib/commands/repair-datasource-keys.js +10 -5
  61. package/lib/commands/repair-datasource.js +19 -7
  62. package/lib/commands/repair-env-template.js +4 -1
  63. package/lib/commands/repair-openapi-sync.js +172 -0
  64. package/lib/commands/repair-persist.js +102 -0
  65. package/lib/commands/repair-rbac-extract.js +27 -0
  66. package/lib/commands/repair-rbac-migrate.js +186 -0
  67. package/lib/commands/repair-rbac.js +214 -31
  68. package/lib/commands/repair-system-alignment.js +246 -0
  69. package/lib/commands/repair-system-permissions.js +168 -0
  70. package/lib/commands/repair.js +120 -338
  71. package/lib/commands/secure.js +1 -1
  72. package/lib/commands/setup-modes.js +455 -0
  73. package/lib/commands/setup-prompts.js +388 -0
  74. package/lib/commands/setup.js +149 -0
  75. package/lib/commands/teardown.js +228 -0
  76. package/lib/commands/up-common.js +79 -19
  77. package/lib/commands/up-dataplane.js +33 -11
  78. package/lib/commands/up-miso.js +7 -11
  79. package/lib/commands/upload.js +109 -23
  80. package/lib/commands/wizard-core-helpers.js +14 -11
  81. package/lib/commands/wizard-core.js +6 -5
  82. package/lib/commands/wizard-dataplane.js +2 -2
  83. package/lib/commands/wizard-entity-selection.js +4 -3
  84. package/lib/commands/wizard-headless.js +2 -1
  85. package/lib/commands/wizard.js +2 -1
  86. package/lib/constants/infra-compose-service-names.js +40 -0
  87. package/lib/core/env-reader.js +16 -3
  88. package/lib/core/secrets-admin-env.js +101 -0
  89. package/lib/core/secrets-ensure-infra.js +34 -1
  90. package/lib/core/secrets-ensure.js +88 -66
  91. package/lib/core/secrets-env-content.js +432 -0
  92. package/lib/core/secrets-env-write.js +27 -1
  93. package/lib/core/secrets-load.js +248 -0
  94. package/lib/core/secrets-names.js +32 -0
  95. package/lib/core/secrets.js +17 -757
  96. package/lib/datasource/capability/basic-exposure.js +76 -0
  97. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  98. package/lib/datasource/capability/capability-key.js +34 -0
  99. package/lib/datasource/capability/capability-resolve.js +172 -0
  100. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  101. package/lib/datasource/capability/copy-operations.js +348 -0
  102. package/lib/datasource/capability/copy-test-payload.js +139 -0
  103. package/lib/datasource/capability/create-operations.js +235 -0
  104. package/lib/datasource/capability/dimension-operations.js +151 -0
  105. package/lib/datasource/capability/dimension-validate.js +219 -0
  106. package/lib/datasource/capability/json-pointer.js +31 -0
  107. package/lib/datasource/capability/reference-rewrite.js +51 -0
  108. package/lib/datasource/capability/relate-operations.js +325 -0
  109. package/lib/datasource/capability/relate-validate.js +219 -0
  110. package/lib/datasource/capability/remove-operations.js +275 -0
  111. package/lib/datasource/capability/run-capability-copy.js +152 -0
  112. package/lib/datasource/capability/run-capability-diff.js +135 -0
  113. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  114. package/lib/datasource/capability/run-capability-edit.js +377 -0
  115. package/lib/datasource/capability/run-capability-relate.js +193 -0
  116. package/lib/datasource/capability/run-capability-remove.js +105 -0
  117. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  118. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  119. package/lib/datasource/list.js +136 -23
  120. package/lib/datasource/log-viewer.js +2 -4
  121. package/lib/datasource/unified-validation-run.js +51 -16
  122. package/lib/datasource/validate.js +53 -1
  123. package/lib/deployment/deploy-poll-ui.js +60 -0
  124. package/lib/deployment/deployer-status.js +29 -3
  125. package/lib/deployment/deployer.js +48 -30
  126. package/lib/deployment/environment.js +7 -2
  127. package/lib/deployment/poll-interval.js +72 -0
  128. package/lib/deployment/push.js +11 -9
  129. package/lib/external-system/deploy.js +4 -1
  130. package/lib/external-system/download.js +61 -32
  131. package/lib/external-system/sync-deploy-manifest.js +33 -0
  132. package/lib/infrastructure/index.js +49 -19
  133. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  134. package/lib/parameters/infra-kv-discovery.js +29 -4
  135. package/lib/parameters/infra-parameter-catalog.js +6 -3
  136. package/lib/parameters/infra-parameter-validate.js +67 -19
  137. package/lib/resolvers/datasource-resolver.js +53 -0
  138. package/lib/resolvers/dimension-file.js +52 -0
  139. package/lib/resolvers/manifest-resolver.js +133 -0
  140. package/lib/schema/external-datasource.schema.json +183 -53
  141. package/lib/schema/external-system.schema.json +23 -10
  142. package/lib/schema/infra.parameter.yaml +26 -11
  143. package/lib/schema/wizard-config.schema.json +1 -1
  144. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  145. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  146. package/lib/utils/app-run-containers.js +2 -2
  147. package/lib/utils/bash-secret-env.js +59 -0
  148. package/lib/utils/cli-secrets-error-format.js +78 -0
  149. package/lib/utils/cli-test-layout-chalk.js +31 -9
  150. package/lib/utils/cli-utils.js +4 -36
  151. package/lib/utils/datasource-test-run-display.js +8 -0
  152. package/lib/utils/dev-hosts-helper.js +3 -2
  153. package/lib/utils/dev-init-ssh-merge.js +2 -1
  154. package/lib/utils/docker-build.js +17 -9
  155. package/lib/utils/docker-reload-mount.js +127 -0
  156. package/lib/utils/external-readme.js +71 -2
  157. package/lib/utils/external-system-local-test-tty.js +3 -2
  158. package/lib/utils/external-system-readiness-core.js +45 -12
  159. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  160. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  161. package/lib/utils/external-system-readiness-display.js +10 -1
  162. package/lib/utils/file-upload.js +40 -3
  163. package/lib/utils/health-check-db-init.js +107 -0
  164. package/lib/utils/health-check-public-warn.js +69 -0
  165. package/lib/utils/health-check-url.js +19 -4
  166. package/lib/utils/health-check.js +135 -105
  167. package/lib/utils/help-builder.js +5 -1
  168. package/lib/utils/image-name.js +34 -7
  169. package/lib/utils/integration-file-backup.js +74 -0
  170. package/lib/utils/mutagen-install.js +30 -3
  171. package/lib/utils/paths.js +108 -25
  172. package/lib/utils/postgres-wipe.js +212 -0
  173. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  174. package/lib/utils/remote-dev-auth.js +21 -5
  175. package/lib/utils/remote-docker-env.js +9 -1
  176. package/lib/utils/remote-secrets-loader.js +42 -3
  177. package/lib/utils/resolve-docker-image-ref.js +9 -3
  178. package/lib/utils/secrets-ancestor-paths.js +47 -0
  179. package/lib/utils/secrets-helpers.js +17 -10
  180. package/lib/utils/secrets-kv-refs.js +42 -0
  181. package/lib/utils/secrets-kv-scope.js +19 -2
  182. package/lib/utils/secrets-materialize-local.js +134 -0
  183. package/lib/utils/secrets-path.js +24 -10
  184. package/lib/utils/secrets-utils.js +2 -2
  185. package/lib/utils/system-builder-root.js +34 -0
  186. package/lib/utils/url-declarative-resolve-build.js +6 -1
  187. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  188. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  189. package/lib/utils/urls-local-registry.js +23 -12
  190. package/lib/utils/validation-poll-ui.js +81 -0
  191. package/lib/utils/validation-run-poll.js +29 -5
  192. package/lib/utils/with-muted-logger.js +53 -0
  193. package/package.json +1 -1
  194. package/templates/applications/dataplane/application.yaml +1 -1
  195. package/templates/applications/dataplane/rbac.yaml +10 -10
  196. package/templates/applications/keycloak/env.template +8 -6
  197. package/templates/applications/miso-controller/application.yaml +7 -0
  198. package/templates/applications/miso-controller/env.template +1 -1
  199. package/templates/applications/miso-controller/rbac.yaml +9 -9
  200. package/templates/external-system/README.md.hbs +83 -123
  201. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  202. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  203. package/.nyc_output/processinfo/index.json +0 -1
  204. package/lib/api/service-users.api.js +0 -150
  205. package/lib/api/types/service-users.types.js +0 -65
  206. package/lib/cli/setup-service-user.js +0 -187
  207. package/lib/commands/service-user.js +0 -429
@@ -12,16 +12,24 @@ const { formatSuccessLine } = require('./cli-test-layout-chalk');
12
12
  const http = require('http');
13
13
  const https = require('https');
14
14
  const net = require('net');
15
+ const dns = require('dns');
15
16
  const chalk = require('chalk');
16
17
  const logger = require('./logger');
17
18
  const { execWithDockerEnv } = require('./docker-exec');
18
19
  const { computeTraefikHealthCheckUrl } = require('./health-check-url');
20
+ const { computePathActive } = require('./url-declarative-url-flags');
21
+ const { isFrontDoorRoutingEnabledInDoc } = require('./url-declarative-vdir-inactive-env');
22
+ const { waitForDbInit } = require('./health-check-db-init');
23
+ const {
24
+ filterTraefikUrlByDns,
25
+ logPublicHealthUrlWarningIfNeeded
26
+ } = require('./health-check-public-warn');
19
27
 
20
28
  /**
21
29
  * Compute the health check URL for an app.
22
30
  *
23
- * - Default (no Traefik front-door): http://localhost:<port><healthPath>
24
- * - With Traefik + app frontDoorRouting.enabled: <publicBase><frontDoorRouting.pattern><healthPath>
31
+ * - Default (path inactive): http://localhost:<port><healthPath> (e.g. Keycloak with KC_HTTP_RELATIVE_PATH=/)
32
+ * - Path active (Traefik on frontDoorRouting.enabled): localhost probe uses same vdir as the container (e.g. /auth/health/ready)
25
33
  *
26
34
  * @async
27
35
  * @param {string} appName
@@ -29,117 +37,67 @@ const { computeTraefikHealthCheckUrl } = require('./health-check-url');
29
37
  * @param {Object|null} appConfig
30
38
  * @param {Object} opts
31
39
  * @param {Object} [opts.runOptions] - runApp options (may include env + effectiveEnvironmentScopedResources)
40
+ * @param {boolean} [opts.skipTraefikPublicUrl] - Omit Traefik URL (localhost leg in dual-probe flow only).
32
41
  * @returns {Promise<string>}
33
42
  */
34
43
  async function computeHealthCheckUrl(appName, healthCheckPort, appConfig, _opts = {}) {
35
- const healthCheckPath = appConfig?.healthCheck?.path || '/health';
44
+ const rawHealthPath = appConfig?.healthCheck?.path || '/health';
36
45
 
37
- // Traefik front-door branch: probe via resolved public host + path (e.g. https://dev02.builder02.local/auth/health/ready)
38
- try {
39
- const traefikUrl = await computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig);
40
- if (traefikUrl) return traefikUrl;
41
- } catch {
42
- // If any resolver step fails, fall back to localhost probing.
46
+ function computeLocalhostHealthPath() {
47
+ // Plan 124 pathActive: prepend front-door pattern when Traefik on; keep bare /health for miso/dataplane style.
48
+ try {
49
+ const runOptions = _opts && typeof _opts === 'object' && _opts.runOptions ? _opts.runOptions : null;
50
+ const traefikOn = Boolean(runOptions && runOptions.traefikEnabled === true);
51
+ const fd = appConfig && appConfig.frontDoorRouting ? appConfig.frontDoorRouting : null;
52
+ const pattern = fd && typeof fd.pattern === 'string' ? fd.pattern : null;
53
+ const pathActive = computePathActive(traefikOn, isFrontDoorRoutingEnabledInDoc(appConfig || null));
54
+ const shouldMount = pathActive && Boolean(pattern) && rawHealthPath !== '/health';
55
+ if (!shouldMount) return rawHealthPath;
56
+ const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('./health-check-url');
57
+ const mountPath = normalizeFrontDoorPatternForHealth(pattern);
58
+ return joinUrlPath(mountPath, rawHealthPath);
59
+ } catch {
60
+ return rawHealthPath;
61
+ }
43
62
  }
44
63
 
45
- // Default: probe local published port.
46
- return `http://localhost:${healthCheckPort}${healthCheckPath}`;
47
- }
64
+ const localhostHealthPath = computeLocalhostHealthPath();
48
65
 
49
- /**
50
- * Checks if db-init container exists and waits for it to complete
51
- * @async
52
- * @function waitForDbInit
53
- * @param {string} appName - Application name
54
- * @throws {Error} If db-init fails
55
- */
56
- /**
57
- * Checks if db-init container exists
58
- * @async
59
- * @function checkDbInitContainerExists
60
- * @param {string} dbInitContainer - Container name
61
- * @returns {Promise<boolean>} True if container exists
62
- */
63
- async function checkDbInitContainerExists(dbInitContainer) {
64
- try {
65
- const { stdout } = await execWithDockerEnv(`docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`);
66
- return stdout.trim() === dbInitContainer;
67
- } catch {
68
- return false;
69
- }
70
- }
71
-
72
- /**
73
- * Gets container exit code
74
- * @async
75
- * @function getContainerExitCode
76
- * @param {string} dbInitContainer - Container name
77
- * @returns {Promise<string>} Exit code
78
- */
79
- async function getContainerExitCode(dbInitContainer) {
80
- const { stdout: exitCode } = await execWithDockerEnv(`docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`);
81
- return exitCode.trim();
82
- }
83
-
84
- /**
85
- * Handles exited container status
86
- * @async
87
- * @function handleExitedContainer
88
- * @param {string} dbInitContainer - Container name
89
- * @returns {Promise<boolean>} True if handled (container already exited)
90
- */
91
- async function handleExitedContainer(dbInitContainer) {
92
- const { stdout: status } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
93
- if (status.trim() === 'exited') {
94
- const exitCode = await getContainerExitCode(dbInitContainer);
95
- if (exitCode === '0') {
96
- logger.log(formatSuccessLine('Database initialization already completed'));
97
- } else {
98
- logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
66
+ // Local readiness probes localhost; optional Traefik URL is for display / dual-probe (see waitForHealthCheck).
67
+ async function maybeGetTraefikUrl() {
68
+ if (_opts && _opts.skipTraefikPublicUrl) return '';
69
+ const runOptions = (_opts && typeof _opts === 'object') ? _opts.runOptions : null;
70
+ const wantsTraefik =
71
+ Boolean(runOptions) &&
72
+ (runOptions.probeViaTraefik === true || runOptions.traefikEnabled === true);
73
+ if (!wantsTraefik) return '';
74
+ try {
75
+ return await computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig);
76
+ } catch {
77
+ return '';
99
78
  }
100
- return true;
101
79
  }
102
- return false;
103
- }
104
80
 
105
- /**
106
- * Waits for container to exit
107
- * @async
108
- * @function waitForContainerExit
109
- * @param {string} dbInitContainer - Container name
110
- * @param {number} maxAttempts - Maximum attempts
111
- */
112
- async function waitForContainerExit(dbInitContainer, maxAttempts) {
113
- for (let attempts = 0; attempts < maxAttempts; attempts++) {
114
- const { stdout: currentStatus } = await execWithDockerEnv(`docker inspect --format='{{.State.Status}}' ${dbInitContainer}`);
115
- if (currentStatus.trim() === 'exited') {
116
- const exitCode = await getContainerExitCode(dbInitContainer);
117
- if (exitCode === '0') {
118
- logger.log(formatSuccessLine('Database initialization completed'));
119
- } else {
120
- logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
121
- }
122
- return;
123
- }
124
- await new Promise(resolve => setTimeout(resolve, 1000));
125
- }
81
+ const traefikUrl = await maybeGetTraefikUrl();
82
+ if (traefikUrl) return traefikUrl;
83
+
84
+ return `http://localhost:${healthCheckPort}${localhostHealthPath}`;
126
85
  }
127
86
 
128
- async function waitForDbInit(appName) {
129
- const dbInitContainer = `aifabrix-${appName}-db-init`;
87
+ async function isHostnameResolvable(hostname, debug) {
88
+ if (!hostname) return false;
89
+ const hn = String(hostname).trim().toLowerCase();
90
+ if (!hn) return false;
91
+ if (hn === 'localhost' || hn === '127.0.0.1' || hn === '::1') return true;
130
92
  try {
131
- if (!(await checkDbInitContainerExists(dbInitContainer))) {
132
- return;
133
- }
134
-
135
- if (await handleExitedContainer(dbInitContainer)) {
136
- return;
93
+ await dns.promises.lookup(hn);
94
+ return true;
95
+ } catch (err) {
96
+ // ENOTFOUND: caller may log a single post-success warning with the full public health URL.
97
+ if (debug && !(err && err.code === 'ENOTFOUND')) {
98
+ logger.log(chalk.gray(`[DEBUG] DNS lookup failed for ${hostname}: ${err.message}`));
137
99
  }
138
-
139
- logger.log(chalk.blue('Waiting for database initialization to complete...'));
140
- await waitForContainerExit(dbInitContainer, 30);
141
- } catch (error) {
142
- // db-init container might not exist, which is fine
100
+ return false;
143
101
  }
144
102
  }
145
103
 
@@ -411,24 +369,96 @@ async function performHealthCheckAttempt(healthCheckUrl, attempt, maxAttempts, d
411
369
  return false;
412
370
  }
413
371
 
372
+ async function computePreferredHealthCheckUrls(appName, healthCheckPort, config, runOptions, debug) {
373
+ const localhostUrl = await computeHealthCheckUrl(appName, healthCheckPort, config, {
374
+ runOptions: runOptions && typeof runOptions === 'object' ? runOptions : {},
375
+ skipTraefikPublicUrl: true
376
+ });
377
+
378
+ let traefikUrl = '';
379
+ /** Full Traefik/public health URL when DNS fails — used for one post-success warning. */
380
+ let skippedPublicHealthUrl = '';
381
+ const wantsTraefikFirst = Boolean(
382
+ runOptions && (runOptions.probeViaTraefik === true || runOptions.traefikEnabled === true)
383
+ );
384
+ if (wantsTraefikFirst) {
385
+ try {
386
+ traefikUrl = await computeTraefikHealthCheckUrl(appName, healthCheckPort, config);
387
+ } catch {
388
+ traefikUrl = '';
389
+ }
390
+ }
391
+
392
+ const filtered = await filterTraefikUrlByDns(traefikUrl, debug, isHostnameResolvable);
393
+ traefikUrl = filtered.traefikUrl;
394
+ skippedPublicHealthUrl = filtered.skippedPublicHealthUrl;
395
+
396
+ const urlsToTry = traefikUrl ? [traefikUrl, localhostUrl] : [localhostUrl];
397
+ if (urlsToTry.length > 1) {
398
+ logger.log(
399
+ chalk.gray(
400
+ `ℹ Health check order: Traefik/DNS (${urlsToTry[0]}), then localhost (${urlsToTry[1]}).`
401
+ )
402
+ );
403
+ }
404
+ if (debug) {
405
+ logger.log(chalk.gray(`[DEBUG] Health check URLs: ${urlsToTry.join(' | ')}`));
406
+ }
407
+ return { urlsToTry, skippedPublicHealthUrl };
408
+ }
409
+
410
+ async function performHealthCheckAttemptForUrls(urlsToTry, attempt, maxAttempts, debug) {
411
+ for (let i = 0; i < urlsToTry.length; i++) {
412
+ const url = urlsToTry[i];
413
+ const passed = await performHealthCheckAttempt(url, attempt, maxAttempts, debug);
414
+ if (passed) {
415
+ return { ok: true, resolvedIndex: i };
416
+ }
417
+ }
418
+ return { ok: false, resolvedIndex: -1 };
419
+ }
420
+
414
421
  async function waitForHealthCheck(appName, timeout = 90, port = null, config = null, debug = false, runOptions = {}) {
415
422
  await waitForDbInit(appName);
416
423
 
417
424
  const healthCheckPort = await determineHealthCheckPort(port, appName, debug);
418
425
  const { maxAttempts } = buildHealthCheckConfig(healthCheckPort, config, timeout, debug);
419
- const healthCheckUrl = await computeHealthCheckUrl(appName, healthCheckPort, config, { runOptions });
420
- if (debug) {
421
- logger.log(chalk.gray(`[DEBUG] Health check URL: ${healthCheckUrl}`));
426
+ const { urlsToTry, skippedPublicHealthUrl } = await computePreferredHealthCheckUrls(
427
+ appName,
428
+ healthCheckPort,
429
+ config,
430
+ runOptions,
431
+ debug
432
+ );
433
+
434
+ if (skippedPublicHealthUrl && urlsToTry.length === 1) {
435
+ logger.log(
436
+ chalk.gray(
437
+ `ℹ Health check: public URL not used (DNS): ${skippedPublicHealthUrl}. ` +
438
+ `Probing ${urlsToTry[0]} only until the app responds.`
439
+ )
440
+ );
422
441
  }
423
442
 
424
443
  for (let attempts = 0; attempts < maxAttempts; attempts++) {
425
- const passed = await performHealthCheckAttempt(healthCheckUrl, attempts, maxAttempts, debug);
426
- if (passed) {
444
+ const attemptResult = await performHealthCheckAttemptForUrls(urlsToTry, attempts, maxAttempts, debug);
445
+ if (attemptResult.ok) {
446
+ logPublicHealthUrlWarningIfNeeded({
447
+ skippedPublicHealthUrl,
448
+ urlsToTry,
449
+ resolvedIndex: attemptResult.resolvedIndex
450
+ });
427
451
  return;
428
452
  }
429
453
 
430
454
  if (attempts < maxAttempts - 1) {
431
- logger.log(chalk.yellow(`Waiting for health check... (${attempts + 1}/${maxAttempts}) ${healthCheckUrl}`));
455
+ const probeHint =
456
+ urlsToTry.length > 1
457
+ ? `trying ${urlsToTry[0]}, then ${urlsToTry[1]}`
458
+ : (urlsToTry[0] || 'health URL');
459
+ logger.log(
460
+ chalk.yellow(`Waiting for health check… (${attempts + 1}/${maxAttempts}) (${probeHint})`)
461
+ );
432
462
  await new Promise(resolve => setTimeout(resolve, 2000));
433
463
  }
434
464
  }
@@ -20,6 +20,8 @@ const CATEGORIES = [
20
20
  {
21
21
  name: 'Infrastructure (Local Development)',
22
22
  commands: [
23
+ { name: 'setup' },
24
+ { name: 'teardown' },
23
25
  { name: 'up-infra' },
24
26
  { name: 'up-platform' },
25
27
  { name: 'up-miso' },
@@ -72,7 +74,7 @@ const CATEGORIES = [
72
74
  { name: 'app' },
73
75
  { name: 'credential' },
74
76
  { name: 'deployment' },
75
- { name: 'service-user' }
77
+ { name: 'integration-client' }
76
78
  ]
77
79
  },
78
80
  {
@@ -96,6 +98,8 @@ const CATEGORIES = [
96
98
  { name: 'delete', term: 'delete <systemKey>' },
97
99
  { name: 'repair', term: 'repair <systemKey>' },
98
100
  { name: 'datasource' },
101
+ { name: 'dimension' },
102
+ { name: 'dimension-value' },
99
103
  { name: 'test', term: 'test <app>' },
100
104
  { name: 'test-e2e', term: 'test-e2e <app>' },
101
105
  { name: 'test-integration', term: 'test-integration <app>' }
@@ -11,18 +11,18 @@
11
11
 
12
12
  /**
13
13
  * Builds a developer-scoped image name for local Docker builds.
14
- * Format: "<base>-dev<developerId>".
15
- * If developerId is missing, non-numeric, or 0 → "<base>-extra".
14
+ * Format: "<base>-dev<developerId>" when developerId is a positive integer.
15
+ * If developerId is missing, non-numeric, or 0 → returns baseName (manifest image; no dev suffix).
16
16
  *
17
17
  * @function buildDevImageName
18
18
  * @param {string} baseName - Base image name (no registry), e.g., "myapp"
19
19
  * @param {(string|number|null|undefined)} developerId - Developer identifier
20
- * @returns {string} Developer-scoped image name
20
+ * @returns {string} Developer-scoped image name or base name when id is 0 / absent
21
21
  *
22
22
  * @example
23
23
  * buildDevImageName('myapp', 123) // "myapp-dev123"
24
- * buildDevImageName('myapp', '0') // "myapp-extra"
25
- * buildDevImageName('myapp') // "myapp-extra"
24
+ * buildDevImageName('myapp', '0') // "myapp"
25
+ * buildDevImageName('myapp') // "myapp"
26
26
  */
27
27
  function buildDevImageName(baseName, developerId) {
28
28
  const id =
@@ -37,13 +37,40 @@ function buildDevImageName(baseName, developerId) {
37
37
  }
38
38
 
39
39
  if (!Number.isFinite(id) || id === 0) {
40
- return `${baseName}-extra`;
40
+ return baseName;
41
41
  }
42
42
 
43
43
  return `${baseName}-dev${id}`;
44
44
  }
45
45
 
46
+ /**
47
+ * Developer-scoped repository path for run/build resolution (may include registry prefix).
48
+ * For qualified paths (slashes), only the last segment gets `-dev<id>`; id 0 returns path unchanged.
49
+ *
50
+ * @param {string} repositoryPath - Full repository path (e.g. "reg/ns/app" or "app")
51
+ * @param {(string|number|null|undefined)} developerId - Developer id from config
52
+ * @returns {string}
53
+ */
54
+ function buildDevImageRepositoryPath(repositoryPath, developerId) {
55
+ if (!repositoryPath || typeof repositoryPath !== 'string') {
56
+ throw new Error('Repository path is required and must be a string');
57
+ }
58
+ const idNum =
59
+ typeof developerId === 'number' ? developerId : parseInt(String(developerId), 10);
60
+ if (!Number.isFinite(idNum) || idNum === 0) {
61
+ return repositoryPath;
62
+ }
63
+ const idx = repositoryPath.lastIndexOf('/');
64
+ const tail = idx === -1 ? repositoryPath : repositoryPath.slice(idx + 1);
65
+ const scopedTail = buildDevImageName(tail, developerId);
66
+ if (idx === -1) {
67
+ return scopedTail;
68
+ }
69
+ return `${repositoryPath.slice(0, idx)}/${scopedTail}`;
70
+ }
71
+
46
72
  module.exports = {
47
- buildDevImageName
73
+ buildDevImageName,
74
+ buildDevImageRepositoryPath
48
75
  };
49
76
 
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Timestamped backups under integration/<app>/backup/ (same layout as datasource capability copy).
3
+ *
4
+ * @fileoverview Backup before mutating integration JSON/YAML
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ /**
15
+ * True if path exists and is a regular file (not mocked by typical existsSync spies in unit tests).
16
+ * @param {string} filePath
17
+ * @returns {boolean}
18
+ */
19
+ function isRegularFile(filePath) {
20
+ try {
21
+ return fs.statSync(filePath).isFile();
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Copies filePath into neighbor backup/ with ISO timestamp suffix.
29
+ * @param {string} filePath - Absolute path to file to copy
30
+ * @param {boolean} noBackup - When true, skip and return null
31
+ * @returns {string|null} Destination path or null
32
+ */
33
+ function writeBackup(filePath, noBackup) {
34
+ if (noBackup) {
35
+ return null;
36
+ }
37
+ const dir = path.dirname(filePath);
38
+ const backupDir = path.join(dir, 'backup');
39
+ fs.mkdirSync(backupDir, { recursive: true });
40
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
41
+ const base = path.basename(filePath);
42
+ const dest = path.join(backupDir, `${base}.${ts}.bak`);
43
+ fs.copyFileSync(filePath, dest);
44
+ return dest;
45
+ }
46
+
47
+ /**
48
+ * Backs up an existing file once per repair run (dedupes by absolute path).
49
+ * @param {string} filePath - Path to file that will be overwritten
50
+ * @param {{ dryRun?: boolean, noBackup?: boolean, backupPaths?: string[], backedUpFiles?: Set<string> }} ctx
51
+ * @returns {string|null}
52
+ */
53
+ function backupIntegrationFile(filePath, ctx) {
54
+ const { dryRun, noBackup, backupPaths, backedUpFiles } = ctx || {};
55
+ if (dryRun || noBackup || !filePath || !isRegularFile(filePath)) {
56
+ return null;
57
+ }
58
+ const abs = path.resolve(filePath);
59
+ if (backedUpFiles) {
60
+ if (backedUpFiles.has(abs)) return null;
61
+ backedUpFiles.add(abs);
62
+ }
63
+ const dest = writeBackup(filePath, false);
64
+ if (dest && Array.isArray(backupPaths)) {
65
+ backupPaths.push(dest);
66
+ }
67
+ return dest;
68
+ }
69
+
70
+ module.exports = {
71
+ writeBackup,
72
+ backupIntegrationFile,
73
+ isRegularFile
74
+ };
@@ -9,6 +9,7 @@
9
9
 
10
10
  const path = require('path');
11
11
  const fs = require('fs');
12
+ const http = require('http');
12
13
  const https = require('https');
13
14
  const { getAifabrixHome } = require('./paths');
14
15
  const { exec } = require('child_process');
@@ -88,10 +89,36 @@ function fetchLatestRelease() {
88
89
  * @param {(msg: string) => void} [log] - Optional progress logger
89
90
  * @returns {Promise<void>}
90
91
  */
91
- function downloadToFile(url, destPath, log) {
92
+ /**
93
+ * @param {string} url
94
+ * @param {string} destPath
95
+ * @param {(msg: string) => void} [log]
96
+ * @param {number} [redirectsLeft]
97
+ * @returns {Promise<void>}
98
+ */
99
+ function downloadToFile(url, destPath, log, redirectsLeft = 8) {
92
100
  return new Promise((resolve, reject) => {
101
+ if (redirectsLeft <= 0) {
102
+ reject(new Error('Download redirect loop'));
103
+ return;
104
+ }
93
105
  const file = fs.createWriteStream(destPath, { flags: 'w' });
94
- const req = https.get(url, { headers: { 'User-Agent': 'aifabrix-builder-cli' } }, (res) => {
106
+ const lib = url.startsWith('http:') ? http : https;
107
+ const req = lib.get(url, { headers: { 'User-Agent': 'aifabrix-builder-cli' } }, (res) => {
108
+ const loc = res.headers.location;
109
+ if ([301, 302, 303, 307, 308].includes(res.statusCode || 0) && loc) {
110
+ file.close();
111
+ fs.unlink(destPath, () => {});
112
+ let nextUrl;
113
+ try {
114
+ nextUrl = new URL(loc, url).href;
115
+ } catch (e) {
116
+ reject(new Error(`Invalid redirect Location: ${loc}`));
117
+ return;
118
+ }
119
+ downloadToFile(nextUrl, destPath, log, redirectsLeft - 1).then(resolve).catch(reject);
120
+ return;
121
+ }
95
122
  if (res.statusCode !== 200) {
96
123
  file.close();
97
124
  fs.unlink(destPath, () => {});
@@ -111,7 +138,7 @@ function downloadToFile(url, destPath, log) {
111
138
  req.setTimeout(120000, () => {
112
139
  req.destroy(); reject(new Error('Download timeout'));
113
140
  });
114
- if (typeof log === 'function') log('Downloading Mutagen...');
141
+ if (typeof log === 'function' && redirectsLeft === 8) log('Downloading Mutagen...');
115
142
  });
116
143
  }
117
144