@aifabrix/builder 2.44.5 → 2.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/.cursor/rules/cli-layout.mdc +8 -4
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/README.md +15 -23
  4. package/integration/hubspot-test/README.md +2 -0
  5. package/integration/hubspot-test/test.js +5 -3
  6. package/jest.projects.js +104 -2
  7. package/lib/api/controller-health.api.js +49 -0
  8. package/lib/api/dimension-values.api.js +82 -0
  9. package/lib/api/dimensions.api.js +114 -0
  10. package/lib/api/external-systems.api.js +1 -0
  11. package/lib/api/integration-clients.api.js +168 -0
  12. package/lib/api/types/dimension-values.types.js +28 -0
  13. package/lib/api/types/dimensions.types.js +31 -0
  14. package/lib/api/types/integration-clients.types.js +45 -0
  15. package/lib/api/validation-runner.js +46 -25
  16. package/lib/app/deploy-config.js +11 -1
  17. package/lib/app/deploy-status-display.js +3 -3
  18. package/lib/app/deploy.js +36 -14
  19. package/lib/app/display.js +15 -11
  20. package/lib/app/helpers.js +3 -3
  21. package/lib/app/index.js +3 -3
  22. package/lib/app/push.js +46 -23
  23. package/lib/app/register.js +7 -6
  24. package/lib/app/restart-display.js +126 -0
  25. package/lib/app/rotate-secret.js +7 -6
  26. package/lib/app/run-container-start.js +12 -6
  27. package/lib/app/run-env-compose.js +30 -1
  28. package/lib/app/run-helpers.js +58 -19
  29. package/lib/app/run-reload-sync.js +148 -0
  30. package/lib/app/run-resolve-image.js +51 -1
  31. package/lib/app/run.js +148 -74
  32. package/lib/app/show-display.js +7 -0
  33. package/lib/app/show.js +87 -5
  34. package/lib/build/index.js +83 -49
  35. package/lib/cli/doctor-check.js +117 -0
  36. package/lib/cli/index.js +8 -2
  37. package/lib/cli/infra-guided.js +460 -0
  38. package/lib/cli/installation-log-command.js +73 -0
  39. package/lib/cli/setup-app.js +31 -3
  40. package/lib/cli/setup-auth.js +98 -27
  41. package/lib/cli/setup-dev-path-commands.js +50 -3
  42. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  43. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  44. package/lib/cli/setup-infra.js +132 -118
  45. package/lib/cli/setup-integration-client.js +182 -0
  46. package/lib/cli/setup-parameters.js +21 -2
  47. package/lib/cli/setup-platform.js +102 -0
  48. package/lib/cli/setup-secrets.js +18 -6
  49. package/lib/cli/setup-utility-resolve.js +132 -0
  50. package/lib/cli/setup-utility.js +143 -84
  51. package/lib/commands/app-logs.js +81 -33
  52. package/lib/commands/auth-config.js +116 -18
  53. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  54. package/lib/commands/datasource-capability-output.js +29 -0
  55. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  56. package/lib/commands/datasource-capability.js +411 -0
  57. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  58. package/lib/commands/datasource.js +53 -13
  59. package/lib/commands/dev-down.js +3 -3
  60. package/lib/commands/dev-infra-gate.js +32 -0
  61. package/lib/commands/dev-init.js +13 -7
  62. package/lib/commands/dimension-value.js +179 -0
  63. package/lib/commands/dimension.js +330 -0
  64. package/lib/commands/integration-client.js +430 -0
  65. package/lib/commands/login-device.js +65 -30
  66. package/lib/commands/login.js +21 -10
  67. package/lib/commands/parameters-validate.js +78 -13
  68. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  69. package/lib/commands/repair-datasource-keys.js +10 -5
  70. package/lib/commands/repair-datasource.js +19 -7
  71. package/lib/commands/repair-env-template.js +4 -1
  72. package/lib/commands/repair-openapi-sync.js +172 -0
  73. package/lib/commands/repair-persist.js +102 -0
  74. package/lib/commands/repair-rbac-extract.js +27 -0
  75. package/lib/commands/repair-rbac-migrate.js +186 -0
  76. package/lib/commands/repair-rbac.js +214 -31
  77. package/lib/commands/repair-system-alignment.js +246 -0
  78. package/lib/commands/repair-system-permissions.js +168 -0
  79. package/lib/commands/repair.js +120 -338
  80. package/lib/commands/secure.js +1 -1
  81. package/lib/commands/setup-modes.js +468 -0
  82. package/lib/commands/setup-prompts.js +421 -0
  83. package/lib/commands/setup.js +254 -0
  84. package/lib/commands/teardown.js +277 -0
  85. package/lib/commands/up-common.js +113 -19
  86. package/lib/commands/up-dataplane.js +44 -19
  87. package/lib/commands/up-miso.js +18 -18
  88. package/lib/commands/upload.js +111 -23
  89. package/lib/commands/wizard-core-helpers.js +14 -11
  90. package/lib/commands/wizard-core.js +6 -5
  91. package/lib/commands/wizard-dataplane.js +2 -2
  92. package/lib/commands/wizard-entity-selection.js +4 -3
  93. package/lib/commands/wizard-headless.js +2 -1
  94. package/lib/commands/wizard.js +2 -1
  95. package/lib/constants/infra-compose-service-names.js +40 -0
  96. package/lib/core/audit-logger.js +1 -34
  97. package/lib/core/config-admin-email.js +56 -0
  98. package/lib/core/config-normalize.js +60 -0
  99. package/lib/core/config-registered-controller-urls.js +54 -0
  100. package/lib/core/config.js +33 -50
  101. package/lib/core/env-reader.js +16 -3
  102. package/lib/core/secrets-admin-env.js +101 -0
  103. package/lib/core/secrets-ensure-infra.js +34 -1
  104. package/lib/core/secrets-ensure.js +88 -66
  105. package/lib/core/secrets-env-content.js +428 -0
  106. package/lib/core/secrets-env-declarative-expand.js +170 -0
  107. package/lib/core/secrets-env-write.js +29 -1
  108. package/lib/core/secrets-load.js +252 -0
  109. package/lib/core/secrets-names.js +32 -0
  110. package/lib/core/secrets.js +17 -757
  111. package/lib/datasource/capability/basic-exposure.js +76 -0
  112. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  113. package/lib/datasource/capability/capability-key.js +34 -0
  114. package/lib/datasource/capability/capability-resolve.js +172 -0
  115. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  116. package/lib/datasource/capability/copy-operations.js +348 -0
  117. package/lib/datasource/capability/copy-test-payload.js +139 -0
  118. package/lib/datasource/capability/create-operations.js +235 -0
  119. package/lib/datasource/capability/dimension-operations.js +151 -0
  120. package/lib/datasource/capability/dimension-validate.js +219 -0
  121. package/lib/datasource/capability/json-pointer.js +31 -0
  122. package/lib/datasource/capability/reference-rewrite.js +51 -0
  123. package/lib/datasource/capability/relate-operations.js +325 -0
  124. package/lib/datasource/capability/relate-validate.js +219 -0
  125. package/lib/datasource/capability/remove-operations.js +275 -0
  126. package/lib/datasource/capability/run-capability-copy.js +152 -0
  127. package/lib/datasource/capability/run-capability-diff.js +135 -0
  128. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  129. package/lib/datasource/capability/run-capability-edit.js +377 -0
  130. package/lib/datasource/capability/run-capability-relate.js +193 -0
  131. package/lib/datasource/capability/run-capability-remove.js +105 -0
  132. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  133. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  134. package/lib/datasource/list.js +136 -23
  135. package/lib/datasource/log-viewer.js +2 -4
  136. package/lib/datasource/unified-validation-run.js +51 -16
  137. package/lib/datasource/validate.js +53 -1
  138. package/lib/deployment/deploy-poll-ui.js +60 -0
  139. package/lib/deployment/deployer-status.js +29 -3
  140. package/lib/deployment/deployer.js +48 -30
  141. package/lib/deployment/environment.js +7 -2
  142. package/lib/deployment/poll-interval.js +72 -0
  143. package/lib/deployment/push.js +11 -9
  144. package/lib/external-system/deploy.js +9 -2
  145. package/lib/external-system/download.js +61 -32
  146. package/lib/external-system/sync-deploy-manifest.js +33 -0
  147. package/lib/infrastructure/index.js +49 -19
  148. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  149. package/lib/internal/node-fs.js +2 -0
  150. package/lib/parameters/infra-kv-discovery.js +29 -4
  151. package/lib/parameters/infra-parameter-catalog.js +6 -3
  152. package/lib/parameters/infra-parameter-validate.js +67 -19
  153. package/lib/resolvers/datasource-resolver.js +53 -0
  154. package/lib/resolvers/dimension-file.js +52 -0
  155. package/lib/resolvers/manifest-resolver.js +133 -0
  156. package/lib/schema/application-schema.json +4 -0
  157. package/lib/schema/external-datasource.schema.json +183 -53
  158. package/lib/schema/external-system.schema.json +23 -10
  159. package/lib/schema/infra.parameter.yaml +26 -1
  160. package/lib/schema/wizard-config.schema.json +1 -1
  161. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  162. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  163. package/lib/utils/app-config-resolver.js +24 -1
  164. package/lib/utils/app-run-containers.js +2 -2
  165. package/lib/utils/applications-config-defaults.js +206 -0
  166. package/lib/utils/auth-config-validator.js +2 -12
  167. package/lib/utils/bash-secret-env.js +59 -0
  168. package/lib/utils/cli-secrets-error-format.js +78 -0
  169. package/lib/utils/cli-test-layout-chalk.js +31 -9
  170. package/lib/utils/cli-utils.js +4 -36
  171. package/lib/utils/compose-generate-docker-compose.js +111 -6
  172. package/lib/utils/compose-generator.js +17 -8
  173. package/lib/utils/controller-url.js +50 -7
  174. package/lib/utils/datasource-test-run-display.js +8 -0
  175. package/lib/utils/dev-hosts-helper.js +3 -2
  176. package/lib/utils/dev-init-ssh-merge.js +2 -1
  177. package/lib/utils/docker-build.js +17 -9
  178. package/lib/utils/docker-reload-mount.js +127 -0
  179. package/lib/utils/env-copy.js +99 -14
  180. package/lib/utils/env-template.js +5 -1
  181. package/lib/utils/external-readme.js +71 -2
  182. package/lib/utils/external-system-local-test-tty.js +3 -2
  183. package/lib/utils/external-system-readiness-core.js +45 -12
  184. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  185. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  186. package/lib/utils/external-system-readiness-display.js +10 -1
  187. package/lib/utils/file-upload.js +40 -3
  188. package/lib/utils/health-check-db-init.js +107 -0
  189. package/lib/utils/health-check-public-warn.js +69 -0
  190. package/lib/utils/health-check-url.js +28 -10
  191. package/lib/utils/health-check.js +139 -107
  192. package/lib/utils/help-builder.js +5 -1
  193. package/lib/utils/image-name.js +34 -7
  194. package/lib/utils/infra-optional-service-flags.js +69 -0
  195. package/lib/utils/installation-log-core.js +282 -0
  196. package/lib/utils/installation-log-record.js +237 -0
  197. package/lib/utils/installation-log.js +123 -0
  198. package/lib/utils/integration-file-backup.js +74 -0
  199. package/lib/utils/log-redaction.js +105 -0
  200. package/lib/utils/manifest-location.js +164 -0
  201. package/lib/utils/manifest-source-emit.js +162 -0
  202. package/lib/utils/mutagen-install.js +30 -3
  203. package/lib/utils/paths.js +308 -76
  204. package/lib/utils/postgres-wipe.js +212 -0
  205. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  206. package/lib/utils/remote-dev-auth.js +21 -5
  207. package/lib/utils/remote-docker-env.js +9 -1
  208. package/lib/utils/remote-secrets-loader.js +49 -4
  209. package/lib/utils/resolve-docker-image-ref.js +9 -3
  210. package/lib/utils/run-cli-flags.js +29 -0
  211. package/lib/utils/secrets-ancestor-paths.js +47 -0
  212. package/lib/utils/secrets-canonical.js +10 -3
  213. package/lib/utils/secrets-helpers.js +17 -10
  214. package/lib/utils/secrets-kv-refs.js +42 -0
  215. package/lib/utils/secrets-kv-scope.js +19 -2
  216. package/lib/utils/secrets-materialize-local.js +134 -0
  217. package/lib/utils/secrets-path.js +26 -13
  218. package/lib/utils/secrets-utils.js +20 -10
  219. package/lib/utils/system-builder-root.js +42 -0
  220. package/lib/utils/url-declarative-public-base.js +80 -12
  221. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  222. package/lib/utils/url-declarative-resolve-build.js +24 -388
  223. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  224. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  225. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  226. package/lib/utils/url-declarative-resolve.js +47 -7
  227. package/lib/utils/url-declarative-runtime-base-path.js +52 -0
  228. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  229. package/lib/utils/urls-local-registry-scan.js +103 -0
  230. package/lib/utils/urls-local-registry.js +158 -76
  231. package/lib/utils/validation-poll-ui.js +81 -0
  232. package/lib/utils/validation-run-poll.js +29 -5
  233. package/lib/utils/with-muted-logger.js +53 -0
  234. package/package.json +3 -1
  235. package/templates/applications/dataplane/application.yaml +5 -1
  236. package/templates/applications/dataplane/rbac.yaml +10 -10
  237. package/templates/applications/keycloak/env.template +8 -6
  238. package/templates/applications/miso-controller/application.yaml +9 -0
  239. package/templates/applications/miso-controller/env.template +27 -29
  240. package/templates/applications/miso-controller/rbac.yaml +9 -9
  241. package/templates/external-system/README.md.hbs +83 -123
  242. package/.npmrc.token +0 -1
  243. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  244. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  245. package/.nyc_output/processinfo/index.json +0 -1
  246. package/lib/api/service-users.api.js +0 -150
  247. package/lib/api/types/service-users.types.js +0 -65
  248. package/lib/cli/setup-service-user.js +0 -187
  249. package/lib/commands/service-user.js +0 -429
@@ -0,0 +1,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
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Shared string redaction for file logs (audit, installation), CLI output, and
3
+ * `aifabrix logs <app>` container env lines (PII / secrets).
4
+ * Single implementation to avoid secret-handling drift between logs.
5
+ *
6
+ * @fileoverview Sensitive substring masking for operational logs
7
+ * @author AI Fabrix Team
8
+ * @version 2.0.0
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ /** Env key patterns that indicate a secret (mask value) — same rules as aifabrix logs <app>. */
14
+ const SECRET_KEY_PATTERN = /password|secret|token|credential|api[_-]?key/i;
15
+
16
+ /** Prefixes to strip before checking key (avoids masking KEYCLOAK_SERVER_URL etc.) */
17
+ const KEY_PREFIXES_TO_STRIP = /^KEYCLOAK_|^KEY_VAULT_/;
18
+
19
+ /**
20
+ * URL with embedded credentials: scheme://user:password@host → scheme://user:***@host
21
+ * (shared with {@link maskEnvLine} / aifabrix logs <app> env dump)
22
+ */
23
+ const URL_CREDENTIAL_PATTERN = /(\w+:\/\/)([^:@]*):([^@]+)@/g;
24
+
25
+ /**
26
+ * Masks embedded user:password@ in URLs inside any string.
27
+ *
28
+ * @param {string} text
29
+ * @returns {string}
30
+ */
31
+ function maskUrlEmbeddedCredentials(text) {
32
+ if (!text || typeof text !== 'string') {
33
+ return text;
34
+ }
35
+ return text.replace(URL_CREDENTIAL_PATTERN, '$1$2:***@');
36
+ }
37
+
38
+ /**
39
+ * Masks a single env line (KEY=value) for PII/secrets — same behavior as aifabrix logs <app> environment dump.
40
+ *
41
+ * @param {string} line - Line in form KEY=value
42
+ * @returns {string} Same line or KEY=*** or value with masked URL credentials
43
+ */
44
+ function maskEnvLine(line) {
45
+ const eq = line.indexOf('=');
46
+ if (eq <= 0) {
47
+ return line;
48
+ }
49
+ const key = line.slice(0, eq);
50
+ const value = line.slice(eq + 1);
51
+
52
+ const keyForCheck = key.replace(KEY_PREFIXES_TO_STRIP, '');
53
+ const isSecretKey = SECRET_KEY_PATTERN.test(keyForCheck);
54
+
55
+ const maskedValue = maskUrlEmbeddedCredentials(value);
56
+ const hasUrlCredentials = maskedValue !== value;
57
+
58
+ if (isSecretKey) {
59
+ return `${key}=***`;
60
+ }
61
+ if (hasUrlCredentials) {
62
+ return `${key}=${maskedValue}`;
63
+ }
64
+ return line;
65
+ }
66
+
67
+ /**
68
+ * Masks sensitive data in strings (audit/installation log messages, metadata).
69
+ * Also applies URL embedded-credential masking (same as {@link maskEnvLine} values).
70
+ *
71
+ * @param {string} value - Value to mask
72
+ * @returns {string}
73
+ */
74
+ function maskSensitiveData(value) {
75
+ if (!value || typeof value !== 'string') {
76
+ return value;
77
+ }
78
+
79
+ const sensitivePatterns = [
80
+ { pattern: /password[=:]\s*([^\s]+)/gi, replacement: 'password=***' },
81
+ { pattern: /secret[=:]\s*([^\s]+)/gi, replacement: 'secret=***' },
82
+ { pattern: /key[=:]\s*([^\s]+)/gi, replacement: 'key=***' },
83
+ { pattern: /token[=:]\s*([^\s]+)/gi, replacement: 'token=***' },
84
+ { pattern: /api[_-]?key[=:]\s*([^\s]+)/gi, replacement: 'api_key=***' }
85
+ ];
86
+
87
+ let masked = value;
88
+ for (const { pattern, replacement } of sensitivePatterns) {
89
+ masked = masked.replace(pattern, replacement);
90
+ }
91
+
92
+ masked = maskUrlEmbeddedCredentials(masked);
93
+
94
+ if (/^[a-f0-9]{32,}$/i.test(masked.trim())) {
95
+ return '***';
96
+ }
97
+
98
+ return masked;
99
+ }
100
+
101
+ module.exports = {
102
+ maskSensitiveData,
103
+ maskEnvLine,
104
+ maskUrlEmbeddedCredentials
105
+ };
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Canonical on-disk application manifest discovery (plan 141 Tier 1 + Tier 2).
3
+ * Lazy-loads `./paths` inside functions to avoid circular dependency with `paths.js`.
4
+ *
5
+ * @fileoverview resolveApplicationManifestPathSync for cwd-first manifest picks
6
+ * @author AI Fabrix Team
7
+ * @version 1.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const path = require('path');
13
+ const { nodeFs } = require('../internal/node-fs');
14
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
15
+
16
+ const fsSync = nodeFs();
17
+
18
+ /**
19
+ * @returns {import('./paths')}
20
+ */
21
+ function getPaths() {
22
+ return require('./paths');
23
+ }
24
+
25
+ /**
26
+ * @param {string} dir
27
+ * @returns {boolean}
28
+ */
29
+ function hasApplicationConfig(dir) {
30
+ try {
31
+ resolveApplicationConfigPath(dir);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @param {string} dir
40
+ * @returns {boolean}
41
+ */
42
+ function isExistingDir(dir) {
43
+ try {
44
+ return fsSync.existsSync(dir) && fsSync.statSync(dir).isDirectory();
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * @param {string} cwd
52
+ * @param {string} targetKey
53
+ * @returns {{ absolutePath: string, tier: string, appKey: string }|null}
54
+ */
55
+ function tryTier1Integration(cwd, targetKey) {
56
+ const dir = path.join(cwd, 'integration', targetKey);
57
+ if (!isExistingDir(dir) || !hasApplicationConfig(dir)) {
58
+ return null;
59
+ }
60
+ return { absolutePath: path.resolve(dir), tier: 'cwd-integration', appKey: targetKey };
61
+ }
62
+
63
+ /**
64
+ * @param {string} cwd
65
+ * @param {string} targetKey
66
+ * @returns {{ absolutePath: string, tier: string, appKey: string }|null}
67
+ */
68
+ function tryTier1Builder(cwd, targetKey) {
69
+ const dir = path.join(cwd, 'builder', targetKey);
70
+ if (!isExistingDir(dir) || !hasApplicationConfig(dir)) {
71
+ return null;
72
+ }
73
+ return { absolutePath: path.resolve(dir), tier: 'cwd-builder', appKey: targetKey };
74
+ }
75
+
76
+ /**
77
+ * Tier 2: `(aifabrix-work | aifabrix-home)/builder/<key>` when a resolvable application manifest exists.
78
+ *
79
+ * @param {string} targetKey
80
+ * @returns {{ absolutePath: string, tier: string, appKey: string }|null}
81
+ */
82
+ function tryTier2MaterializationBuilder(targetKey) {
83
+ const { getSystemBuilderRoot } = getPaths();
84
+ const dir = path.join(getSystemBuilderRoot(), targetKey);
85
+ if (!isExistingDir(dir) || !hasApplicationConfig(dir)) {
86
+ return null;
87
+ }
88
+ return { absolutePath: path.resolve(dir), tier: 'system-builder', appKey: targetKey };
89
+ }
90
+
91
+ /**
92
+ * @param {string|undefined} cwdOpt
93
+ * @returns {string}
94
+ */
95
+ function resolveCwdSafe(cwdOpt) {
96
+ try {
97
+ return path.resolve(cwdOpt ? cwdOpt : process.cwd());
98
+ } catch {
99
+ return path.resolve(process.cwd());
100
+ }
101
+ }
102
+
103
+ /**
104
+ * @param {{ targetKey?: string, mode?: string, cwd?: string }|null|undefined} opts
105
+ * @returns {{ targetKey: string, mode: string, cwd: string }|null}
106
+ */
107
+ function parseManifestResolveOpts(opts) {
108
+ const targetKey = opts && typeof opts.targetKey === 'string' ? opts.targetKey.trim() : '';
109
+ if (!targetKey) {
110
+ return null;
111
+ }
112
+ const mode = opts && opts.mode ? opts.mode : 'auto';
113
+ const cwd = resolveCwdSafe(opts && opts.cwd);
114
+ return { targetKey, mode, cwd };
115
+ }
116
+
117
+ /**
118
+ * @param {string} mode
119
+ * @param {string} cwd
120
+ * @param {string} targetKey
121
+ * @returns {{ absolutePath: string, tier: string, appKey: string }|null}
122
+ */
123
+ function tryTier1ByMode(mode, cwd, targetKey) {
124
+ if (mode === 'integration' || mode === 'auto') {
125
+ const integrationHit = tryTier1Integration(cwd, targetKey);
126
+ if (integrationHit) {
127
+ return integrationHit;
128
+ }
129
+ }
130
+ if (mode === 'builder' || mode === 'auto') {
131
+ const builderHit = tryTier1Builder(cwd, targetKey);
132
+ if (builderHit) {
133
+ return builderHit;
134
+ }
135
+ }
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * Resolves `application.yaml` (or sibling config) for an app using plan 141 order:
141
+ * `cwd/integration/<key>` → `cwd/builder/<key>` → `(aifabrix-work | aifabrix-home)/builder/<key>`.
142
+ *
143
+ * @param {{ targetKey: string, mode?: 'auto'|'integration'|'builder', cwd?: string }} opts
144
+ * @returns {{ absolutePath: string, tier: string, appKey: string }|null} Null when not found
145
+ */
146
+ function resolveApplicationManifestPathSync(opts) {
147
+ const parsed = parseManifestResolveOpts(opts);
148
+ if (!parsed) {
149
+ return null;
150
+ }
151
+ const { targetKey, mode, cwd } = parsed;
152
+ const tier1 = tryTier1ByMode(mode, cwd, targetKey);
153
+ if (tier1) {
154
+ return tier1;
155
+ }
156
+ if (mode === 'auto') {
157
+ return tryTier2MaterializationBuilder(targetKey);
158
+ }
159
+ return null;
160
+ }
161
+
162
+ module.exports = {
163
+ resolveApplicationManifestPathSync
164
+ };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Plan 141 P2: one gray **Manifest:** line on TTY when a command has resolved an application manifest.
3
+ *
4
+ * @fileoverview emitManifestMetadataLineIfTTY
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const { resolveApplicationConfigPath } = require('./app-config-resolver');
13
+ const { resolveApplicationManifestPathSync } = require('./manifest-location');
14
+ const { metadata } = require('./cli-test-layout-chalk');
15
+ const pathsUtil = require('./paths');
16
+
17
+ /**
18
+ * @param {string} tier - Tier id from {@link resolveApplicationManifestPathSync}
19
+ * @returns {string}
20
+ */
21
+ function humanTierLabel(tier) {
22
+ if (tier === 'cwd-integration') return 'cwd/integration';
23
+ if (tier === 'cwd-builder') return 'cwd/builder';
24
+ if (tier === 'system-builder') return 'system-builder';
25
+ return tier;
26
+ }
27
+
28
+ /**
29
+ * Gray metadata line: `Manifest: <tier> — <configPath>`.
30
+ *
31
+ * @param {{ tier: string, configPath: string }} args
32
+ * @returns {string}
33
+ */
34
+ function formatManifestSourceMetadataLine(args) {
35
+ const tier = args && typeof args.tier === 'string' ? args.tier : 'resolved';
36
+ const configPath = args && typeof args.configPath === 'string' ? args.configPath : '';
37
+ return metadata(`Manifest: ${humanTierLabel(tier)} — ${configPath}`);
38
+ }
39
+
40
+ /**
41
+ * @param {string} appKey
42
+ * @param {string} appPath
43
+ * @param {string} [cwd]
44
+ * @returns {string}
45
+ */
46
+ function computeTierForAppPath(appKey, appPath, cwd) {
47
+ const hit = resolveApplicationManifestPathSync({
48
+ targetKey: appKey,
49
+ cwd: cwd || process.cwd()
50
+ });
51
+ if (hit && path.resolve(hit.absolutePath) === path.resolve(appPath)) {
52
+ return hit.tier;
53
+ }
54
+ return 'resolved';
55
+ }
56
+
57
+ /**
58
+ * Machine-readable manifest provenance for `--json` consumers (plan 141).
59
+ *
60
+ * @param {string} appKey
61
+ * @param {string} appPath - Application directory (absolute)
62
+ * @param {{ cwd?: string }} [opts]
63
+ * @returns {{ tier: string, tierLabel: string, configPath: string }}
64
+ */
65
+ function getManifestSourcePayload(appKey, appPath, opts = {}) {
66
+ const key = appKey && typeof appKey === 'string' ? appKey.trim() : '';
67
+ const dir = appPath && typeof appPath === 'string' ? appPath.trim() : '';
68
+ if (!key || !dir) {
69
+ return { tier: 'unknown', tierLabel: 'unknown', configPath: '' };
70
+ }
71
+ let configPath = '';
72
+ try {
73
+ configPath = resolveApplicationConfigPath(dir);
74
+ } catch {
75
+ configPath = '';
76
+ }
77
+ let tier = 'resolved';
78
+ try {
79
+ tier = computeTierForAppPath(key, dir, opts.cwd);
80
+ } catch {
81
+ tier = 'resolved';
82
+ }
83
+ return {
84
+ tier,
85
+ tierLabel: humanTierLabel(tier),
86
+ configPath
87
+ };
88
+ }
89
+
90
+ /**
91
+ * @returns {boolean}
92
+ */
93
+ function isStdoutTty() {
94
+ return Boolean(process.stdout && process.stdout.isTTY);
95
+ }
96
+
97
+ /**
98
+ * Logs one gray manifest line when appropriate (TTY, not JSON, not env-only).
99
+ *
100
+ * @param {{ log: function(string): void }} logger - e.g. `require('./logger')`
101
+ * @param {{ appKey: string, appPath: string, envOnly?: boolean, json?: boolean, cwd?: string }} opts
102
+ * @returns {void}
103
+ */
104
+ function emitManifestMetadataLineIfTTY(logger, opts) {
105
+ if (!logger || typeof logger.log !== 'function') return;
106
+ if (!opts || opts.json || opts.envOnly) return;
107
+ if (!isStdoutTty()) return;
108
+ const appKey = opts.appKey && typeof opts.appKey === 'string' ? opts.appKey.trim() : '';
109
+ const appPath = opts.appPath && typeof opts.appPath === 'string' ? opts.appPath.trim() : '';
110
+ if (!appKey || !appPath) return;
111
+ let configPath;
112
+ try {
113
+ configPath = resolveApplicationConfigPath(appPath);
114
+ } catch {
115
+ return;
116
+ }
117
+ const tier = computeTierForAppPath(appKey, appPath, opts.cwd);
118
+ logger.log(formatManifestSourceMetadataLine({ tier, configPath }));
119
+ }
120
+
121
+ /**
122
+ * One gray **Manifest:** line for a platform system builder app (`keycloak`, `miso-controller`, `dataplane`).
123
+ *
124
+ * @param {{ log: function(string): void }} logger
125
+ * @param {string} appKey
126
+ * @param {{ envOnly?: boolean, json?: boolean, cwd?: string }} [opts]
127
+ * @returns {void}
128
+ */
129
+ function emitSystemBuilderAppManifestLineIfTTY(logger, appKey, opts = {}) {
130
+ if (!appKey || typeof appKey !== 'string') return;
131
+ emitManifestMetadataLineIfTTY(logger, {
132
+ appKey,
133
+ appPath: pathsUtil.getBuilderPath(appKey),
134
+ envOnly: !!opts.envOnly,
135
+ json: !!opts.json,
136
+ cwd: opts.cwd
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Gray **Manifest:** line for each platform app (Keycloak, Miso Controller, dataplane), in order.
142
+ * Used after templates exist (e.g. guided `up-platform` after muted registry prep).
143
+ *
144
+ * @param {{ log: function(string): void }} logger
145
+ * @param {{ envOnly?: boolean, json?: boolean, cwd?: string }} [opts]
146
+ * @returns {void}
147
+ */
148
+ function emitAllPlatformSystemManifestLinesIfTTY(logger, opts = {}) {
149
+ for (const appKey of pathsUtil.SYSTEM_BUILDER_APP_KEYS) {
150
+ emitSystemBuilderAppManifestLineIfTTY(logger, appKey, opts);
151
+ }
152
+ }
153
+
154
+ module.exports = {
155
+ emitManifestMetadataLineIfTTY,
156
+ emitSystemBuilderAppManifestLineIfTTY,
157
+ emitAllPlatformSystemManifestLinesIfTTY,
158
+ formatManifestSourceMetadataLine,
159
+ computeTierForAppPath,
160
+ humanTierLabel,
161
+ getManifestSourcePayload
162
+ };
@@ -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