@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
@@ -1,4 +1,4 @@
1
- const { formatSuccessLine } = require('./cli-test-layout-chalk');
1
+ const { formatSuccessLine, formatProgress } = require('./cli-test-layout-chalk');
2
2
  /**
3
3
  * AI Fabrix Builder - App Run Container Helpers
4
4
  *
@@ -118,7 +118,7 @@ async function checkContainerRunning(appName, developerId, debug = false, scopeO
118
118
  async function stopAndRemoveContainer(appName, developerId, debug = false, scopeOpts = null) {
119
119
  try {
120
120
  const containerName = getContainerName(appName, developerId, scopeOpts);
121
- logger.log(chalk.yellow(`Stopping existing container ${containerName}...`));
121
+ logger.log(formatProgress(`Stopping container ${containerName}…`));
122
122
  const stopCmd = `docker stop ${containerName}`;
123
123
  if (debug) {
124
124
  logger.log(chalk.gray(`[DEBUG] Executing: ${stopCmd}`));
@@ -0,0 +1,206 @@
1
+ /**
2
+ * User `applications` entries in config.yaml (read by `aifabrix resolve`, written by `aifabrix run` dev).
3
+ *
4
+ * @fileoverview Per-app reload and `proxy` (Traefik/public URL hints) in ~/.aifabrix/config.yaml
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
+ * Persist `applications.<appKey>.reload` from the last `aifabrix run` (dev only; CLI is source of truth).
15
+ *
16
+ * @async
17
+ * @param {string} appKey - Application key
18
+ * @param {boolean} reload - Same as passing `--reload` on run
19
+ * @returns {Promise<void>}
20
+ */
21
+ async function persistApplicationReloadFlag(appKey, reload) {
22
+ if (!appKey || typeof appKey !== 'string') {
23
+ return;
24
+ }
25
+ const cfg = await config.getConfig();
26
+ if (!cfg.applications || typeof cfg.applications !== 'object') {
27
+ cfg.applications = {};
28
+ }
29
+ const prev = cfg.applications[appKey];
30
+ const nextEntry =
31
+ prev && typeof prev === 'object' && !Array.isArray(prev) ? { ...prev, reload: Boolean(reload) } : { reload: Boolean(reload) };
32
+ cfg.applications[appKey] = nextEntry;
33
+ await config.saveConfig(cfg);
34
+ }
35
+
36
+ /**
37
+ * True when `applications.<appKey>.reload` is explicitly **true** (bind-mount + hot reload for `aifabrix run`).
38
+ * Does **not** control other compose overrides (e.g. Python image `command` when the image lacks `uvicorn` on PATH).
39
+ *
40
+ * @param {Object|null|undefined} userConfig - Parsed config.yaml root
41
+ * @param {string} appKey - Application key (folder / app.key)
42
+ * @returns {boolean} True when `applications.<appKey>.reload` is explicitly true
43
+ */
44
+ function isApplicationsReloadDefaultOn(userConfig, appKey) {
45
+ if (!userConfig || !appKey || typeof appKey !== 'string') {
46
+ return false;
47
+ }
48
+ const apps = userConfig.applications;
49
+ if (!apps || typeof apps !== 'object') {
50
+ return false;
51
+ }
52
+ const entry = apps[appKey];
53
+ if (!entry || typeof entry !== 'object') {
54
+ return false;
55
+ }
56
+ return entry.reload === true;
57
+ }
58
+
59
+ /**
60
+ * True when `remote-server` is a non-empty string (after trim).
61
+ *
62
+ * @param {string|null|undefined} remoteServer
63
+ * @returns {boolean}
64
+ */
65
+ function isRemoteServerConfigured(remoteServer) {
66
+ return Boolean(remoteServer && String(remoteServer).trim());
67
+ }
68
+
69
+ /**
70
+ * Whether `build.envOutputPath` should be regenerated with the **local** profile (`generateEnvContent(..., 'local')`).
71
+ * **False** only when **both** `remote-server` is set and `applications.<appKey>.reload` is true — then the host file
72
+ * uses the same **docker**-flavor expansion as `builder/<app>/.env` (copy path). All other cases use **local**.
73
+ *
74
+ * @param {Object|null|undefined} userConfig - Parsed config.yaml root
75
+ * @param {string} appKey - Application key
76
+ * @param {string|null|undefined} remoteServer - `remote-server` value (or null)
77
+ * @returns {boolean}
78
+ */
79
+ function isPreferLocalEnvOutputPath(userConfig, appKey, remoteServer) {
80
+ if (!isRemoteServerConfigured(remoteServer)) {
81
+ return true;
82
+ }
83
+ return !isApplicationsReloadDefaultOn(userConfig, appKey);
84
+ }
85
+
86
+ /**
87
+ * For `aifabrix resolve`: reads `remote-server` and returns {@link isPreferLocalEnvOutputPath}.
88
+ *
89
+ * @async
90
+ * @param {Object|null|undefined} userConfig
91
+ * @param {string} appKey
92
+ * @returns {Promise<boolean>}
93
+ */
94
+ async function resolvePreferLocalEnvOutputPathFlag(userConfig, appKey) {
95
+ let remoteServer = null;
96
+ try {
97
+ const rs = await config.getRemoteServer();
98
+ if (rs && String(rs).trim()) {
99
+ remoteServer = String(rs).trim();
100
+ }
101
+ } catch {
102
+ remoteServer = null;
103
+ }
104
+ return isPreferLocalEnvOutputPath(userConfig, appKey, remoteServer);
105
+ }
106
+
107
+ /**
108
+ * Normalize YAML boolean-ish values.
109
+ * @param {unknown} v
110
+ * @returns {boolean|undefined} undefined if absent / not coercible
111
+ */
112
+ function coerceTriStateBool(v) {
113
+ if (v === true || v === 'true' || v === 'yes' || v === 'on' || v === 1 || v === '1') {
114
+ return true;
115
+ }
116
+ if (v === false || v === 'false' || v === 'no' || v === 'off' || v === 0 || v === '0') {
117
+ return false;
118
+ }
119
+ return undefined;
120
+ }
121
+
122
+ /**
123
+ * Whether `aifabrix run` / resolve should use Traefik-style public URL hints for this app when infra allows.
124
+ * Default **false** when unset. Legacy `noProxy: true` ⇒ false; legacy `noProxy: false` with no `proxy` ⇒ true.
125
+ *
126
+ * @param {Object|null|undefined} userConfig - Parsed config.yaml root
127
+ * @param {string} appKey - Application key
128
+ * @returns {boolean}
129
+ */
130
+ function getApplicationsRunProxyHint(userConfig, appKey) {
131
+ if (!userConfig || !appKey || typeof appKey !== 'string') {
132
+ return false;
133
+ }
134
+ const apps = userConfig.applications;
135
+ if (!apps || typeof apps !== 'object') {
136
+ return false;
137
+ }
138
+ const entry = apps[appKey];
139
+ if (!entry || typeof entry !== 'object') {
140
+ return false;
141
+ }
142
+ const proxyCoerced = coerceTriStateBool(entry.proxy);
143
+ if (proxyCoerced !== undefined) {
144
+ return proxyCoerced;
145
+ }
146
+ const legacyNo = coerceTriStateBool(entry.noProxy);
147
+ if (legacyNo === true) {
148
+ return false;
149
+ }
150
+ if (legacyNo === false) {
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+
156
+ /**
157
+ * Persist `applications.<appKey>.proxy` from each `aifabrix run` (same pattern as `reload` for dev-only reload flag).
158
+ * `proxy: true` means use Traefik/front-door URL hints when infra + that app's `application.yaml` allow; `false` means docker `url://` **public** bases for **that app** use localhost + published port. During `resolve`/`run`, each `url://` token uses the **target** app's `applications.<targetKey>.proxy`, not only the app whose `env.template` is being expanded.
159
+ * Removes legacy `noProxy` on the same entry when present.
160
+ *
161
+ * @async
162
+ * @param {string} appKey - Application key
163
+ * @param {boolean} proxyEnabled - Same as a normal `aifabrix run` with proxy hints on (`!isRunCliNoProxy(options)`)
164
+ * @returns {Promise<void>}
165
+ */
166
+ async function persistApplicationRunProxyFlag(appKey, proxyEnabled) {
167
+ if (!appKey || typeof appKey !== 'string') {
168
+ return;
169
+ }
170
+ const cfg = await config.getConfig();
171
+ if (!cfg.applications || typeof cfg.applications !== 'object') {
172
+ cfg.applications = {};
173
+ }
174
+ const prev = cfg.applications[appKey];
175
+ const nextEntry =
176
+ prev && typeof prev === 'object' && !Array.isArray(prev)
177
+ ? { ...prev, proxy: Boolean(proxyEnabled) }
178
+ : { proxy: Boolean(proxyEnabled) };
179
+ if (Object.prototype.hasOwnProperty.call(nextEntry, 'noProxy')) {
180
+ delete nextEntry.noProxy;
181
+ }
182
+ cfg.applications[appKey] = nextEntry;
183
+ await config.saveConfig(cfg);
184
+ }
185
+
186
+ /**
187
+ * Whether declarative `url://*` expansion should treat Traefik as on (infra Traefik + per-app `proxy: true`).
188
+ *
189
+ * @param {Object|null|undefined} userConfig
190
+ * @param {string} appKey
191
+ * @returns {boolean}
192
+ */
193
+ function isDeclarativeTraefikUrlsEnabled(userConfig, appKey) {
194
+ return Boolean(userConfig && userConfig.traefik === true) && getApplicationsRunProxyHint(userConfig, appKey);
195
+ }
196
+
197
+ module.exports = {
198
+ getApplicationsRunProxyHint,
199
+ isApplicationsReloadDefaultOn,
200
+ isDeclarativeTraefikUrlsEnabled,
201
+ isPreferLocalEnvOutputPath,
202
+ isRemoteServerConfigured,
203
+ persistApplicationRunProxyFlag,
204
+ persistApplicationReloadFlag,
205
+ resolvePreferLocalEnvOutputPathFlag
206
+ };
@@ -8,7 +8,7 @@
8
8
  * @version 2.0.0
9
9
  */
10
10
 
11
- const { getControllerUrlFromLoggedInUser } = require('./controller-url');
11
+ const { hasStoredDeviceTokenForController } = require('./controller-url');
12
12
 
13
13
  /**
14
14
  * Validate controller URL format
@@ -72,17 +72,7 @@ async function checkUserLoggedIn(controllerUrl) {
72
72
  if (!controllerUrl) {
73
73
  return false;
74
74
  }
75
-
76
- const normalizedUrl = controllerUrl.trim().replace(/\/+$/, '');
77
- const loggedInControllerUrl = await getControllerUrlFromLoggedInUser();
78
-
79
- if (!loggedInControllerUrl) {
80
- return false;
81
- }
82
-
83
- // Normalize both URLs for comparison
84
- const normalizedLoggedIn = loggedInControllerUrl.trim().replace(/\/+$/, '');
85
- return normalizedLoggedIn === normalizedUrl;
75
+ return hasStoredDeviceTokenForController(controllerUrl);
86
76
  }
87
77
 
88
78
  module.exports = {
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Map secrets keys `BASH_<NAME>` to process-style env `{ [NAME]: value }` for child_process env.
3
+ * Same naming rule as kv://BASH_* resolution (see secrets-bash-kv.js).
4
+ *
5
+ * @fileoverview BASH-prefixed secret → exported-style env for Docker/subprocess
6
+ * @author AI Fabrix Team
7
+ * @version 1.0.0
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const secretsLoad = require('../core/secrets-load');
13
+
14
+ /**
15
+ * @param {string} suffix - Part after BASH_
16
+ * @returns {boolean}
17
+ */
18
+ function isValidExportedName(suffix) {
19
+ return Boolean(suffix && /^[A-Za-z_][A-Za-z0-9_]*$/.test(suffix));
20
+ }
21
+
22
+ /**
23
+ * Collect `BASH_*` entries from a flat or shallow secrets object.
24
+ *
25
+ * @param {Record<string, unknown>|null|undefined} secrets - Merged secrets map (decrypted)
26
+ * @returns {Record<string, string>} e.g. { NPM_TOKEN: '...' } from BASH_NPM_TOKEN
27
+ */
28
+ function collectBashPrefixedEnv(secrets) {
29
+ const out = {};
30
+ if (!secrets || typeof secrets !== 'object') return out;
31
+ for (const [k, v] of Object.entries(secrets)) {
32
+ if (typeof k !== 'string' || !k.startsWith('BASH_')) continue;
33
+ if (v === undefined || v === null) continue;
34
+ const str = typeof v === 'string' ? v.trim() : String(v).trim();
35
+ if (!str) continue;
36
+ const suffix = k.slice(5);
37
+ if (!isValidExportedName(suffix)) continue;
38
+ out[suffix] = str;
39
+ }
40
+ return out;
41
+ }
42
+
43
+ /**
44
+ * Overlay for `child_process` `env`: values from primary user `secrets.local.yaml` plus `aifabrix-secrets`
45
+ * for every `BASH_*` key (merged via {@link secretsLoad.loadSecrets}).
46
+ *
47
+ * @param {string|null} [secretsPath] - Optional explicit secrets file (same as resolve)
48
+ * @param {string|null} [appName] - Optional app name for loadSecrets second arg
49
+ * @returns {Promise<Record<string, string>>}
50
+ */
51
+ async function getBashPrefixedProcessEnvOverlay(secretsPath = null, appName = null) {
52
+ const secrets = await secretsLoad.loadSecrets(secretsPath, appName);
53
+ return collectBashPrefixedEnv(secrets);
54
+ }
55
+
56
+ module.exports = {
57
+ collectBashPrefixedEnv,
58
+ getBashPrefixedProcessEnvOverlay
59
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @fileoverview Missing-secrets CLI error lines (layout colors via cli-layout-chalk).
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const { metadata, sectionTitle } = require('./cli-layout-chalk');
11
+
12
+ /**
13
+ * @param {string[]} messages - Output lines
14
+ * @param {RegExpMatchArray|null} missingSecretsMatch - Missing secrets capture
15
+ */
16
+ function pushMissingSecretsBodyLines(messages, missingSecretsMatch) {
17
+ if (missingSecretsMatch) {
18
+ const parts = missingSecretsMatch[1]
19
+ .trim()
20
+ .split(',')
21
+ .map((s) => s.trim())
22
+ .filter(Boolean);
23
+ if (parts.length > 1) {
24
+ messages.push(` ${sectionTitle('Missing secrets:')}`);
25
+ for (const ref of parts) {
26
+ messages.push(` ${chalk.cyan('-')} ${chalk.white(ref)}`);
27
+ }
28
+ return;
29
+ }
30
+ if (parts.length === 1) {
31
+ messages.push(` ${sectionTitle('Missing secrets:')} ${chalk.white(parts[0])}`);
32
+ return;
33
+ }
34
+ messages.push(` ${sectionTitle('Missing secrets:')}`);
35
+ return;
36
+ }
37
+ messages.push(` ${chalk.white('Missing secrets in secrets file.')}`);
38
+ }
39
+
40
+ /**
41
+ * @param {string[]} messages - Output lines
42
+ * @param {RegExpMatchArray|null} fileInfoMatch - File location capture
43
+ * @param {RegExpMatchArray|null} resolveMatch - Resolve command capture
44
+ */
45
+ function pushSecretsResolutionHintLines(messages, fileInfoMatch, resolveMatch) {
46
+ if (fileInfoMatch) {
47
+ messages.push(` ${metadata('Secrets file location:')} ${chalk.white(fileInfoMatch[1])}`);
48
+ }
49
+ if (resolveMatch) {
50
+ messages.push(
51
+ ` ${metadata('Run:')} ${chalk.yellow(`aifabrix resolve ${resolveMatch[1]} to generate missing secrets.`)}`
52
+ );
53
+ return;
54
+ }
55
+ messages.push(` ${metadata('Run:')} ${chalk.yellow('aifabrix resolve <app-name> to generate missing secrets.')}`);
56
+ }
57
+
58
+ /**
59
+ * Format secrets-related errors
60
+ * @param {string} errorMsg - Error message
61
+ * @returns {string[]|null} Array of error message lines or null if not a secrets error
62
+ */
63
+ function formatSecretsError(errorMsg) {
64
+ if (!errorMsg.includes('Missing secrets')) {
65
+ return null;
66
+ }
67
+
68
+ const messages = [];
69
+ pushMissingSecretsBodyLines(messages, errorMsg.match(/Missing secrets: ([^\n]+)/));
70
+ pushSecretsResolutionHintLines(
71
+ messages,
72
+ errorMsg.match(/Secrets file location: ([^\n]+)/),
73
+ errorMsg.match(/Run "aifabrix resolve ([^"]+)"/)
74
+ );
75
+ return messages;
76
+ }
77
+
78
+ module.exports = { formatSecretsError };
@@ -7,6 +7,18 @@
7
7
 
8
8
  const chalk = require('chalk');
9
9
 
10
+ /**
11
+ * Invoke chalk[method](text) when the mock/real chalk exposes that method; otherwise return text.
12
+ * Keeps layout helpers working in Jest suites that only stub a subset of chalk.
13
+ * @param {string} method - chalk method name (e.g. 'white', 'bold', 'gray')
14
+ * @param {string} text
15
+ * @returns {string}
16
+ */
17
+ function chalkStyle(method, text) {
18
+ const fn = chalk[method];
19
+ return typeof fn === 'function' ? fn(text) : text;
20
+ }
21
+
10
22
  /** Canonical success glyph (green), layout §CORE / §3. */
11
23
  function successGlyph() {
12
24
  return chalk.green('✔');
@@ -35,6 +47,15 @@ function formatSuccessParagraph(message) {
35
47
  return chalk.green(`\n✔ ${message}`);
36
48
  }
37
49
 
50
+ /**
51
+ * Non-blocking warning (layout §CORE): yellow ⚠ + white detail.
52
+ * @param {string} message - text after the glyph (do not prefix with ⚠)
53
+ * @returns {string}
54
+ */
55
+ function formatWarningLine(message) {
56
+ return `${chalkStyle('yellow', '⚠')} ${chalkStyle('white', message)}`;
57
+ }
58
+
38
59
  /**
39
60
  * Blocking error line: red ✖ + red message (layout §18).
40
61
  * @param {string} message
@@ -65,7 +86,7 @@ function formatIssue(title, hint) {
65
86
  */
66
87
  function formatNextActions(lines) {
67
88
  const body = (lines || [])
68
- .map(line => `${chalk.cyan('-')} ${chalk.white(line)}`)
89
+ .map(line => `${chalkStyle('cyan', '-')} ${chalkStyle('white', line)}`)
69
90
  .join('\n');
70
91
  return `${sectionTitle('Next actions:')}\n${body}`;
71
92
  }
@@ -87,7 +108,7 @@ function formatDocsLine(label, url) {
87
108
  * @returns {string}
88
109
  */
89
110
  function formatProgress(message) {
90
- return `${chalk.yellow('⏳')} ${chalk.white(message)}`;
111
+ return `${chalkStyle('yellow', '⏳')} ${chalkStyle('white', message)}`;
91
112
  }
92
113
 
93
114
  /**
@@ -99,7 +120,7 @@ function formatProgress(message) {
99
120
  */
100
121
  function formatBulletSection(title, items, opts) {
101
122
  const bulletColor = opts && opts.bullet === 'red' ? chalk.red : chalk.cyan;
102
- const body = (items || []).map(line => `${bulletColor('-')} ${chalk.white(line)}`).join('\n');
123
+ const body = (items || []).map(line => `${bulletColor('-')} ${chalkStyle('white', line)}`).join('\n');
103
124
  return `${sectionTitle(title)}\n${body}`;
104
125
  }
105
126
 
@@ -109,7 +130,7 @@ function formatBulletSection(title, items, opts) {
109
130
  * @returns {string}
110
131
  */
111
132
  function sectionTitle(text) {
112
- return chalk.white.bold(text);
133
+ return chalkStyle('bold', chalkStyle('white', text));
113
134
  }
114
135
 
115
136
  /**
@@ -119,7 +140,7 @@ function sectionTitle(text) {
119
140
  * @returns {string}
120
141
  */
121
142
  function headerKeyValue(label, value) {
122
- return `${chalk.gray(label)} ${chalk.white.bold(value)}`;
143
+ return `${chalkStyle('gray', label)} ${chalkStyle('bold', chalkStyle('white', value))}`;
123
144
  }
124
145
 
125
146
  /**
@@ -201,7 +222,7 @@ function formatDatasourceListRow(rowStatus, name, statusHint) {
201
222
  ? chalk.red('✖')
202
223
  : chalk.gray('⏭');
203
224
  const hint = statusHint ? ` ${chalk.gray(`(${statusHint})`)}` : '';
204
- return ` ${sym} ${chalk.white(name)}${hint}`;
225
+ return ` ${sym} ${chalkStyle('white', name)}${hint}`;
205
226
  }
206
227
 
207
228
  /**
@@ -232,13 +253,13 @@ function colorRollupPrefixedLine(line) {
232
253
  const trimmed = line.trimStart();
233
254
  const first = trimmed[0];
234
255
  if (first === '✔') {
235
- return chalk.green('✔') + chalk.white(trimmed.slice(1));
256
+ return chalk.green('✔') + chalkStyle('white', trimmed.slice(1));
236
257
  }
237
258
  if (first === '⚠') {
238
- return chalk.yellow('⚠') + chalk.white(trimmed.slice(1));
259
+ return chalk.yellow('⚠') + chalkStyle('white', trimmed.slice(1));
239
260
  }
240
261
  if (first === '✖') {
241
- return chalk.red('✖') + chalk.white(trimmed.slice(1));
262
+ return chalk.red('✖') + chalkStyle('white', trimmed.slice(1));
242
263
  }
243
264
  return line;
244
265
  }
@@ -258,6 +279,7 @@ module.exports = {
258
279
  failureGlyph,
259
280
  formatSuccessLine,
260
281
  formatSuccessParagraph,
282
+ formatWarningLine,
261
283
  formatBlockingError,
262
284
  formatIssue,
263
285
  formatNextActions,
@@ -11,6 +11,8 @@
11
11
  const path = require('path');
12
12
  const chalk = require('chalk');
13
13
  const logger = require('./logger');
14
+ const { formatBlockingError, infoLine } = require('./cli-layout-chalk');
15
+ const { formatSecretsError } = require('./cli-secrets-error-format');
14
16
  const { getDockerDaemonStartHintSentence, getDockerApiOverTcpHintLines } = require('./docker-not-running-hint');
15
17
 
16
18
  /**
@@ -231,40 +233,6 @@ function formatAzureError(errorMsg) {
231
233
  return null;
232
234
  }
233
235
 
234
- /**
235
- * Format secrets-related errors
236
- * @param {string} errorMsg - Error message
237
- * @returns {string[]|null} Array of error message lines or null if not a secrets error
238
- */
239
- function formatSecretsError(errorMsg) {
240
- if (!errorMsg.includes('Missing secrets')) {
241
- return null;
242
- }
243
-
244
- const messages = [];
245
- const missingSecretsMatch = errorMsg.match(/Missing secrets: ([^\n]+)/);
246
- const fileInfoMatch = errorMsg.match(/Secrets file location: ([^\n]+)/);
247
- const resolveMatch = errorMsg.match(/Run "aifabrix resolve ([^"]+)"/);
248
-
249
- if (missingSecretsMatch) {
250
- messages.push(` Missing secrets: ${missingSecretsMatch[1]}`);
251
- } else {
252
- messages.push(' Missing secrets in secrets file.');
253
- }
254
-
255
- if (fileInfoMatch) {
256
- messages.push(` Secrets file location: ${fileInfoMatch[1]}`);
257
- }
258
-
259
- if (resolveMatch) {
260
- messages.push(` Run: aifabrix resolve ${resolveMatch[1]} to generate missing secrets.`);
261
- } else {
262
- messages.push(' Run: aifabrix resolve <app-name> to generate missing secrets.');
263
- }
264
-
265
- return messages;
266
- }
267
-
268
236
  /**
269
237
  * Format deployment-related errors
270
238
  * @param {string} errorMsg - Error message
@@ -388,9 +356,9 @@ function formatError(error) {
388
356
  * @param {string[]} errorMessages - Error message lines
389
357
  */
390
358
  function logError(command, errorMessages) {
391
- logger.error(`\nError in ${command} command:`);
359
+ logger.error(`\n${formatBlockingError(`Error in ${command} command:`)}`);
392
360
  errorMessages.forEach(msg => logger.error(msg));
393
- logger.error('\n💡 Run "aifabrix doctor" for environment diagnostics.\n');
361
+ logger.log(`\n${infoLine(' Run "aifabrix doctor" for environment diagnostics.')}\n`);
394
362
  }
395
363
 
396
364
  /**