@aifabrix/builder 2.44.6 → 2.45.5

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 (257) hide show
  1. package/.cursor/rules/cli-layout.mdc +7 -3
  2. package/.cursor/rules/docs-rules.mdc +3 -1
  3. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
  4. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
  5. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
  6. package/jest.projects.js +100 -1
  7. package/lib/api/agent-metadata-validation.api.js +93 -0
  8. package/lib/api/audit-api-helpers.js +68 -0
  9. package/lib/api/audit.api.js +119 -0
  10. package/lib/api/auth-cache.api.js +44 -0
  11. package/lib/api/certificates.api.js +4 -4
  12. package/lib/api/credential.api.js +2 -2
  13. package/lib/api/dataplane-health.api.js +137 -0
  14. package/lib/api/dataplane-sync.api.js +27 -0
  15. package/lib/api/datasources-core.api.js +21 -18
  16. package/lib/api/datasources-extended.api.js +22 -22
  17. package/lib/api/dimensions.api.js +3 -0
  18. package/lib/api/external-systems.api.js +13 -13
  19. package/lib/api/governance-scenarios.api.js +39 -0
  20. package/lib/api/groups.api.js +79 -0
  21. package/lib/api/index.js +45 -2
  22. package/lib/api/pipeline.api.js +4 -4
  23. package/lib/api/protection.api.js +157 -0
  24. package/lib/api/records-bulk.api.js +30 -0
  25. package/lib/api/records-search-parse.js +49 -0
  26. package/lib/api/records-search.api.js +29 -0
  27. package/lib/api/types/agent-metadata-validation.types.js +67 -0
  28. package/lib/api/types/audit.types.js +72 -0
  29. package/lib/api/types/dataplane-health.types.js +27 -0
  30. package/lib/api/types/dimensions.types.js +2 -0
  31. package/lib/api/types/governance-scenarios.types.js +39 -0
  32. package/lib/api/types/protection.types.js +69 -0
  33. package/lib/api/types/records-bulk.types.js +35 -0
  34. package/lib/api/types/records-search.types.js +28 -0
  35. package/lib/api/user-groups.api.js +50 -0
  36. package/lib/api/users.api.js +95 -0
  37. package/lib/api/validation-run.api.js +3 -3
  38. package/lib/api/validation-runner.js +3 -3
  39. package/lib/api/wizard-platform.api.js +3 -3
  40. package/lib/api/wizard.api.js +21 -21
  41. package/lib/app/deploy.js +34 -19
  42. package/lib/app/helpers.js +3 -3
  43. package/lib/app/index.js +3 -3
  44. package/lib/app/register.js +7 -6
  45. package/lib/app/restart-display.js +52 -21
  46. package/lib/app/rotate-secret.js +7 -6
  47. package/lib/app/run-helpers.js +15 -8
  48. package/lib/app/run.js +57 -9
  49. package/lib/app/show-display.js +7 -0
  50. package/lib/app/show.js +87 -5
  51. package/lib/build/index.js +9 -5
  52. package/lib/cli/index.js +4 -2
  53. package/lib/cli/infra-guided.js +62 -28
  54. package/lib/cli/installation-log-command.js +73 -0
  55. package/lib/cli/setup-app.help.js +50 -2
  56. package/lib/cli/setup-app.js +44 -25
  57. package/lib/cli/setup-app.test-commands.js +80 -21
  58. package/lib/cli/setup-auth.js +124 -52
  59. package/lib/cli/setup-external-system.js +37 -3
  60. package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
  61. package/lib/cli/setup-infra-up-platform-action.js +131 -0
  62. package/lib/cli/setup-infra.js +60 -119
  63. package/lib/cli/setup-platform.js +1 -1
  64. package/lib/cli/setup-utility-help-after.js +45 -0
  65. package/lib/cli/setup-utility-resolve.js +132 -0
  66. package/lib/cli/setup-utility.js +148 -116
  67. package/lib/commands/app-logs.js +81 -33
  68. package/lib/commands/auth-config.js +116 -18
  69. package/lib/commands/auth-status-dataplane-version.js +231 -0
  70. package/lib/commands/auth-status-display.js +94 -0
  71. package/lib/commands/auth-status.js +70 -6
  72. package/lib/commands/credential-push.js +10 -5
  73. package/lib/commands/datasource-load-export-cli.js +178 -0
  74. package/lib/commands/datasource-test-trust-cli.js +114 -0
  75. package/lib/commands/datasource-unified-test-cli.js +25 -17
  76. package/lib/commands/datasource-unified-test-cli.options.js +18 -3
  77. package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
  78. package/lib/commands/datasource-verify-audit-cli.js +96 -0
  79. package/lib/commands/datasource.js +68 -1
  80. package/lib/commands/dimension-value.js +43 -13
  81. package/lib/commands/dimension.js +38 -7
  82. package/lib/commands/identity-apply-sync.js +131 -0
  83. package/lib/commands/identity-group-cli.js +146 -0
  84. package/lib/commands/identity-membership-role-cache.js +175 -0
  85. package/lib/commands/identity-shared.js +94 -0
  86. package/lib/commands/identity-user-cli.js +158 -0
  87. package/lib/commands/identity.js +43 -0
  88. package/lib/commands/login.js +46 -16
  89. package/lib/commands/protection-cli-leaves.js +286 -0
  90. package/lib/commands/protection.js +50 -0
  91. package/lib/commands/repair.js +13 -3
  92. package/lib/commands/setup-modes.js +19 -6
  93. package/lib/commands/setup-prompts.js +41 -8
  94. package/lib/commands/setup.js +114 -9
  95. package/lib/commands/teardown.js +54 -5
  96. package/lib/commands/test-e2e-external.js +40 -9
  97. package/lib/commands/test-governance-cli-options.js +47 -0
  98. package/lib/commands/test-governance-command-action.js +55 -0
  99. package/lib/commands/test-governance-external.js +88 -0
  100. package/lib/commands/test-trust-cli-options.js +32 -0
  101. package/lib/commands/test-trust-command-action.js +118 -0
  102. package/lib/commands/test-trust-external.js +127 -0
  103. package/lib/commands/up-common.js +48 -14
  104. package/lib/commands/up-dataplane.js +21 -18
  105. package/lib/commands/up-miso.js +12 -8
  106. package/lib/commands/upload.js +38 -15
  107. package/lib/commands/wizard-core.js +3 -0
  108. package/lib/core/audit-logger.js +1 -34
  109. package/lib/core/config-admin-email.js +56 -0
  110. package/lib/core/config-device-dataplane.js +185 -0
  111. package/lib/core/config-normalize.js +60 -0
  112. package/lib/core/config-registered-controller-urls.js +54 -0
  113. package/lib/core/config.js +33 -50
  114. package/lib/core/secrets-ensure-infra.js +1 -1
  115. package/lib/core/secrets-env-content.js +86 -90
  116. package/lib/core/secrets-env-declarative-expand.js +170 -0
  117. package/lib/core/secrets-env-write.js +2 -0
  118. package/lib/core/secrets-load.js +106 -102
  119. package/lib/datasource/agent-trust-debug-log.js +45 -0
  120. package/lib/datasource/agent-trust-fetch.js +115 -0
  121. package/lib/datasource/agent-trust-map.js +101 -0
  122. package/lib/datasource/agent-trust-mock.js +44 -0
  123. package/lib/datasource/agent-trust-run.js +93 -0
  124. package/lib/datasource/agent-trust-timeout.js +25 -0
  125. package/lib/datasource/audit-evidence-display.js +80 -0
  126. package/lib/datasource/audit-evidence-extract-debug.js +103 -0
  127. package/lib/datasource/audit-evidence-extract.js +177 -0
  128. package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
  129. package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
  130. package/lib/datasource/audit-evidence-poll.js +109 -0
  131. package/lib/datasource/audit-evidence-run.js +177 -0
  132. package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
  133. package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
  134. package/lib/datasource/audit-evidence-verify.js +108 -0
  135. package/lib/datasource/bulk-loader-run-batches.js +88 -0
  136. package/lib/datasource/bulk-loader-service-retry.js +52 -0
  137. package/lib/datasource/bulk-loader-service.js +127 -0
  138. package/lib/datasource/capability/run-capability-dimension.js +2 -1
  139. package/lib/datasource/datasource-exporter-resolve.js +82 -0
  140. package/lib/datasource/datasource-exporter-service.js +144 -0
  141. package/lib/datasource/datasource-identifier.js +148 -0
  142. package/lib/datasource/datasource-load-export-context.js +92 -0
  143. package/lib/datasource/integration-context.js +6 -4
  144. package/lib/datasource/local-data-paths.js +145 -0
  145. package/lib/datasource/log-cleaner.js +241 -0
  146. package/lib/datasource/log-viewer.js +111 -6
  147. package/lib/datasource/record-file-parser.js +85 -0
  148. package/lib/datasource/record-mapper.js +129 -0
  149. package/lib/datasource/resolve-app.js +74 -55
  150. package/lib/datasource/test-e2e.js +26 -2
  151. package/lib/datasource/unified-validation-run-resolve.js +45 -12
  152. package/lib/datasource/unified-validation-run.js +43 -17
  153. package/lib/datasource/validate.js +37 -20
  154. package/lib/external-system/deploy.js +5 -1
  155. package/lib/external-system/test-system-level.js +25 -4
  156. package/lib/external-system/test.js +3 -1
  157. package/lib/governance/governance-pack-loader.js +70 -0
  158. package/lib/governance/governance-run-display.js +119 -0
  159. package/lib/identity/identity-apply-core.js +108 -0
  160. package/lib/identity/identity-apply-phases.js +244 -0
  161. package/lib/identity/identity-apply-service.js +77 -0
  162. package/lib/identity/identity-csv-parser.js +114 -0
  163. package/lib/internal/fs-real-sync.js +29 -1
  164. package/lib/internal/node-fs.js +2 -0
  165. package/lib/protection/auth-context.js +45 -0
  166. package/lib/protection/convert-batch.js +90 -0
  167. package/lib/protection/load.js +41 -0
  168. package/lib/protection/paths.js +158 -0
  169. package/lib/protection/preflight-datasource-ready.js +46 -0
  170. package/lib/protection/protection-create-probe-lines.js +142 -0
  171. package/lib/protection/protection-create-probes.js +95 -0
  172. package/lib/protection/protection-create-scaffold.js +281 -0
  173. package/lib/protection/protection-display-helpers.js +445 -0
  174. package/lib/protection/protection-display.js +215 -0
  175. package/lib/protection/protection-preset-registry.js +97 -0
  176. package/lib/protection/report-exit.js +62 -0
  177. package/lib/protection/resolve.js +202 -0
  178. package/lib/protection/run-commands-validate.js +130 -0
  179. package/lib/protection/run-commands.js +216 -0
  180. package/lib/protection/run-protection-create-helpers.js +134 -0
  181. package/lib/protection/run-protection-create-probes-flow.js +43 -0
  182. package/lib/protection/run-protection-create.js +191 -0
  183. package/lib/protection/scope.js +22 -0
  184. package/lib/protection/sync-after-upload.js +64 -0
  185. package/lib/protection/upload-batch.js +127 -0
  186. package/lib/protection/validate-batch.js +113 -0
  187. package/lib/protection/validate-local.js +65 -0
  188. package/lib/resolvers/dimension-file.js +29 -4
  189. package/lib/schema/application-schema.json +4 -0
  190. package/lib/schema/external-datasource.schema.json +47 -13
  191. package/lib/schema/flag-map-validation-run.json +2 -2
  192. package/lib/schema/infra.parameter.yaml +10 -0
  193. package/lib/schema/protection.schema.json +374 -0
  194. package/lib/schema/type/credential-field-registry.json +636 -0
  195. package/lib/schema/wizard-config.schema.json +1 -1
  196. package/lib/utils/agent-trust-run-display.js +231 -0
  197. package/lib/utils/agent-trust-run-exit.js +59 -0
  198. package/lib/utils/agent-trust-wait-ui.js +94 -0
  199. package/lib/utils/app-config-resolver.js +24 -1
  200. package/lib/utils/applications-config-defaults.js +206 -0
  201. package/lib/utils/auth-config-validator.js +2 -12
  202. package/lib/utils/bash-secret-env.js +1 -1
  203. package/lib/utils/cli-exit-codes.js +30 -0
  204. package/lib/utils/cli-sync-options.js +33 -0
  205. package/lib/utils/compose-generate-docker-compose.js +111 -6
  206. package/lib/utils/compose-generator.js +17 -8
  207. package/lib/utils/controller-url.js +50 -7
  208. package/lib/utils/credential-push-ui.js +90 -0
  209. package/lib/utils/dataplane-cli-version-gate.js +202 -0
  210. package/lib/utils/dataplane-cli-version-help.js +74 -0
  211. package/lib/utils/datasource-export-display.js +137 -0
  212. package/lib/utils/datasource-load-display.js +174 -0
  213. package/lib/utils/datasource-load-export-ui.js +120 -0
  214. package/lib/utils/datasource-test-run-capability-scope.js +3 -3
  215. package/lib/utils/datasource-test-run-display.js +3 -3
  216. package/lib/utils/datasource-test-run-tty-log.js +5 -1
  217. package/lib/utils/env-copy.js +99 -14
  218. package/lib/utils/env-template.js +5 -1
  219. package/lib/utils/health-check-url.js +18 -15
  220. package/lib/utils/health-check.js +7 -5
  221. package/lib/utils/help-builder.js +30 -24
  222. package/lib/utils/infra-optional-service-flags.js +69 -0
  223. package/lib/utils/installation-log-core.js +282 -0
  224. package/lib/utils/installation-log-record.js +237 -0
  225. package/lib/utils/installation-log.js +123 -0
  226. package/lib/utils/log-redaction.js +105 -0
  227. package/lib/utils/manifest-location.js +164 -0
  228. package/lib/utils/manifest-source-emit.js +184 -0
  229. package/lib/utils/paths.js +238 -89
  230. package/lib/utils/remote-secrets-loader.js +7 -1
  231. package/lib/utils/run-cli-flags.js +29 -0
  232. package/lib/utils/secrets-canonical.js +10 -3
  233. package/lib/utils/secrets-path.js +3 -4
  234. package/lib/utils/secrets-utils.js +20 -10
  235. package/lib/utils/semver-compare.js +127 -0
  236. package/lib/utils/system-builder-root.js +10 -2
  237. package/lib/utils/upload-pipeline-ui.js +91 -0
  238. package/lib/utils/url-declarative-public-base.js +80 -12
  239. package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
  240. package/lib/utils/url-declarative-resolve-build.js +24 -393
  241. package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
  242. package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
  243. package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
  244. package/lib/utils/url-declarative-resolve.js +47 -7
  245. package/lib/utils/url-declarative-runtime-base-path.js +21 -1
  246. package/lib/utils/urls-local-registry-scan.js +103 -0
  247. package/lib/utils/urls-local-registry.js +161 -90
  248. package/lib/utils/validation-poll-ui.js +14 -7
  249. package/lib/validation/wizard-config-validator.js +58 -19
  250. package/package.json +3 -1
  251. package/templates/applications/dataplane/application.yaml +4 -0
  252. package/templates/applications/dataplane/env.template +13 -1
  253. package/templates/applications/dataplane/rbac.yaml +4 -0
  254. package/templates/applications/miso-controller/application.yaml +2 -0
  255. package/templates/applications/miso-controller/env.template +27 -29
  256. package/templates/external-system/README.md.hbs +56 -8
  257. package/.npmrc.token +0 -1
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Canonical Builder CLI exit codes for scriptable commands.
3
+ *
4
+ * Documented in `docs/commands/authentication.md`. Use in
5
+ * `aifabrix auth status --validate` and any future `--validate`-style flag.
6
+ *
7
+ * @fileoverview Exit-code constants for scriptable Builder CLI commands
8
+ * @author AI Fabrix Team
9
+ * @version 2.0.0
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ /** Success. */
15
+ const EXIT_OK = 0;
16
+
17
+ /** Not authenticated / invalid token. */
18
+ const EXIT_NOT_AUTHENTICATED = 1;
19
+
20
+ /**
21
+ * Authenticated but installed Builder CLI version is older than the dataplane
22
+ * minimum advertised by `GET /api/v1/health → minBuilderCliVersion` (plan 403.0).
23
+ */
24
+ const EXIT_CLI_VERSION_INCOMPATIBLE = 3;
25
+
26
+ module.exports = {
27
+ EXIT_OK,
28
+ EXIT_NOT_AUTHENTICATED,
29
+ EXIT_CLI_VERSION_INCOMPATIBLE
30
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Detect skip-sync from Commander options and raw argv (--no-sync → sync: false).
3
+ * @author AI Fabrix Team
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * True when the user passed `--no-sync` or Commander set `sync: false` / `noSync: true`.
11
+ * Do not use for `protection upload --no-sync` (post-upload datasource sync only).
12
+ *
13
+ * @param {Object|null|undefined} options - Commander-parsed options
14
+ * @param {import('commander').Command} [cmd] - Commander command (for rawArgs fallback)
15
+ * @returns {boolean}
16
+ */
17
+ function cliOptsSkipSync(options, cmd) {
18
+ if (!options || typeof options !== 'object') {
19
+ return false;
20
+ }
21
+ if (options.noSync === true) {
22
+ return true;
23
+ }
24
+ if (options.sync === false) {
25
+ return true;
26
+ }
27
+ const raw = cmd && Array.isArray(cmd.rawArgs) ? cmd.rawArgs : [];
28
+ return raw.includes('--no-sync');
29
+ }
30
+
31
+ module.exports = {
32
+ cliOptsSkipSync
33
+ };
@@ -15,6 +15,99 @@ const paths = require('./paths');
15
15
  const { getInfraDirName } = require('../infrastructure/helpers');
16
16
  const { resolveMisoEnvironment } = require('./compose-miso-env');
17
17
 
18
+ /**
19
+ * When running a pulled Python image (no bind-mounted source), the image CMD may invoke the
20
+ * `uvicorn` console script, which is not always on PATH. Derive a `python -m uvicorn` command from
21
+ * `build.reloadStart` so generated compose matches the same app entrypoint as hot-reload runs.
22
+ *
23
+ * @param {string|undefined} reloadStart - Value from application.yaml `build.reloadStart`
24
+ * @returns {string|null} Shell command fragment (no `cd`, no `exec`; image compose prepends `exec ` in JS)
25
+ */
26
+ function derivePythonImageStartFromReload(reloadStart) {
27
+ if (typeof reloadStart !== 'string' || !reloadStart.trim()) {
28
+ return null;
29
+ }
30
+ const trimmed = reloadStart.trim().replace(/\s+--reload\b/g, '').trim();
31
+ if (/^python3?\s+-m\s+uvicorn\s+/i.test(trimmed)) {
32
+ return normalizeComposeShellPortRef(trimmed);
33
+ }
34
+ if (trimmed.startsWith('uvicorn ')) {
35
+ const rest = trimmed.slice('uvicorn '.length);
36
+ return normalizeComposeShellPortRef(`python -m uvicorn ${rest}`);
37
+ }
38
+ return null;
39
+ }
40
+
41
+ /**
42
+ * Compose sets `PORT` in the service environment; replace `${PORT:-...}` with `$$PORT` in the
43
+ * fragment so the compose file avoids ambiguous `:` parsing in flow scalars and Compose expands
44
+ * `$$` to `$` for the container shell (which then expands `PORT` from the service environment).
45
+ *
46
+ * @param {string} cmd - Shell command fragment
47
+ * @returns {string} Same fragment with `${PORT:-...}` replaced by `$$PORT` (Compose escapes `$$` → `$` for the shell)
48
+ */
49
+ function normalizeComposeShellPortRef(cmd) {
50
+ return cmd.replace(/\$\{PORT:-[^}]+\}/g, () => '$$PORT');
51
+ }
52
+
53
+ /**
54
+ * Normalizes `build.reloadStart` for Python when using a bind-mounted source in compose.
55
+ *
56
+ * @param {string} raw - Trimmed reload command
57
+ * @returns {string} Command suitable for `cd /app && …` in compose
58
+ */
59
+ function normalizePythonReloadForComposeMounted(raw) {
60
+ const s = normalizeComposeShellPortRef(raw);
61
+ if (/^python3?\s+-m\s+uvicorn\s+/i.test(s)) {
62
+ return s;
63
+ }
64
+ if (s.startsWith('uvicorn ')) {
65
+ return `python -m uvicorn ${s.slice('uvicorn '.length)}`;
66
+ }
67
+ return s;
68
+ }
69
+
70
+ /**
71
+ * Builds the single `reloadStartCommand` passed to compose templates (`command: …` when set).
72
+ *
73
+ * `applications.<app>.reload` in config.yaml (and `aifabrix run --reload`) only controls **bind-mount +
74
+ * `--reload`** via `devMountPath`. For pulled images without a mount, use optional `build.imageRun` in
75
+ * application.yaml, or (Python only) derive from `build.reloadStart` when `imageRun` is unset.
76
+ *
77
+ * @param {string} language - `application.yaml` build.language
78
+ * @param {string|null} devMountPath - Bind mount path when reload sync is active
79
+ * @param {string|undefined} reloadRaw - `build.reloadStart`
80
+ * @param {Object} [build] - `application.yaml` `build` object (`imageRun`, etc.)
81
+ * @returns {string|null}
82
+ */
83
+ function buildReloadStartCommandForCompose(language, devMountPath, reloadRaw, build = {}) {
84
+ const buildObj = build && typeof build === 'object' ? build : {};
85
+ const imageRunRaw = typeof buildObj.imageRun === 'string' ? buildObj.imageRun.trim() : '';
86
+ const reloadTrimmed = typeof reloadRaw === 'string' ? reloadRaw.trim() : '';
87
+
88
+ if (devMountPath) {
89
+ if (!reloadTrimmed) {
90
+ return null;
91
+ }
92
+ return language === 'python' ? normalizePythonReloadForComposeMounted(reloadTrimmed) : reloadTrimmed;
93
+ }
94
+
95
+ if (imageRunRaw) {
96
+ return normalizeComposeShellPortRef(imageRunRaw);
97
+ }
98
+
99
+ if (!reloadTrimmed) {
100
+ return null;
101
+ }
102
+
103
+ if (language === 'python') {
104
+ const imageCmd = derivePythonImageStartFromReload(reloadTrimmed);
105
+ return imageCmd ? `exec ${imageCmd}` : null;
106
+ }
107
+
108
+ return null;
109
+ }
110
+
18
111
  /**
19
112
  * Resolve infra `pgpass` path: prefer system config dir; when home differs, allow legacy layout.
20
113
  * @param {string|number} devId - Developer id (infra vs infra-dev{n})
@@ -119,14 +212,15 @@ async function loadComposeHead(deps, appName, appConfig, options) {
119
212
  * @param {object} head - From loadComposeHead
120
213
  * @returns {object}
121
214
  */
122
- function buildComposeLayouts(deps, appName, appConfig, head) {
215
+ function buildComposeLayouts(deps, appName, appConfig, head, options) {
123
216
  const { buildNetworkAndContainerNames, buildServiceConfig, buildVolumesConfig, buildNetworksConfig } = deps;
124
217
  const { port, imageOverride, devId, idNum, scoped, remoteServer } = head;
125
218
  const { networkName, containerName } = buildNetworkAndContainerNames(appName, devId, idNum, scoped);
126
219
  const serviceConfig = buildServiceConfig(appName, appConfig, port, devId, {
127
220
  imageOverride,
128
221
  scopeOpts: scoped,
129
- remoteServer
222
+ remoteServer,
223
+ omitAppTraefikLabels: options && options.omitAppTraefikLabels === true
130
224
  });
131
225
  const volumesConfig = buildVolumesConfig(appName);
132
226
  const networksConfig = buildNetworksConfig(appConfig);
@@ -161,8 +255,13 @@ async function resolveComposePathsAndSecrets(ctx) {
161
255
  );
162
256
  const devMountPath = resolveDevMountPath(options);
163
257
  const reloadRaw = appConfig.build?.reloadStart;
164
- const reloadStartCommand =
165
- devMountPath && typeof reloadRaw === 'string' && reloadRaw.trim().length > 0 ? reloadRaw.trim() : null;
258
+ const language = appConfig.build?.language || appConfig.language || 'typescript';
259
+ const reloadStartCommand = buildReloadStartCommandForCompose(
260
+ language,
261
+ devMountPath,
262
+ reloadRaw,
263
+ appConfig.build
264
+ );
166
265
  const infraPgpassPath = resolveInfraPgpassPath(devId, paths, fsSync.existsSync);
167
266
  const useInfraPgpass = serviceCf.requiresDatabase && fsSync.existsSync(infraPgpassPath);
168
267
  return {
@@ -185,7 +284,7 @@ async function resolveComposePathsAndSecrets(ctx) {
185
284
  */
186
285
  async function generateDockerComposeImpl(deps, appName, appConfig, options) {
187
286
  const head = await loadComposeHead(deps, appName, appConfig, options);
188
- const layouts = buildComposeLayouts(deps, appName, appConfig, head);
287
+ const layouts = buildComposeLayouts(deps, appName, appConfig, head, options);
189
288
  const side = await resolveComposePathsAndSecrets({
190
289
  options,
191
290
  appName,
@@ -213,4 +312,10 @@ function createGenerateDockerCompose(deps) {
213
312
  return (appName, appConfig, options) => generateDockerComposeImpl(deps, appName, appConfig, options);
214
313
  }
215
314
 
216
- module.exports = { createGenerateDockerCompose, resolveInfraPgpassPath };
315
+ module.exports = {
316
+ createGenerateDockerCompose,
317
+ resolveInfraPgpassPath,
318
+ derivePythonImageStartFromReload,
319
+ buildReloadStartCommandForCompose,
320
+ normalizePythonReloadForComposeMounted
321
+ };
@@ -282,10 +282,16 @@ function buildRequiresConfig(config) {
282
282
  * @param {string} [runExtras.imageOverride] - Full image reference for run (e.g. from --image)
283
283
  * @param {Object|null} [runExtras.scopeOpts] - Traefik / scoped compose options
284
284
  * @param {string|null|undefined} [runExtras.remoteServer] - For ${REMOTE_HOST} in frontDoorRouting.host
285
+ * @param {boolean} [runExtras.omitAppTraefikLabels] - When true (user config `traefik: false`), omit Traefik labels and BASE_PATH env from compose even if application.yaml enables frontDoorRouting
285
286
  * @returns {Object} Service configuration
286
287
  */
287
288
  function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
288
- const { imageOverride = null, scopeOpts = null, remoteServer = null } = runExtras;
289
+ const {
290
+ imageOverride = null,
291
+ scopeOpts = null,
292
+ remoteServer = null,
293
+ omitAppTraefikLabels = false
294
+ } = runExtras;
289
295
  const containerPortValue = getContainerPort(config, 3000);
290
296
  const hostPort = port;
291
297
  const useTraefikScope =
@@ -305,13 +311,16 @@ function buildServiceConfig(appName, config, port, devId, runExtras = {}) {
305
311
  containerPort: containerPortValue, // Container port (always set, equals containerPort if exists, else port)
306
312
  hostPort: hostPort, // Host port (options.port if provided, else config.port)
307
313
  healthCheck,
308
- traefik: buildTraefikConfig(
309
- config,
310
- devId,
311
- scopeForHealthAndTraefik,
312
- remoteServer,
313
- healthCheck.path
314
- ),
314
+ traefik:
315
+ omitAppTraefikLabels === true
316
+ ? { enabled: false }
317
+ : buildTraefikConfig(
318
+ config,
319
+ devId,
320
+ scopeForHealthAndTraefik,
321
+ remoteServer,
322
+ healthCheck.path
323
+ ),
315
324
  ...buildRequiresConfig(config)
316
325
  };
317
326
  }
@@ -42,8 +42,38 @@ function normalizeUrl(url) {
42
42
  }
43
43
 
44
44
  /**
45
- * Get controller URL from logged-in user's device tokens
46
- * Returns the first available controller URL from device tokens stored in config
45
+ * True when `config.yaml` has a non-expired-looking device entry for this controller URL
46
+ * (normalized host/path comparison).
47
+ *
48
+ * @async
49
+ * @param {string} controllerUrl
50
+ * @returns {Promise<boolean>}
51
+ */
52
+ async function hasStoredDeviceTokenForController(controllerUrl) {
53
+ if (!controllerUrl || typeof controllerUrl !== 'string') {
54
+ return false;
55
+ }
56
+ const want = normalizeUrl(controllerUrl);
57
+ if (!want) {
58
+ return false;
59
+ }
60
+ try {
61
+ const userConfig = await config.getConfig();
62
+ if (!userConfig.device || typeof userConfig.device !== 'object') {
63
+ return false;
64
+ }
65
+ return Object.keys(userConfig.device).some((key) => normalizeUrl(key) === want);
66
+ } catch {
67
+ return false;
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Get controller URL from logged-in user's device tokens.
73
+ * Prefers the entry under {@link config.controller} when it matches a `device` key; otherwise a
74
+ * single `device` entry; with multiple unrelated entries and no matching default, returns **null**
75
+ * (callers must not treat arbitrary key order as the active controller).
76
+ *
47
77
  * @async
48
78
  * @function getControllerUrlFromLoggedInUser
49
79
  * @returns {Promise<string|null>} Controller URL from logged-in user, or null if not found
@@ -60,11 +90,23 @@ async function getControllerUrlFromLoggedInUser() {
60
90
  return null;
61
91
  }
62
92
 
63
- // Return the first available controller URL (normalized)
64
- const firstControllerUrl = deviceUrls[0];
65
- return normalizeUrl(firstControllerUrl);
66
- } catch (error) {
67
- // If config doesn't exist or can't be read, return null
93
+ const cfgController =
94
+ userConfig.controller && typeof userConfig.controller === 'string'
95
+ ? normalizeUrl(userConfig.controller)
96
+ : '';
97
+ if (cfgController) {
98
+ const match = deviceUrls.find((u) => normalizeUrl(u) === cfgController);
99
+ if (match) {
100
+ return normalizeUrl(match);
101
+ }
102
+ }
103
+
104
+ if (deviceUrls.length === 1) {
105
+ return normalizeUrl(deviceUrls[0]);
106
+ }
107
+
108
+ return null;
109
+ } catch {
68
110
  return null;
69
111
  }
70
112
  }
@@ -109,6 +151,7 @@ async function resolveControllerUrl() {
109
151
 
110
152
  module.exports = {
111
153
  getDefaultControllerUrl,
154
+ hasStoredDeviceTokenForController,
112
155
  getControllerUrlFromLoggedInUser,
113
156
  getControllerFromConfig,
114
157
  resolveControllerUrl
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @fileoverview Progress line + optional in-place spinner for credential secret push.
3
+ * @author AI Fabrix Team
4
+ * @version 1.2.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('./logger');
11
+ const { formatProgress } = require('./cli-test-layout-chalk');
12
+
13
+ /**
14
+ * @param {Object} [options]
15
+ * @param {boolean} [options.json] - When true, skip progress line and spinner
16
+ * @returns {boolean}
17
+ */
18
+ function shouldShowCredentialPushProgress(options = {}) {
19
+ return options.json !== true;
20
+ }
21
+
22
+ /**
23
+ * @param {Object} [options]
24
+ * @returns {boolean}
25
+ */
26
+ function shouldUseCredentialPushSpinner(options = {}) {
27
+ if (!shouldShowCredentialPushProgress(options)) {
28
+ return false;
29
+ }
30
+ return Boolean(process && process.stdout && process.stdout.isTTY);
31
+ }
32
+
33
+ /**
34
+ * @param {string} systemKey
35
+ * @returns {string}
36
+ */
37
+ function buildCredentialPushSpinnerLabel(systemKey) {
38
+ const key = systemKey && String(systemKey).trim() ? String(systemKey).trim() : 'integration';
39
+ return `Pushing credential secrets for ${key}…`;
40
+ }
41
+
42
+ /**
43
+ * @param {string} systemKey
44
+ * @returns {string}
45
+ */
46
+ function buildCredentialPushSpinnerText(systemKey) {
47
+ return formatProgress(buildCredentialPushSpinnerLabel(systemKey));
48
+ }
49
+
50
+ /**
51
+ * Run credential push work with visible progress (⏳ line or ora with label on TTY).
52
+ *
53
+ * @async
54
+ * @param {Function} work - Zero-arg async function performing the push
55
+ * @param {Object} [opts]
56
+ * @param {string} [opts.systemKey]
57
+ * @param {boolean} [opts.json]
58
+ * @returns {Promise<*>}
59
+ */
60
+ async function runWithCredentialPushSpinner(work, opts = {}) {
61
+ if (!shouldShowCredentialPushProgress(opts)) {
62
+ return work();
63
+ }
64
+
65
+ if (!shouldUseCredentialPushSpinner(opts)) {
66
+ logger.log(buildCredentialPushSpinnerText(opts.systemKey));
67
+ return work();
68
+ }
69
+
70
+ const ora = require('ora');
71
+ const label = buildCredentialPushSpinnerLabel(opts.systemKey);
72
+ const spinner = ora({
73
+ text: chalk.white(label),
74
+ spinner: 'dots'
75
+ }).start();
76
+
77
+ try {
78
+ return await work();
79
+ } finally {
80
+ spinner.stop();
81
+ }
82
+ }
83
+
84
+ module.exports = {
85
+ shouldShowCredentialPushProgress,
86
+ shouldUseCredentialPushSpinner,
87
+ buildCredentialPushSpinnerLabel,
88
+ buildCredentialPushSpinnerText,
89
+ runWithCredentialPushSpinner
90
+ };
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Builder CLI ↔ dataplane version gate (plan 142.0).
3
+ *
4
+ * Resolves the dataplane-advertised `minBuilderCliVersion` (from cache or a
5
+ * fresh health probe) and compares it against the installed Builder CLI
6
+ * version. Used at the top of dataplane API entrypoints so an outdated CLI
7
+ * cannot mutate dataplane state silently.
8
+ *
9
+ * Local-only commands (validate, run, up-infra, file generation) do **not**
10
+ * call into this module; the gate is intentionally per-call, not global.
11
+ *
12
+ * @fileoverview Compatibility gate for dataplane API entrypoints
13
+ * @author AI Fabrix Team
14
+ * @version 2.0.0
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
20
+ const {
21
+ getDeviceDataplaneVersions,
22
+ updateDeviceDataplaneVersions,
23
+ isCacheStale
24
+ } = require('../core/config-device-dataplane');
25
+ const { compareSemver, isValidSemver } = require('./semver-compare');
26
+ const { formatCliVersionGateError } = require('./dataplane-cli-version-help');
27
+
28
+ /**
29
+ * Get the installed Builder CLI version from `package.json`.
30
+ * Centralized so tests can stub it via Jest module mocks.
31
+ * @returns {string}
32
+ */
33
+ function getInstalledCliVersion() {
34
+ return require('../../package.json').version;
35
+ }
36
+
37
+ /**
38
+ * Internal sentinel error code so callers can distinguish gate failures from
39
+ * generic dataplane errors when needed.
40
+ * @type {string}
41
+ */
42
+ const ERROR_CODE = 'CLI_VERSION_INCOMPATIBLE';
43
+
44
+ /**
45
+ * True when `err` was thrown by this gate.
46
+ * @param {*} err
47
+ * @returns {boolean}
48
+ */
49
+ function isCliVersionIncompatibleError(err) {
50
+ return Boolean(err && err.code === ERROR_CODE);
51
+ }
52
+
53
+ /**
54
+ * Resolve the dataplane minimum Builder CLI version, preferring cached value
55
+ * when fresh; otherwise probe health and persist a new snapshot under
56
+ * `device.<controllerUrl>`.
57
+ *
58
+ * @private
59
+ * @async
60
+ * @param {string} controllerUrl
61
+ * @param {string} dataplaneUrl
62
+ * @param {{ forceRefresh?: boolean }} [options]
63
+ * @returns {Promise<{ minBuilderCliVersion?: string, dataplaneVersion?: string }>}
64
+ */
65
+ async function resolveMinBuilderCliVersion(controllerUrl, dataplaneUrl, options = {}) {
66
+ const forceRefresh = Boolean(options.forceRefresh);
67
+ if (!forceRefresh) {
68
+ const cached = await safeReadCache(controllerUrl);
69
+ if (cached && !isCacheStale(cached)) {
70
+ return {
71
+ minBuilderCliVersion: cached.minBuilderCliVersion,
72
+ dataplaneVersion: cached.version
73
+ };
74
+ }
75
+ }
76
+
77
+ const snapshot = await safeFetch(dataplaneUrl);
78
+ if (!snapshot) {
79
+ // Cannot reach dataplane → do not gate (offline / pre-rollout). Reuse
80
+ // any cached value we may have.
81
+ const cached = await safeReadCache(controllerUrl);
82
+ return {
83
+ minBuilderCliVersion: cached ? cached.minBuilderCliVersion : undefined,
84
+ dataplaneVersion: cached ? cached.version : undefined
85
+ };
86
+ }
87
+
88
+ await safeWriteCache(controllerUrl, {
89
+ version: snapshot.version,
90
+ minBuilderCliVersion: snapshot.minBuilderCliVersion || null
91
+ });
92
+ return {
93
+ minBuilderCliVersion: snapshot.minBuilderCliVersion,
94
+ dataplaneVersion: snapshot.version
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Read cached versions without throwing — gate failures should not mask real
100
+ * dataplane errors caused by config corruption.
101
+ * @private
102
+ * @async
103
+ * @param {string} controllerUrl
104
+ * @returns {Promise<Object|null>}
105
+ */
106
+ async function safeReadCache(controllerUrl) {
107
+ if (!controllerUrl) return null;
108
+ try {
109
+ return await getDeviceDataplaneVersions(controllerUrl);
110
+ } catch (_error) {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Fetch health swallowing transport errors so callers can decide what to do
117
+ * when the dataplane is unreachable.
118
+ * @private
119
+ * @async
120
+ * @param {string} dataplaneUrl
121
+ * @returns {Promise<Object|null>}
122
+ */
123
+ async function safeFetch(dataplaneUrl) {
124
+ try {
125
+ return await fetchDataplaneGeneralHealth(dataplaneUrl);
126
+ } catch (_error) {
127
+ return null;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Write the cache without throwing — best-effort persistence.
133
+ * @private
134
+ * @async
135
+ * @param {string} controllerUrl
136
+ * @param {Object} versions
137
+ * @returns {Promise<void>}
138
+ */
139
+ async function safeWriteCache(controllerUrl, versions) {
140
+ if (!controllerUrl) return;
141
+ try {
142
+ await updateDeviceDataplaneVersions(controllerUrl, versions);
143
+ } catch (_error) {
144
+ // intentionally swallowed — cache write failure must not block the call
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Throw when the installed CLI is older than the dataplane minimum.
150
+ *
151
+ * No-op when:
152
+ * - `dataplaneUrl` is empty (no dataplane in play)
153
+ * - Dataplane omits `minBuilderCliVersion` (no enforcement)
154
+ * - Dataplane is unreachable and no cached minimum is known
155
+ * - Cached/advertised minimum is not valid semver (warn-only contract per plan)
156
+ *
157
+ * @async
158
+ * @param {string} dataplaneUrl - Dataplane base URL (e.g. `http://localhost:3201`)
159
+ * @param {Object} [options]
160
+ * @param {string} [options.controllerUrl] - Controller URL for cache lookup/write
161
+ * @param {boolean} [options.forceRefresh] - Skip cache and probe health now
162
+ * @returns {Promise<void>}
163
+ * @throws {Error & { code: 'CLI_VERSION_INCOMPATIBLE', required: string, installed: string, formatted: string }}
164
+ */
165
+ async function assertDataplaneCliVersionCompatible(dataplaneUrl, options = {}) {
166
+ if (!dataplaneUrl || typeof dataplaneUrl !== 'string') {
167
+ return;
168
+ }
169
+ const { minBuilderCliVersion } = await resolveMinBuilderCliVersion(
170
+ options.controllerUrl || null,
171
+ dataplaneUrl,
172
+ { forceRefresh: Boolean(options.forceRefresh) }
173
+ );
174
+ if (!minBuilderCliVersion) return;
175
+ if (!isValidSemver(minBuilderCliVersion)) return;
176
+
177
+ // Go through module.exports so tests can stub `getInstalledCliVersion`.
178
+ const installed = module.exports.getInstalledCliVersion();
179
+ if (!isValidSemver(installed)) return;
180
+
181
+ if (compareSemver(installed, minBuilderCliVersion) >= 0) {
182
+ return;
183
+ }
184
+
185
+ const formatted = formatCliVersionGateError(minBuilderCliVersion, installed);
186
+ const error = new Error(
187
+ `Builder CLI ${installed} is below the dataplane minimum (${minBuilderCliVersion}). ` +
188
+ 'Upgrade with: npm install -g @aifabrix/builder@latest'
189
+ );
190
+ error.code = ERROR_CODE;
191
+ error.required = minBuilderCliVersion;
192
+ error.installed = installed;
193
+ error.formatted = formatted;
194
+ throw error;
195
+ }
196
+
197
+ module.exports = {
198
+ assertDataplaneCliVersionCompatible,
199
+ isCliVersionIncompatibleError,
200
+ getInstalledCliVersion,
201
+ ERROR_CODE
202
+ };