@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,69 @@
1
+ /**
2
+ * Optional infra compose flags (Traefik, pgAdmin, Redis Commander) for config.yaml ↔ startInfra.
3
+ *
4
+ * @fileoverview Effective flag resolution + backfill missing keys after up-infra / setup
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const config = require('../core/config');
12
+
13
+ /**
14
+ * Resolves effective boolean from CLI option vs config.
15
+ * @param {*} optValue - options.traefik | options.pgAdmin | options.redisAdmin
16
+ * @param {*} cfgValue - cfg.traefik | cfg.pgadmin | cfg.redisCommander
17
+ * @param {boolean} defaultWhenUndef - Default when config value is undefined
18
+ * @returns {boolean}
19
+ */
20
+ function resolveInfraOptionalFlag(optValue, cfgValue, defaultWhenUndef = true) {
21
+ if (optValue === true) return true;
22
+ if (optValue === false) return false;
23
+ return cfgValue !== false && (cfgValue === true || defaultWhenUndef);
24
+ }
25
+
26
+ /**
27
+ * Effective optional infra service flags (same resolution as up-infra → startInfra).
28
+ *
29
+ * @param {Object} cfg - Config from {@link config.getConfig}
30
+ * @param {Object} [options] - Commander options (omit for setup-modes)
31
+ * @returns {{ traefik: boolean, pgadmin: boolean, redisCommander: boolean }}
32
+ */
33
+ function computeEffectiveInfraOptionalFlags(cfg, options = {}) {
34
+ return {
35
+ traefik: resolveInfraOptionalFlag(options.traefik, cfg.traefik, false),
36
+ pgadmin: resolveInfraOptionalFlag(options.pgAdmin, cfg.pgadmin, true),
37
+ redisCommander: resolveInfraOptionalFlag(options.redisAdmin, cfg.redisCommander, true)
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Writes `traefik` / `pgadmin` / `redisCommander` when missing so config matches compose defaults.
43
+ *
44
+ * @param {Object} cfg - Mutable config (updated when save runs)
45
+ * @param {{ traefik: boolean, pgadmin: boolean, redisCommander: boolean }} effective
46
+ * @returns {Promise<void>}
47
+ */
48
+ async function persistMissingInfraOptionalServiceFlags(cfg, effective) {
49
+ const keys = ['traefik', 'pgadmin', 'redisCommander'];
50
+ const merged = { ...cfg };
51
+ let dirty = false;
52
+ for (const k of keys) {
53
+ if (typeof merged[k] === 'undefined') {
54
+ merged[k] = effective[k];
55
+ dirty = true;
56
+ }
57
+ }
58
+ if (!dirty) {
59
+ return;
60
+ }
61
+ await config.saveConfig(merged);
62
+ Object.assign(cfg, merged);
63
+ }
64
+
65
+ module.exports = {
66
+ resolveInfraOptionalFlag,
67
+ computeEffectiveInfraOptionalFlags,
68
+ persistMissingInfraOptionalServiceFlags
69
+ };
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Shared helpers for installation.log (no append I/O).
3
+ *
4
+ * @fileoverview installation log core utilities
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const fsSync = require('fs');
12
+ const path = require('path');
13
+ const paths = require('./paths');
14
+ const { maskSensitiveData } = require('./log-redaction');
15
+ const { loadConfigFile } = require('./config-format');
16
+ const { resolveDockerImageRef } = require('./resolve-docker-image-ref');
17
+ const { parseImageOptions } = require('../commands/up-miso');
18
+
19
+ let opCounter = 0;
20
+ let opDay = '';
21
+
22
+ let cachedCliVersion = null;
23
+
24
+ /**
25
+ * @returns {string}
26
+ */
27
+ function getCliVersion() {
28
+ if (cachedCliVersion) {
29
+ return cachedCliVersion;
30
+ }
31
+ try {
32
+ const pkgPath = path.join(__dirname, '..', '..', 'package.json');
33
+ const raw = fsSync.readFileSync(pkgPath, 'utf8');
34
+ const pkg = JSON.parse(raw);
35
+ cachedCliVersion = typeof pkg.version === 'string' ? pkg.version : 'unknown';
36
+ } catch {
37
+ cachedCliVersion = 'unknown';
38
+ }
39
+ return cachedCliVersion;
40
+ }
41
+
42
+ /**
43
+ * @returns {string} e.g. op_20260513_001
44
+ */
45
+ function createOperationId() {
46
+ const d = new Date();
47
+ const day = d.toISOString().slice(0, 10).replace(/-/g, '');
48
+ if (day !== opDay) {
49
+ opDay = day;
50
+ opCounter = 0;
51
+ }
52
+ opCounter += 1;
53
+ return `op_${day}_${String(opCounter).padStart(3, '0')}`;
54
+ }
55
+
56
+ /**
57
+ * @param {string} ref
58
+ * @returns {string}
59
+ */
60
+ function stripDockerDigest(ref) {
61
+ if (!ref || typeof ref !== 'string') {
62
+ return ref;
63
+ }
64
+ return ref.replace(/@sha256:[a-f0-9]+$/i, '');
65
+ }
66
+
67
+ /**
68
+ * @param {string|undefined} url
69
+ * @returns {string|undefined}
70
+ */
71
+ function sanitizeUrl(url) {
72
+ if (!url || typeof url !== 'string') {
73
+ return undefined;
74
+ }
75
+ try {
76
+ const u = new URL(url);
77
+ u.username = '';
78
+ u.password = '';
79
+ let s = u.toString();
80
+ if (s.endsWith('/')) {
81
+ s = s.slice(0, -1);
82
+ }
83
+ return s;
84
+ } catch {
85
+ return maskSensitiveData(url);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * @param {Object} [options]
91
+ * @returns {'interactive'|'automation'}
92
+ */
93
+ function resolveLogMode(options = {}) {
94
+ const assume =
95
+ options.yes === true ||
96
+ options.assumeYes === true ||
97
+ process.env.CI === 'true' ||
98
+ process.env.CI === '1';
99
+ if (assume) {
100
+ return 'automation';
101
+ }
102
+ if (process.stdin && process.stdin.isTTY && process.stdout && process.stdout.isTTY) {
103
+ return 'interactive';
104
+ }
105
+ return 'automation';
106
+ }
107
+
108
+ /**
109
+ * @param {boolean|undefined} optVal
110
+ * @returns {boolean}
111
+ */
112
+ function cliBoolExplicit(optVal) {
113
+ return optVal === true || optVal === false;
114
+ }
115
+
116
+ /**
117
+ * @param {string} label
118
+ * @param {boolean} value
119
+ * @param {boolean} fromCli
120
+ * @returns {string}
121
+ */
122
+ function infraLine(label, value, fromCli) {
123
+ const src = fromCli ? 'cli override' : 'config';
124
+ return ` ${label}: ${value} (${src})`;
125
+ }
126
+
127
+ /**
128
+ * @param {Object} cfg
129
+ * @param {Object} options
130
+ * @returns {{ lines: string[] }}
131
+ */
132
+ function buildInfraSectionLines(cfg, options = {}) {
133
+ const { computeEffectiveInfraOptionalFlags } = require('./infra-optional-service-flags');
134
+ const effective = computeEffectiveInfraOptionalFlags(cfg, options);
135
+ const tls = cfg.tlsEnabled === true;
136
+ const tlsFromCli = cliBoolExplicit(options.tls);
137
+ const lines = [
138
+ 'Infra',
139
+ infraLine('traefik', effective.traefik, cliBoolExplicit(options.traefik)),
140
+ infraLine('tlsEnabled', tls, tlsFromCli),
141
+ infraLine('pgAdmin', effective.pgadmin, cliBoolExplicit(options.pgAdmin)),
142
+ infraLine('redisCommander', effective.redisCommander, cliBoolExplicit(options.redisAdmin))
143
+ ];
144
+ return { lines };
145
+ }
146
+
147
+ /**
148
+ * @param {string} appName
149
+ * @returns {Object|null}
150
+ */
151
+ function loadBuilderAppVariables(appName) {
152
+ try {
153
+ const dir = paths.getBuilderPath(appName);
154
+ if (!fsSync.existsSync(dir)) {
155
+ return null;
156
+ }
157
+ const configPath = paths.resolveApplicationConfigPath(dir);
158
+ return loadConfigFile(configPath) || {};
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+
164
+ /**
165
+ * @param {string} appName
166
+ * @param {Object} map
167
+ * @param {Object} runOpts
168
+ * @returns {void}
169
+ */
170
+ function applyParsedImageMapToRunOpts(appName, map, runOpts) {
171
+ if (appName === 'keycloak' && map.keycloak) {
172
+ runOpts.image = map.keycloak;
173
+ }
174
+ if (appName === 'miso-controller' && map['miso-controller']) {
175
+ runOpts.image = map['miso-controller'];
176
+ }
177
+ if (appName === 'dataplane' && map.dataplane) {
178
+ runOpts.image = map.dataplane;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * @param {string} appName
184
+ * @param {Object} options
185
+ * @returns {Object}
186
+ */
187
+ function buildImageResolveOpts(appName, options) {
188
+ const runOpts = {
189
+ registry: options.registry || undefined,
190
+ base: options.base !== false
191
+ };
192
+ const imageList = options.image;
193
+ if (Array.isArray(imageList)) {
194
+ applyParsedImageMapToRunOpts(appName, parseImageOptions(imageList), runOpts);
195
+ } else if (typeof imageList === 'string' && imageList.trim()) {
196
+ const t = imageList.trim();
197
+ if (!t.includes('=') && appName === 'dataplane') {
198
+ runOpts.image = t;
199
+ } else if (t.includes('=')) {
200
+ applyParsedImageMapToRunOpts(appName, parseImageOptions([t]), runOpts);
201
+ }
202
+ }
203
+ return runOpts;
204
+ }
205
+
206
+ /**
207
+ * @param {string} imageRef
208
+ * @returns {string}
209
+ */
210
+ function imageRefForLog(imageRef) {
211
+ return stripDockerDigest(String(imageRef).trim());
212
+ }
213
+
214
+ /**
215
+ * @param {string} appName
216
+ * @param {Object} options
217
+ * @returns {string}
218
+ */
219
+ function resolveAppImageDisplay(appName, options) {
220
+ const variables = loadBuilderAppVariables(appName);
221
+ if (!variables) {
222
+ return 'unknown';
223
+ }
224
+ try {
225
+ const runOpts = buildImageResolveOpts(appName, options);
226
+ const { imageName, imageTag } = resolveDockerImageRef(appName, variables, runOpts);
227
+ return imageRefForLog(`${imageName}:${imageTag}`);
228
+ } catch {
229
+ return 'unknown';
230
+ }
231
+ }
232
+
233
+ /**
234
+ * @param {string[]} appNames
235
+ * @param {Object} options
236
+ * @returns {Record<string,string>}
237
+ */
238
+ function collectPlatformAppImages(appNames, options = {}) {
239
+ /** @type {Record<string,string>} */
240
+ const out = {};
241
+ for (const app of appNames) {
242
+ out[app] = resolveAppImageDisplay(app, options);
243
+ }
244
+ return out;
245
+ }
246
+
247
+ /**
248
+ * @param {Record<string,string>} platformApps
249
+ * @returns {string}
250
+ */
251
+ function derivePlatformVersion(platformApps) {
252
+ const tags = new Set();
253
+ for (const ref of Object.values(platformApps)) {
254
+ if (!ref || ref === 'unknown') {
255
+ continue;
256
+ }
257
+ const noDig = stripDockerDigest(ref);
258
+ const idx = noDig.lastIndexOf(':');
259
+ if (idx <= 0 || idx >= noDig.length - 1) {
260
+ continue;
261
+ }
262
+ const tag = noDig.slice(idx + 1);
263
+ if (!tag.includes('/')) {
264
+ tags.add(tag);
265
+ }
266
+ }
267
+ if (tags.size === 0) {
268
+ return 'unknown';
269
+ }
270
+ return [...tags].sort().join('|');
271
+ }
272
+
273
+ module.exports = {
274
+ getCliVersion,
275
+ createOperationId,
276
+ stripDockerDigest,
277
+ sanitizeUrl,
278
+ resolveLogMode,
279
+ buildInfraSectionLines,
280
+ collectPlatformAppImages,
281
+ derivePlatformVersion
282
+ };
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Build deterministic installation.log record lines (no I/O).
3
+ *
4
+ * @fileoverview installation record body formatting
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const config = require('../core/config');
12
+ const { maskSensitiveData } = require('./log-redaction');
13
+ const {
14
+ createOperationId,
15
+ getCliVersion,
16
+ collectPlatformAppImages,
17
+ derivePlatformVersion,
18
+ buildInfraSectionLines,
19
+ resolveLogMode,
20
+ sanitizeUrl
21
+ } = require('./installation-log-core');
22
+
23
+ /**
24
+ * @param {string[]} lines
25
+ * @param {string} isoHeader
26
+ * @returns {void}
27
+ */
28
+ function pushTopBanner(lines, isoHeader) {
29
+ lines.push('='.repeat(80));
30
+ lines.push(`INSTALLATION ${isoHeader}`);
31
+ lines.push('-'.repeat(80));
32
+ }
33
+
34
+ /**
35
+ * @param {string[]} lines
36
+ * @param {Object} payload
37
+ * @param {Object} identity
38
+ * @returns {void}
39
+ */
40
+ function pushIdentityCore(lines, payload, identity) {
41
+ const { startedAt, completedAt, developerId, mode, platformVersion, durationSec } = identity;
42
+ lines.push('recordVersion: 1');
43
+ lines.push(`operationId: ${createOperationId()}`);
44
+ lines.push(`command: ${payload.command}`);
45
+ lines.push(`outcome: ${payload.outcome}`);
46
+ lines.push(`durationSec: ${durationSec}`);
47
+ lines.push(`cliVersion: ${getCliVersion()}`);
48
+ lines.push(`platformVersion: ${platformVersion}`);
49
+ lines.push(`developerId: ${developerId}`);
50
+ lines.push(`mode: ${mode}`);
51
+ lines.push(`startedAt: ${startedAt.toISOString()}`);
52
+ lines.push(`completedAt: ${completedAt.toISOString()}`);
53
+
54
+ if (payload.setupMode) {
55
+ lines.push(`setupMode: ${payload.setupMode}`);
56
+ }
57
+ if (payload.upPlatformForce) {
58
+ lines.push('upPlatformForce: true');
59
+ }
60
+ lines.push('');
61
+ }
62
+
63
+ /**
64
+ * @param {string[]} lines
65
+ * @param {Object} payload
66
+ * @param {Object} identity
67
+ * @param {Date} identity.startedAt
68
+ * @param {Date} identity.completedAt
69
+ * @param {string} identity.developerId
70
+ * @param {string} identity.mode
71
+ * @param {string} identity.platformVersion
72
+ * @returns {void}
73
+ */
74
+ function pushHeaderAndIdentity(lines, payload, identity) {
75
+ const { startedAt, completedAt } = identity;
76
+ const durationSec = Math.round(
77
+ Math.max(0, completedAt.getTime() - startedAt.getTime()) / 1000
78
+ );
79
+ const isoHeader = completedAt.toISOString();
80
+ pushTopBanner(lines, isoHeader);
81
+ pushIdentityCore(lines, payload, { ...identity, durationSec });
82
+ }
83
+
84
+ /**
85
+ * @param {string[]} lines
86
+ * @param {Object} payload
87
+ * @returns {void}
88
+ */
89
+ function pushInfraBlock(lines, payload) {
90
+ if (!payload.infra || !payload.infra.cfg) {
91
+ return;
92
+ }
93
+ const { lines: infraLines } = buildInfraSectionLines(payload.infra.cfg, payload.infra.options || {});
94
+ lines.push(...infraLines);
95
+ lines.push('');
96
+ }
97
+
98
+ /**
99
+ * @param {string[]} lines
100
+ * @param {Record<string,string>|undefined} platformApps
101
+ * @returns {void}
102
+ */
103
+ function pushPlatformAppsBlock(lines, platformApps) {
104
+ if (!platformApps || Object.keys(platformApps).length === 0) {
105
+ return;
106
+ }
107
+ lines.push('Platform Apps');
108
+ for (const key of Object.keys(platformApps).sort()) {
109
+ lines.push(` ${key}: ${platformApps[key]}`);
110
+ }
111
+ lines.push('');
112
+ }
113
+
114
+ /**
115
+ * @param {string[]} lines
116
+ * @param {Object} payload
117
+ * @returns {void}
118
+ */
119
+ function pushConfigBlock(lines, payload) {
120
+ const cfgExtra = payload.configExtra || {};
121
+ const hasConfig =
122
+ cfgExtra.controllerUrl ||
123
+ cfgExtra.adminEmail === 'set' ||
124
+ cfgExtra.adminEmail === 'unset';
125
+ if (!hasConfig) {
126
+ return;
127
+ }
128
+ lines.push('Config');
129
+ if (cfgExtra.controllerUrl) {
130
+ lines.push(` controllerUrl: ${sanitizeUrl(cfgExtra.controllerUrl)}`);
131
+ }
132
+ if (cfgExtra.adminEmail === 'set' || cfgExtra.adminEmail === 'unset') {
133
+ lines.push(` adminEmail: ${cfgExtra.adminEmail}`);
134
+ }
135
+ lines.push('');
136
+ }
137
+
138
+ /**
139
+ * @param {string[]} lines
140
+ * @param {Object} payload
141
+ * @returns {void}
142
+ */
143
+ function pushCleanupBlock(lines, payload) {
144
+ const cleanup = payload.cleanup;
145
+ if (!cleanup || typeof cleanup !== 'object' || Object.keys(cleanup).length === 0) {
146
+ return;
147
+ }
148
+ lines.push('Cleanup');
149
+ if (cleanup.volumesRemoved !== undefined) {
150
+ lines.push(` volumesRemoved: ${cleanup.volumesRemoved}`);
151
+ }
152
+ if (cleanup.configPreserved !== undefined) {
153
+ lines.push(` configPreserved: ${cleanup.configPreserved}`);
154
+ }
155
+ if (Array.isArray(cleanup.cleanedAppKeys) && cleanup.cleanedAppKeys.length > 0) {
156
+ lines.push(` cleanedAppKeys: ${cleanup.cleanedAppKeys.sort().join(', ')}`);
157
+ }
158
+ lines.push('');
159
+ }
160
+
161
+ /**
162
+ * @param {string[]} lines
163
+ * @param {Object} payload
164
+ * @returns {void}
165
+ */
166
+ function pushErrorSection(lines, payload) {
167
+ if (payload.outcome !== 'failure' || !payload.error) {
168
+ return;
169
+ }
170
+ const msg =
171
+ typeof payload.error.message === 'string'
172
+ ? payload.error.message
173
+ : String(payload.error);
174
+ lines.push('Error');
175
+ lines.push(` message: ${maskSensitiveData(msg)}`);
176
+ if (payload.errorCode) {
177
+ lines.push(` code: ${maskSensitiveData(String(payload.errorCode))}`);
178
+ }
179
+ lines.push('');
180
+ }
181
+
182
+ /**
183
+ * @param {Object} payload
184
+ * @returns {Promise<Object>}
185
+ */
186
+ async function resolvePlatformContextForRecord(payload) {
187
+ const completedAt = payload.completedAt || new Date();
188
+ const startedAt = payload.startedAt || completedAt;
189
+ const options = payload.options || {};
190
+ const mode = resolveLogMode(options);
191
+
192
+ let developerId = 'unknown';
193
+ try {
194
+ const id = await config.getDeveloperId();
195
+ developerId = id === null || id === undefined ? 'unknown' : String(id);
196
+ } catch {
197
+ // ignore
198
+ }
199
+
200
+ let platformApps = payload.platformApps;
201
+ if (!platformApps && Array.isArray(payload.platformAppList) && payload.platformAppList.length > 0) {
202
+ platformApps = collectPlatformAppImages(payload.platformAppList, options);
203
+ }
204
+ const platformVersion =
205
+ payload.platformVersion ||
206
+ (platformApps ? derivePlatformVersion(platformApps) : 'unknown');
207
+
208
+ return { completedAt, startedAt, mode, developerId, platformApps, platformVersion };
209
+ }
210
+
211
+ /**
212
+ * @param {Object} payload
213
+ * @returns {Promise<string[]>}
214
+ */
215
+ async function buildInstallationRecordLines(payload) {
216
+ const ctx = await resolvePlatformContextForRecord(payload);
217
+ const lines = [];
218
+ pushHeaderAndIdentity(lines, payload, {
219
+ startedAt: ctx.startedAt,
220
+ completedAt: ctx.completedAt,
221
+ developerId: ctx.developerId,
222
+ mode: ctx.mode,
223
+ platformVersion: ctx.platformVersion
224
+ });
225
+ pushInfraBlock(lines, payload);
226
+ pushPlatformAppsBlock(lines, ctx.platformApps);
227
+ pushConfigBlock(lines, payload);
228
+ pushCleanupBlock(lines, payload);
229
+ pushErrorSection(lines, payload);
230
+ lines.push('='.repeat(80));
231
+ lines.push('');
232
+ return lines;
233
+ }
234
+
235
+ module.exports = {
236
+ buildInstallationRecordLines
237
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Append-only operational log for platform install / infra commands.
3
+ * One record per top-level CLI command; no secrets; deterministic section order.
4
+ *
5
+ * @fileoverview installation.log writer beside config.yaml
6
+ * @author AI Fabrix Team
7
+ * @version 2.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fs = require('fs').promises;
13
+ const path = require('path');
14
+ const paths = require('./paths');
15
+ const config = require('../core/config');
16
+ const logger = require('./logger');
17
+ const { buildInstallationRecordLines } = require('./installation-log-record');
18
+ const { sanitizeUrl } = require('./installation-log-core');
19
+
20
+ const INSTALLATION_LOG = 'installation.log';
21
+ /** Rotate when log exceeds this size; keep .1 and .2 backups. */
22
+ const MAX_LOG_BYTES = 10 * 1024 * 1024;
23
+
24
+ /**
25
+ * @param {string} logPath
26
+ * @param {number} [maxBytes]
27
+ * @returns {Promise<void>}
28
+ */
29
+ async function rotateInstallationLogIfNeeded(logPath, maxBytes = MAX_LOG_BYTES) {
30
+ let st;
31
+ try {
32
+ st = await fs.stat(logPath);
33
+ } catch (e) {
34
+ if (e && e.code === 'ENOENT') {
35
+ return;
36
+ }
37
+ throw e;
38
+ }
39
+ if (st.size < maxBytes) {
40
+ return;
41
+ }
42
+ const dir = path.dirname(logPath);
43
+ const base = path.basename(logPath);
44
+ const p2 = path.join(dir, `${base}.2`);
45
+ const p1 = path.join(dir, `${base}.1`);
46
+ try {
47
+ await fs.unlink(p2);
48
+ } catch {
49
+ // ignore
50
+ }
51
+ try {
52
+ await fs.rename(p1, p2);
53
+ } catch {
54
+ // ignore
55
+ }
56
+ try {
57
+ await fs.rename(logPath, p1);
58
+ } catch {
59
+ // ignore
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param {Object} payload
65
+ * @returns {Promise<void>}
66
+ */
67
+ async function appendInstallationRecord(payload) {
68
+ const lines = await buildInstallationRecordLines(payload);
69
+ const body = lines.join('\n');
70
+ const logDir = paths.getAifabrixSystemDir();
71
+ const logPath = path.join(logDir, INSTALLATION_LOG);
72
+
73
+ try {
74
+ await fs.mkdir(logDir, { recursive: true });
75
+ await rotateInstallationLogIfNeeded(logPath);
76
+ await fs.appendFile(logPath, body, 'utf8');
77
+ } catch (err) {
78
+ logger.warn(`installation.log: could not append (${err && err.message ? err.message : err})`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @returns {Promise<string|undefined>}
84
+ */
85
+ async function resolveControllerUrlForLog() {
86
+ try {
87
+ const { resolveControllerUrl } = require('./controller-url');
88
+ const u = await resolveControllerUrl();
89
+ return sanitizeUrl(u);
90
+ } catch {
91
+ return undefined;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @returns {Promise<'set'|'unset'>}
97
+ */
98
+ async function resolveAdminEmailPresence() {
99
+ try {
100
+ const e = await config.getAdminEmail();
101
+ return e && String(e).trim() ? 'set' : 'unset';
102
+ } catch {
103
+ return 'unset';
104
+ }
105
+ }
106
+
107
+ const core = require('./installation-log-core');
108
+
109
+ module.exports = {
110
+ appendInstallationRecord,
111
+ createOperationId: core.createOperationId,
112
+ stripDockerDigest: core.stripDockerDigest,
113
+ collectPlatformAppImages: core.collectPlatformAppImages,
114
+ derivePlatformVersion: core.derivePlatformVersion,
115
+ resolveControllerUrlForLog,
116
+ resolveAdminEmailPresence,
117
+ buildInfraSectionLines: core.buildInfraSectionLines,
118
+ resolveLogMode: core.resolveLogMode,
119
+ getCliVersion: core.getCliVersion,
120
+ rotateInstallationLogIfNeeded,
121
+ INSTALLATION_LOG,
122
+ MAX_LOG_BYTES
123
+ };