@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,127 @@
1
+ /**
2
+ * Internal semver comparator (no `semver` npm dependency).
3
+ *
4
+ * Supports `MAJOR.MINOR.PATCH` and optional prerelease suffix
5
+ * `-<prerelease>` (e.g. `2.45.0-beta.1`). Optional leading `v` is tolerated.
6
+ * Prerelease versions always rank **before** the same MAJOR.MINOR.PATCH
7
+ * without a prerelease (semver §11.3).
8
+ *
9
+ * @fileoverview Internal semver compare for Builder CLI compatibility gate
10
+ * @author AI Fabrix Team
11
+ * @version 2.0.0
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const SEMVER_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z][-0-9A-Za-z.]*))?$/;
17
+
18
+ /**
19
+ * Parse a semver-like string into a comparable structure.
20
+ * @param {string} input - Version string (e.g. "2.45.0" or "v2.45.0-beta.1")
21
+ * @returns {{ major:number, minor:number, patch:number, prerelease:string[] }|null}
22
+ * Parsed parts or null when input does not match the supported grammar
23
+ */
24
+ function parseSemver(input) {
25
+ if (typeof input !== 'string') {
26
+ return null;
27
+ }
28
+ const match = SEMVER_PATTERN.exec(input.trim());
29
+ if (!match) {
30
+ return null;
31
+ }
32
+ const [, major, minor, patch, prerelease] = match;
33
+ return {
34
+ major: Number(major),
35
+ minor: Number(minor),
36
+ patch: Number(patch),
37
+ prerelease: prerelease ? prerelease.split('.') : []
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Check whether a string is a supported semver (incl. optional prerelease).
43
+ * @param {string} input
44
+ * @returns {boolean}
45
+ */
46
+ function isValidSemver(input) {
47
+ return parseSemver(input) !== null;
48
+ }
49
+
50
+ /**
51
+ * Compare two prerelease identifier arrays per semver §11.4.
52
+ * @private
53
+ * @param {string[]} a
54
+ * @param {string[]} b
55
+ * @returns {-1|0|1}
56
+ */
57
+ function comparePrereleaseIds(a, b) {
58
+ const len = Math.max(a.length, b.length);
59
+ for (let i = 0; i < len; i += 1) {
60
+ if (a[i] === undefined) return -1;
61
+ if (b[i] === undefined) return 1;
62
+ const aNum = /^\d+$/.test(a[i]);
63
+ const bNum = /^\d+$/.test(b[i]);
64
+ if (aNum && bNum) {
65
+ const diff = Number(a[i]) - Number(b[i]);
66
+ if (diff !== 0) return diff > 0 ? 1 : -1;
67
+ } else if (aNum) {
68
+ return -1;
69
+ } else if (bNum) {
70
+ return 1;
71
+ } else if (a[i] !== b[i]) {
72
+ return a[i] > b[i] ? 1 : -1;
73
+ }
74
+ }
75
+ return 0;
76
+ }
77
+
78
+ /**
79
+ * Compare two semver strings.
80
+ *
81
+ * @param {string} a
82
+ * @param {string} b
83
+ * @returns {-1|0|1} `-1` when `a < b`, `0` when equal, `1` when `a > b`
84
+ * @throws {Error} When either argument is not a valid semver
85
+ *
86
+ * @example
87
+ * compareSemver('2.44.0', '2.45.0') === -1
88
+ * compareSemver('2.45.0', '2.45.0') === 0
89
+ * compareSemver('2.45.0-beta.1', '2.45.0') === -1
90
+ */
91
+ function compareSemver(a, b) {
92
+ const parsedA = parseSemver(a);
93
+ const parsedB = parseSemver(b);
94
+ if (!parsedA) throw new Error(`Invalid semver: "${a}"`);
95
+ if (!parsedB) throw new Error(`Invalid semver: "${b}"`);
96
+
97
+ if (parsedA.major !== parsedB.major) return parsedA.major > parsedB.major ? 1 : -1;
98
+ if (parsedA.minor !== parsedB.minor) return parsedA.minor > parsedB.minor ? 1 : -1;
99
+ if (parsedA.patch !== parsedB.patch) return parsedA.patch > parsedB.patch ? 1 : -1;
100
+
101
+ // §11.3 — Prerelease has lower precedence than the same MAJOR.MINOR.PATCH release.
102
+ if (parsedA.prerelease.length === 0 && parsedB.prerelease.length === 0) return 0;
103
+ if (parsedA.prerelease.length === 0) return 1;
104
+ if (parsedB.prerelease.length === 0) return -1;
105
+
106
+ return comparePrereleaseIds(parsedA.prerelease, parsedB.prerelease);
107
+ }
108
+
109
+ /**
110
+ * Convenience: true when `current >= required`.
111
+ * Returns `true` when `required` is empty/null/undefined (no enforcement).
112
+ * @param {string} current
113
+ * @param {string} [required]
114
+ * @returns {boolean}
115
+ * @throws {Error} If either provided value is not valid semver
116
+ */
117
+ function isAtLeast(current, required) {
118
+ if (required === undefined || required === null || required === '') return true;
119
+ return compareSemver(current, required) >= 0;
120
+ }
121
+
122
+ module.exports = {
123
+ parseSemver,
124
+ isValidSemver,
125
+ compareSemver,
126
+ isAtLeast
127
+ };
@@ -17,6 +17,10 @@ const path = require('path');
17
17
  * `builder/`) beside `config.yaml` (e.g. `$HOME/.aifabrix/builder` when `AIFABRIX_HOME=$HOME`).
18
18
  * When `aifabrix-home` relocates home outside the config tree, materialize under that home instead.
19
19
  *
20
+ * Uses a resolved-path prefix check (not `path.relative`) so "nested under home" is stable across
21
+ * platforms (e.g. sibling paths like `/var/aifabrix` vs `/var/aifabrix-config` must not be treated
22
+ * as nested).
23
+ *
20
24
  * @param {string} systemDir - Absolute config/state directory (same as `getAifabrixSystemDir()`).
21
25
  * @param {string} homeDir - Absolute AI Fabrix home (`getAifabrixHome()`).
22
26
  * @returns {string} Absolute directory whose `builder/` subdir is used
@@ -24,8 +28,12 @@ const path = require('path');
24
28
  function resolveSystemBuilderParentDir(systemDir, homeDir) {
25
29
  const s = path.resolve(systemDir);
26
30
  const h = path.resolve(homeDir);
27
- const rel = path.relative(h, s);
28
- const configUnderHome = rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
31
+ if (s === h) {
32
+ return s;
33
+ }
34
+ const sep = path.sep;
35
+ const homePrefix = h.endsWith(sep) ? h : `${h}${sep}`;
36
+ const configUnderHome = s.startsWith(homePrefix);
29
37
  return configUnderHome ? s : h;
30
38
  }
31
39
 
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @fileoverview Progress line + optional ora spinner for dataplane pipeline upload/publish.
3
+ * @author AI Fabrix Team
4
+ * @version 1.1.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 shouldShowUploadPipelineProgress(options = {}) {
19
+ return options.json !== true;
20
+ }
21
+
22
+ /**
23
+ * @param {Object} [options]
24
+ * @returns {boolean}
25
+ */
26
+ function shouldUseUploadPipelineSpinner(options = {}) {
27
+ if (!shouldShowUploadPipelineProgress(options)) {
28
+ return false;
29
+ }
30
+ return Boolean(process && process.stdout && process.stdout.isTTY);
31
+ }
32
+
33
+ /**
34
+ * Plain label for spinner / progress (no glyph).
35
+ * @param {string} systemKey
36
+ * @returns {string}
37
+ */
38
+ function buildUploadPipelineSpinnerLabel(systemKey) {
39
+ const key = systemKey && String(systemKey).trim() ? String(systemKey).trim() : 'integration';
40
+ return `Publishing ${key} to dataplane…`;
41
+ }
42
+
43
+ /**
44
+ * @param {string} systemKey
45
+ * @returns {string}
46
+ */
47
+ function buildUploadPipelineProgressText(systemKey) {
48
+ return formatProgress(buildUploadPipelineSpinnerLabel(systemKey));
49
+ }
50
+
51
+ /**
52
+ * Run pipeline upload + OpenAPI sync with visible progress (⏳ line or ora with label on TTY).
53
+ *
54
+ * @async
55
+ * @param {Function} work - Zero-arg async function (publish + follow-up I/O)
56
+ * @param {Object} [opts]
57
+ * @param {string} [opts.systemKey]
58
+ * @param {boolean} [opts.json]
59
+ * @returns {Promise<*>}
60
+ */
61
+ async function runWithUploadPipelineSpinner(work, opts = {}) {
62
+ if (!shouldShowUploadPipelineProgress(opts)) {
63
+ return work();
64
+ }
65
+
66
+ if (!shouldUseUploadPipelineSpinner(opts)) {
67
+ logger.log(buildUploadPipelineProgressText(opts.systemKey));
68
+ return work();
69
+ }
70
+
71
+ const ora = require('ora');
72
+ const label = buildUploadPipelineSpinnerLabel(opts.systemKey);
73
+ const spinner = ora({
74
+ text: chalk.white(label),
75
+ spinner: 'dots'
76
+ }).start();
77
+
78
+ try {
79
+ return await work();
80
+ } finally {
81
+ spinner.stop();
82
+ }
83
+ }
84
+
85
+ module.exports = {
86
+ shouldShowUploadPipelineProgress,
87
+ shouldUseUploadPipelineSpinner,
88
+ buildUploadPipelineSpinnerLabel,
89
+ buildUploadPipelineProgressText,
90
+ runWithUploadPipelineSpinner
91
+ };
@@ -11,6 +11,26 @@
11
11
  const { expandFrontDoorHostPlaceholders } = require('./compose-generator');
12
12
  const { publishedHostPort, localHostPort } = require('./declarative-url-ports');
13
13
 
14
+ /**
15
+ * Public URL authorities that must use `http://` (browsers hit loopback without TLS in practice).
16
+ *
17
+ * @param {string} hostname - Parsed URL hostname
18
+ * @returns {boolean}
19
+ */
20
+ function isLocalhostPublicSchemeHostname(hostname) {
21
+ const h = String(hostname || '').trim().toLowerCase();
22
+ if (!h) {
23
+ return false;
24
+ }
25
+ if (h === 'localhost') {
26
+ return true;
27
+ }
28
+ if (h === '127.0.0.1') {
29
+ return true;
30
+ }
31
+ return h === '::1';
32
+ }
33
+
14
34
  /**
15
35
  * Expand frontDoorRouting.host placeholders for url:// (same rules as Traefik labels in compose-generator).
16
36
  *
@@ -31,6 +51,17 @@ function hostPortForProfile(profile, listenPort, developerIdNum) {
31
51
  : localHostPort(listenPort, developerIdNum);
32
52
  }
33
53
 
54
+ /**
55
+ * Scheme for `declarativePublicUrlsUseLocalhost` bases: always **http** for loopback reachability
56
+ * (ignore `tlsEnabled` / `remote-server` https — localhost URLs are not served with TLS in dev).
57
+ * @param {Object} opts - Kept for call-site compatibility (same shape as {@link computePublicUrlBaseString})
58
+ * @returns {'http'}
59
+ */
60
+ function schemeForDeclarativeLocalhostPublicBase(opts) {
61
+ void opts;
62
+ return 'http';
63
+ }
64
+
34
65
  /**
35
66
  * Local profile: workstation `+10` applies only to the app being resolved (`currentAppKey`).
36
67
  * Cross-app tokens (e.g. `url://keycloak-public` from miso-controller) use `publishedHostPort`
@@ -62,10 +93,10 @@ function resolveHostPortForDeclarativePublic(opts) {
62
93
  }
63
94
 
64
95
  /**
65
- * Without Traefik, `remote-server` is the dev machine host. Apps bind published host ports, not 443 + path.
66
- * If the URL already has an explicit port, keep host:port but **scheme follows `infraTlsEnabled`**, not the
67
- * literal `https://` in `remote-server` when TLS is off (`up-infra` without `--tls`).
68
- * If `remote-server` omits a port, append the profile-specific published/listen-derived port.
96
+ * Public authority from `remote-server` when expansion opts in (Traefik on, or per-app `proxy: true` ⇒
97
+ * `declarativePublicUrlsUseLocalhost === false`). If the URL already has an explicit port, keep host:port but
98
+ * **scheme follows `infraTlsEnabled`**, not the literal `https://` in `remote-server` when TLS is off
99
+ * (`up-infra` without `--tls`). If `remote-server` omits a port, append the profile-specific published/listen-derived port.
69
100
  *
70
101
  * @param {string} rawRemote
71
102
  * @param {'docker'|'local'} profile
@@ -85,7 +116,10 @@ function remotePublicBaseWithoutTraefik(
85
116
  const raw = String(rawRemote || '').trim().replace(/\/+$/, '');
86
117
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : (infraTlsEnabled ? `https://${raw}` : `http://${raw}`);
87
118
  const u = new URL(withScheme);
88
- const scheme = infraTlsEnabled ? 'https' : 'http';
119
+ let scheme = infraTlsEnabled ? 'https' : 'http';
120
+ if (isLocalhostPublicSchemeHostname(u.hostname)) {
121
+ scheme = 'http';
122
+ }
89
123
 
90
124
  if (u.port !== '') {
91
125
  return `${scheme}://${u.host}`;
@@ -105,17 +139,31 @@ function remotePublicBaseWithoutTraefik(
105
139
  return `${scheme}://${u.hostname}:${hostPort}`;
106
140
  }
107
141
 
142
+ /**
143
+ * True when `frontDoorRouting.host` depends on `remote-server` for expansion (`${REMOTE_HOST}`).
144
+ * Without a remote, expansion yields a bare `devNN` label (no DNS) — public `url://` bases must fall back to localhost.
145
+ *
146
+ * @param {string|null|undefined} hostTemplate
147
+ * @returns {boolean}
148
+ */
149
+ function templateUsesRemoteHostPlaceholder(hostTemplate) {
150
+ return /\$\{REMOTE_HOST\}/.test(String(hostTemplate || ''));
151
+ }
152
+
108
153
  /**
109
154
  * @param {Object} opts - same shape as computePublicUrlBaseString
110
155
  * @returns {string|null}
111
156
  */
112
157
  function buildTraefikPublicBaseIfApplicable(opts) {
113
- const { traefik, pathActive, hostTemplate, tls, developerIdRaw, remoteServer, infraTlsEnabled } =
158
+ const { traefik, pathActive, hostTemplate, developerIdRaw, remoteServer, infraTlsEnabled } =
114
159
  opts;
115
160
  // Plan 124: Traefik host authority only when pathActive (traefik ∧ frontDoorRouting.enabled)
116
161
  if (!traefik || !pathActive || !hostTemplate || !String(hostTemplate).trim()) {
117
162
  return null;
118
163
  }
164
+ if (templateUsesRemoteHostPlaceholder(hostTemplate) && !String(remoteServer || '').trim()) {
165
+ return null;
166
+ }
119
167
  const expanded = expandFrontDoorHostTemplateForUrls(hostTemplate, {
120
168
  developerIdRaw,
121
169
  remoteServer
@@ -123,8 +171,12 @@ function buildTraefikPublicBaseIfApplicable(opts) {
123
171
  if (!expanded) {
124
172
  return null;
125
173
  }
126
- const useHttps = Boolean(infraTlsEnabled) || tls !== false;
127
- const scheme = useHttps ? 'https' : 'http';
174
+ // Scheme follows global `tlsEnabled` (`up-infra --tls`) only — not `frontDoorRouting.tls` when infra TLS is off.
175
+ const useHttps = Boolean(infraTlsEnabled);
176
+ let scheme = useHttps ? 'https' : 'http';
177
+ if (isLocalhostPublicSchemeHostname(expanded)) {
178
+ scheme = 'http';
179
+ }
128
180
  return `${scheme}://${expanded}`.replace(/\/+$/, '');
129
181
  }
130
182
 
@@ -140,8 +192,9 @@ function buildTraefikPublicBaseIfApplicable(opts) {
140
192
  * @param {'docker'|'local'} opts.profile
141
193
  * @param {number} opts.listenPort
142
194
  * @param {number} opts.developerIdNum
143
- * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); when true, Traefik front-door public URLs use https even if application.yaml has `frontDoorRouting.tls: false`
195
+ * @param {boolean} [opts.infraTlsEnabled] - `tlsEnabled` from ~/.aifabrix/config.yaml (`up-infra --tls`); Traefik front-door public bases use **`https`** only when this is **true** (even if `frontDoorRouting.tls` is **false**). When **false**, front-door bases use **`http`** regardless of `frontDoorRouting.tls`. Loopback hostnames stay **http** always.
144
196
  * @param {boolean} [opts.pathActive] - traefik ∧ frontDoorRouting.enabled; required for Traefik host branch (plan 124)
197
+ * @param {boolean|undefined} [opts.declarativePublicUrlsUseLocalhost] - **`true`**: force localhost authority (+ port rules below). **`false`**: allow `remote-server` as authority when Traefik is on, or when global `traefik` is not `false` and per-app `proxy` opts in without Traefik. **`undefined`**: default — omit `remote-server` when Traefik is off so published services use `http://localhost:<port>` (e.g. `af setup` / device login URLs). When user config has **`traefik: false`**, expansion forces localhost for public bases regardless of per-app `proxy`.
145
198
  * @returns {string}
146
199
  */
147
200
  function computePublicUrlBaseString(opts) {
@@ -159,7 +212,23 @@ function computePublicUrlBaseString(opts) {
159
212
  return traefikBase;
160
213
  }
161
214
 
162
- if (remoteServer && String(remoteServer).trim()) {
215
+ if (opts.declarativePublicUrlsUseLocalhost) {
216
+ const hostPort = resolveHostPortForDeclarativePublic({
217
+ profile,
218
+ listenPort,
219
+ developerIdNum,
220
+ declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
221
+ declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
222
+ });
223
+ const scheme = schemeForDeclarativeLocalhostPublicBase(opts);
224
+ return `${scheme}://localhost:${hostPort}`;
225
+ }
226
+
227
+ const remoteTrimmed = remoteServer && String(remoteServer).trim();
228
+ const useRemotePublicAuthority =
229
+ Boolean(remoteTrimmed) &&
230
+ (Boolean(opts.traefik) || opts.declarativePublicUrlsUseLocalhost === false);
231
+ if (useRemotePublicAuthority) {
163
232
  return remotePublicBaseWithoutTraefik(
164
233
  remoteServer,
165
234
  profile,
@@ -177,8 +246,7 @@ function computePublicUrlBaseString(opts) {
177
246
  declarativeTargetAppKey: declarativePortOpts.declarativeTargetAppKey,
178
247
  declarativeCurrentAppKey: declarativePortOpts.declarativeCurrentAppKey
179
248
  });
180
- const scheme = infraTlsEnabled ? 'https' : 'http';
181
- return `${scheme}://localhost:${hostPort}`;
249
+ return `http://localhost:${hostPort}`;
182
250
  }
183
251
 
184
252
  module.exports = {
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Public/internal URL string builders for declarative url:// (join + build*).
3
+ *
4
+ * @fileoverview Split from url-declarative-resolve-build.js for ESLint max-lines
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const {
12
+ computePublicUrlBaseString,
13
+ resolveHostPortForDeclarativePublic
14
+ } = require('./url-declarative-public-base');
15
+
16
+ /**
17
+ * Join base URL origin with path segments (no duplicate slashes).
18
+ * @param {string} originOrBase - https://host or https://host:port
19
+ * @param {string} suffixPath - /dev/data
20
+ * @returns {string}
21
+ */
22
+ function joinUrlPath(originOrBase, suffixPath) {
23
+ const base = String(originOrBase || '').replace(/\/+$/, '');
24
+ let suf = suffixPath || '';
25
+ if (!suf.startsWith('/')) {
26
+ suf = `/${suf}`;
27
+ }
28
+ return `${base}${suf}`.replace(/([^:]\/)\/+/g, '$1');
29
+ }
30
+
31
+ /**
32
+ * @param {Object} opts
33
+ * @returns {string}
34
+ */
35
+ function buildPublicUrlString(opts) {
36
+ const {
37
+ profile,
38
+ listenPort,
39
+ developerIdNum,
40
+ remoteServer,
41
+ pathPrefix,
42
+ patternPath,
43
+ traefik,
44
+ hostTemplate,
45
+ tls,
46
+ developerIdRaw,
47
+ infraTlsEnabled,
48
+ frontDoorIngressActive,
49
+ declarativeTargetAppKey,
50
+ declarativeCurrentAppKey,
51
+ declarativePublicUrlsUseLocalhost
52
+ } = opts;
53
+ const base = computePublicUrlBaseString({
54
+ traefik: Boolean(traefik),
55
+ pathActive: Boolean(frontDoorIngressActive),
56
+ hostTemplate: hostTemplate || null,
57
+ tls: tls !== false,
58
+ developerIdRaw,
59
+ remoteServer,
60
+ profile,
61
+ listenPort,
62
+ developerIdNum,
63
+ infraTlsEnabled: Boolean(infraTlsEnabled),
64
+ declarativeTargetAppKey,
65
+ declarativeCurrentAppKey,
66
+ declarativePublicUrlsUseLocalhost
67
+ });
68
+ const patternSegment =
69
+ frontDoorIngressActive === false
70
+ ? ''
71
+ : patternPath === '/' || patternPath === ''
72
+ ? ''
73
+ : patternPath;
74
+ const rawSuffix = `${pathPrefix}${patternSegment}`.replace(/\/{2,}/g, '/');
75
+ if (!rawSuffix || rawSuffix === '/') {
76
+ return String(base).replace(/\/+$/, '');
77
+ }
78
+ const normalizedSuffix = rawSuffix.startsWith('/') ? rawSuffix : `/${rawSuffix}`;
79
+ return joinUrlPath(base, normalizedSuffix);
80
+ }
81
+
82
+ /**
83
+ * Public reachability origin only (no env path prefix, no front-door pattern path).
84
+ * @param {Object} opts
85
+ * @returns {string}
86
+ */
87
+ function buildPublicHostOriginString(opts) {
88
+ const {
89
+ profile,
90
+ listenPort,
91
+ developerIdNum,
92
+ remoteServer,
93
+ traefik,
94
+ hostTemplate,
95
+ tls,
96
+ developerIdRaw,
97
+ infraTlsEnabled,
98
+ frontDoorIngressActive,
99
+ declarativeTargetAppKey,
100
+ declarativeCurrentAppKey,
101
+ declarativePublicUrlsUseLocalhost
102
+ } = opts;
103
+ const base = computePublicUrlBaseString({
104
+ traefik: Boolean(traefik),
105
+ pathActive: Boolean(frontDoorIngressActive),
106
+ hostTemplate: hostTemplate || null,
107
+ tls: tls !== false,
108
+ developerIdRaw,
109
+ remoteServer,
110
+ profile,
111
+ listenPort,
112
+ developerIdNum,
113
+ infraTlsEnabled: Boolean(infraTlsEnabled),
114
+ declarativeTargetAppKey,
115
+ declarativeCurrentAppKey,
116
+ declarativePublicUrlsUseLocalhost
117
+ });
118
+ try {
119
+ return new URL(base).origin;
120
+ } catch {
121
+ const hostPort = resolveHostPortForDeclarativePublic({
122
+ profile,
123
+ listenPort,
124
+ developerIdNum,
125
+ declarativeTargetAppKey,
126
+ declarativeCurrentAppKey
127
+ });
128
+ return `http://localhost:${hostPort}`;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Internal service origin only (scheme + host + port), no path suffix.
134
+ * @param {Object} opts
135
+ * @returns {string}
136
+ */
137
+ function buildInternalHostOriginString(opts) {
138
+ const {
139
+ profile,
140
+ listenPort,
141
+ targetAppKey,
142
+ remoteServer,
143
+ pathPrefix,
144
+ patternPath,
145
+ developerIdNum,
146
+ traefik,
147
+ hostTemplate,
148
+ tls,
149
+ developerIdRaw,
150
+ infraTlsEnabled,
151
+ frontDoorIngressActive,
152
+ declarativeTargetAppKey,
153
+ declarativeCurrentAppKey,
154
+ declarativePublicUrlsUseLocalhost
155
+ } = opts;
156
+ if (profile === 'docker') {
157
+ return `http://${targetAppKey}:${listenPort}`;
158
+ }
159
+ if (remoteServer && String(remoteServer).trim()) {
160
+ const pub = buildPublicUrlString({
161
+ profile: 'local',
162
+ listenPort,
163
+ developerIdNum,
164
+ remoteServer,
165
+ pathPrefix,
166
+ patternPath,
167
+ traefik,
168
+ hostTemplate,
169
+ tls,
170
+ developerIdRaw,
171
+ infraTlsEnabled,
172
+ frontDoorIngressActive,
173
+ declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
174
+ declarativeCurrentAppKey: declarativeCurrentAppKey,
175
+ declarativePublicUrlsUseLocalhost
176
+ });
177
+ try {
178
+ return new URL(pub).origin;
179
+ } catch {
180
+ return `http://${targetAppKey}:${listenPort}`;
181
+ }
182
+ }
183
+ return `http://${targetAppKey}:${listenPort}`;
184
+ }
185
+
186
+ function buildInternalUrlString(opts) {
187
+ const {
188
+ profile,
189
+ listenPort,
190
+ targetAppKey,
191
+ runtimeBasePath,
192
+ remoteServer,
193
+ pathPrefix,
194
+ patternPath,
195
+ developerIdNum,
196
+ traefik,
197
+ hostTemplate,
198
+ tls,
199
+ developerIdRaw,
200
+ infraTlsEnabled,
201
+ frontDoorIngressActive,
202
+ declarativeTargetAppKey,
203
+ declarativeCurrentAppKey,
204
+ declarativePublicUrlsUseLocalhost
205
+ } = opts;
206
+ if (profile === 'docker') {
207
+ const origin = `http://${targetAppKey}:${listenPort}`;
208
+ return runtimeBasePath ? joinUrlPath(origin, runtimeBasePath) : origin;
209
+ }
210
+ if (remoteServer && String(remoteServer).trim()) {
211
+ return buildPublicUrlString({
212
+ profile: 'local',
213
+ listenPort,
214
+ developerIdNum,
215
+ remoteServer,
216
+ pathPrefix,
217
+ patternPath,
218
+ traefik,
219
+ hostTemplate,
220
+ tls,
221
+ developerIdRaw,
222
+ infraTlsEnabled,
223
+ frontDoorIngressActive,
224
+ declarativeTargetAppKey: declarativeTargetAppKey || targetAppKey,
225
+ declarativeCurrentAppKey: declarativeCurrentAppKey,
226
+ declarativePublicUrlsUseLocalhost
227
+ });
228
+ }
229
+ return `http://${targetAppKey}:${listenPort}`;
230
+ }
231
+
232
+ module.exports = {
233
+ joinUrlPath,
234
+ buildPublicUrlString,
235
+ buildPublicHostOriginString,
236
+ buildInternalUrlString,
237
+ buildInternalHostOriginString
238
+ };