@aifabrix/builder 2.44.5 → 2.44.6

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 (207) hide show
  1. package/.cursor/rules/cli-layout.mdc +1 -1
  2. package/.cursor/rules/project-rules.mdc +1 -1
  3. package/.npmrc.token +1 -1
  4. package/README.md +15 -23
  5. package/integration/hubspot-test/README.md +2 -0
  6. package/integration/hubspot-test/test.js +5 -3
  7. package/jest.projects.js +48 -2
  8. package/lib/api/controller-health.api.js +49 -0
  9. package/lib/api/dimension-values.api.js +82 -0
  10. package/lib/api/dimensions.api.js +114 -0
  11. package/lib/api/external-systems.api.js +1 -0
  12. package/lib/api/integration-clients.api.js +168 -0
  13. package/lib/api/types/dimension-values.types.js +28 -0
  14. package/lib/api/types/dimensions.types.js +31 -0
  15. package/lib/api/types/integration-clients.types.js +45 -0
  16. package/lib/api/validation-runner.js +46 -25
  17. package/lib/app/deploy-config.js +11 -1
  18. package/lib/app/deploy-status-display.js +3 -3
  19. package/lib/app/deploy.js +36 -14
  20. package/lib/app/display.js +15 -11
  21. package/lib/app/push.js +46 -23
  22. package/lib/app/register.js +1 -1
  23. package/lib/app/restart-display.js +95 -0
  24. package/lib/app/rotate-secret.js +1 -1
  25. package/lib/app/run-container-start.js +12 -6
  26. package/lib/app/run-env-compose.js +30 -1
  27. package/lib/app/run-helpers.js +44 -12
  28. package/lib/app/run-reload-sync.js +148 -0
  29. package/lib/app/run-resolve-image.js +51 -1
  30. package/lib/app/run.js +99 -73
  31. package/lib/build/index.js +75 -45
  32. package/lib/cli/doctor-check.js +117 -0
  33. package/lib/cli/index.js +8 -2
  34. package/lib/cli/infra-guided.js +445 -0
  35. package/lib/cli/setup-app.js +20 -2
  36. package/lib/cli/setup-auth.js +26 -0
  37. package/lib/cli/setup-dev-path-commands.js +50 -3
  38. package/lib/cli/setup-infra.js +134 -61
  39. package/lib/cli/setup-integration-client.js +182 -0
  40. package/lib/cli/setup-parameters.js +21 -2
  41. package/lib/cli/setup-platform.js +102 -0
  42. package/lib/cli/setup-secrets.js +18 -6
  43. package/lib/cli/setup-utility.js +78 -33
  44. package/lib/commands/datasource-capability-dimension-cli.js +128 -0
  45. package/lib/commands/datasource-capability-output.js +29 -0
  46. package/lib/commands/datasource-capability-relate-cli.js +140 -0
  47. package/lib/commands/datasource-capability.js +411 -0
  48. package/lib/commands/datasource-unified-test-cli.options.js +1 -1
  49. package/lib/commands/datasource.js +53 -13
  50. package/lib/commands/dev-down.js +3 -3
  51. package/lib/commands/dev-infra-gate.js +32 -0
  52. package/lib/commands/dev-init.js +13 -7
  53. package/lib/commands/dimension-value.js +179 -0
  54. package/lib/commands/dimension.js +330 -0
  55. package/lib/commands/integration-client.js +430 -0
  56. package/lib/commands/login-device.js +65 -30
  57. package/lib/commands/login.js +21 -10
  58. package/lib/commands/parameters-validate.js +78 -13
  59. package/lib/commands/repair-datasource-auto-rbac.js +166 -0
  60. package/lib/commands/repair-datasource-keys.js +10 -5
  61. package/lib/commands/repair-datasource.js +19 -7
  62. package/lib/commands/repair-env-template.js +4 -1
  63. package/lib/commands/repair-openapi-sync.js +172 -0
  64. package/lib/commands/repair-persist.js +102 -0
  65. package/lib/commands/repair-rbac-extract.js +27 -0
  66. package/lib/commands/repair-rbac-migrate.js +186 -0
  67. package/lib/commands/repair-rbac.js +214 -31
  68. package/lib/commands/repair-system-alignment.js +246 -0
  69. package/lib/commands/repair-system-permissions.js +168 -0
  70. package/lib/commands/repair.js +120 -338
  71. package/lib/commands/secure.js +1 -1
  72. package/lib/commands/setup-modes.js +455 -0
  73. package/lib/commands/setup-prompts.js +388 -0
  74. package/lib/commands/setup.js +149 -0
  75. package/lib/commands/teardown.js +228 -0
  76. package/lib/commands/up-common.js +79 -19
  77. package/lib/commands/up-dataplane.js +33 -11
  78. package/lib/commands/up-miso.js +7 -11
  79. package/lib/commands/upload.js +109 -23
  80. package/lib/commands/wizard-core-helpers.js +14 -11
  81. package/lib/commands/wizard-core.js +6 -5
  82. package/lib/commands/wizard-dataplane.js +2 -2
  83. package/lib/commands/wizard-entity-selection.js +4 -3
  84. package/lib/commands/wizard-headless.js +2 -1
  85. package/lib/commands/wizard.js +2 -1
  86. package/lib/constants/infra-compose-service-names.js +40 -0
  87. package/lib/core/env-reader.js +16 -3
  88. package/lib/core/secrets-admin-env.js +101 -0
  89. package/lib/core/secrets-ensure-infra.js +34 -1
  90. package/lib/core/secrets-ensure.js +88 -66
  91. package/lib/core/secrets-env-content.js +432 -0
  92. package/lib/core/secrets-env-write.js +27 -1
  93. package/lib/core/secrets-load.js +248 -0
  94. package/lib/core/secrets-names.js +32 -0
  95. package/lib/core/secrets.js +17 -757
  96. package/lib/datasource/capability/basic-exposure.js +76 -0
  97. package/lib/datasource/capability/capability-diff-slice.js +41 -0
  98. package/lib/datasource/capability/capability-key.js +34 -0
  99. package/lib/datasource/capability/capability-resolve.js +172 -0
  100. package/lib/datasource/capability/capability-storage-keys.js +22 -0
  101. package/lib/datasource/capability/copy-operations.js +348 -0
  102. package/lib/datasource/capability/copy-test-payload.js +139 -0
  103. package/lib/datasource/capability/create-operations.js +235 -0
  104. package/lib/datasource/capability/dimension-operations.js +151 -0
  105. package/lib/datasource/capability/dimension-validate.js +219 -0
  106. package/lib/datasource/capability/json-pointer.js +31 -0
  107. package/lib/datasource/capability/reference-rewrite.js +51 -0
  108. package/lib/datasource/capability/relate-operations.js +325 -0
  109. package/lib/datasource/capability/relate-validate.js +219 -0
  110. package/lib/datasource/capability/remove-operations.js +275 -0
  111. package/lib/datasource/capability/run-capability-copy.js +152 -0
  112. package/lib/datasource/capability/run-capability-diff.js +135 -0
  113. package/lib/datasource/capability/run-capability-dimension.js +291 -0
  114. package/lib/datasource/capability/run-capability-edit.js +377 -0
  115. package/lib/datasource/capability/run-capability-relate.js +193 -0
  116. package/lib/datasource/capability/run-capability-remove.js +105 -0
  117. package/lib/datasource/capability/templates/minimal-fetch.json +18 -0
  118. package/lib/datasource/capability/validate-capability-slice.js +35 -0
  119. package/lib/datasource/list.js +136 -23
  120. package/lib/datasource/log-viewer.js +2 -4
  121. package/lib/datasource/unified-validation-run.js +51 -16
  122. package/lib/datasource/validate.js +53 -1
  123. package/lib/deployment/deploy-poll-ui.js +60 -0
  124. package/lib/deployment/deployer-status.js +29 -3
  125. package/lib/deployment/deployer.js +48 -30
  126. package/lib/deployment/environment.js +7 -2
  127. package/lib/deployment/poll-interval.js +72 -0
  128. package/lib/deployment/push.js +11 -9
  129. package/lib/external-system/deploy.js +4 -1
  130. package/lib/external-system/download.js +61 -32
  131. package/lib/external-system/sync-deploy-manifest.js +33 -0
  132. package/lib/infrastructure/index.js +49 -19
  133. package/lib/infrastructure/orphan-infra-docker-teardown.js +177 -0
  134. package/lib/parameters/infra-kv-discovery.js +29 -4
  135. package/lib/parameters/infra-parameter-catalog.js +6 -3
  136. package/lib/parameters/infra-parameter-validate.js +67 -19
  137. package/lib/resolvers/datasource-resolver.js +53 -0
  138. package/lib/resolvers/dimension-file.js +52 -0
  139. package/lib/resolvers/manifest-resolver.js +133 -0
  140. package/lib/schema/external-datasource.schema.json +183 -53
  141. package/lib/schema/external-system.schema.json +23 -10
  142. package/lib/schema/infra.parameter.yaml +26 -11
  143. package/lib/schema/wizard-config.schema.json +1 -1
  144. package/lib/utils/aifabrix-config-dir-walk.js +40 -0
  145. package/lib/utils/aifabrix-runtime-config-dir.js +26 -3
  146. package/lib/utils/app-run-containers.js +2 -2
  147. package/lib/utils/bash-secret-env.js +59 -0
  148. package/lib/utils/cli-secrets-error-format.js +78 -0
  149. package/lib/utils/cli-test-layout-chalk.js +31 -9
  150. package/lib/utils/cli-utils.js +4 -36
  151. package/lib/utils/datasource-test-run-display.js +8 -0
  152. package/lib/utils/dev-hosts-helper.js +3 -2
  153. package/lib/utils/dev-init-ssh-merge.js +2 -1
  154. package/lib/utils/docker-build.js +17 -9
  155. package/lib/utils/docker-reload-mount.js +127 -0
  156. package/lib/utils/external-readme.js +71 -2
  157. package/lib/utils/external-system-local-test-tty.js +3 -2
  158. package/lib/utils/external-system-readiness-core.js +45 -12
  159. package/lib/utils/external-system-readiness-deploy-display.js +3 -3
  160. package/lib/utils/external-system-readiness-display-internals.js +33 -3
  161. package/lib/utils/external-system-readiness-display.js +10 -1
  162. package/lib/utils/file-upload.js +40 -3
  163. package/lib/utils/health-check-db-init.js +107 -0
  164. package/lib/utils/health-check-public-warn.js +69 -0
  165. package/lib/utils/health-check-url.js +19 -4
  166. package/lib/utils/health-check.js +135 -105
  167. package/lib/utils/help-builder.js +5 -1
  168. package/lib/utils/image-name.js +34 -7
  169. package/lib/utils/integration-file-backup.js +74 -0
  170. package/lib/utils/mutagen-install.js +30 -3
  171. package/lib/utils/paths.js +108 -25
  172. package/lib/utils/postgres-wipe.js +212 -0
  173. package/lib/utils/register-aifabrix-shell-env.js +15 -0
  174. package/lib/utils/remote-dev-auth.js +21 -5
  175. package/lib/utils/remote-docker-env.js +9 -1
  176. package/lib/utils/remote-secrets-loader.js +42 -3
  177. package/lib/utils/resolve-docker-image-ref.js +9 -3
  178. package/lib/utils/secrets-ancestor-paths.js +47 -0
  179. package/lib/utils/secrets-helpers.js +17 -10
  180. package/lib/utils/secrets-kv-refs.js +42 -0
  181. package/lib/utils/secrets-kv-scope.js +19 -2
  182. package/lib/utils/secrets-materialize-local.js +134 -0
  183. package/lib/utils/secrets-path.js +24 -10
  184. package/lib/utils/secrets-utils.js +2 -2
  185. package/lib/utils/system-builder-root.js +34 -0
  186. package/lib/utils/url-declarative-resolve-build.js +6 -1
  187. package/lib/utils/url-declarative-runtime-base-path.js +32 -0
  188. package/lib/utils/url-declarative-vdir-inactive-env.js +2 -1
  189. package/lib/utils/urls-local-registry.js +23 -12
  190. package/lib/utils/validation-poll-ui.js +81 -0
  191. package/lib/utils/validation-run-poll.js +29 -5
  192. package/lib/utils/with-muted-logger.js +53 -0
  193. package/package.json +1 -1
  194. package/templates/applications/dataplane/application.yaml +1 -1
  195. package/templates/applications/dataplane/rbac.yaml +10 -10
  196. package/templates/applications/keycloak/env.template +8 -6
  197. package/templates/applications/miso-controller/application.yaml +7 -0
  198. package/templates/applications/miso-controller/env.template +1 -1
  199. package/templates/applications/miso-controller/rbac.yaml +9 -9
  200. package/templates/external-system/README.md.hbs +83 -123
  201. package/.nyc_output/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  202. package/.nyc_output/processinfo/55e9d034-ddab-4579-a706-e02a91d75c91.json +0 -1
  203. package/.nyc_output/processinfo/index.json +0 -1
  204. package/lib/api/service-users.api.js +0 -150
  205. package/lib/api/types/service-users.types.js +0 -65
  206. package/lib/cli/setup-service-user.js +0 -187
  207. package/lib/commands/service-user.js +0 -429
@@ -19,6 +19,7 @@ const {
19
19
  headerKeyValue,
20
20
  formatStatusKeyValue,
21
21
  colorRollupPrefixedLine,
22
+ formatSuccessLine,
22
23
  metadata: metaGray
23
24
  } = require('./cli-test-layout-chalk');
24
25
 
@@ -338,7 +339,7 @@ function displayLocalExternalTestPlanLayout(results, verbose, appName) {
338
339
  function logVerboseSystemRows(systemResults) {
339
340
  for (const s of systemResults || []) {
340
341
  const ok = s.valid;
341
- logger.log(ok ? chalk.green(` ${s.file}`) : chalk.red(` ✖ ${s.file}`));
342
+ logger.log(ok ? (chalk.green(' ') + formatSuccessLine(s.file)) : chalk.red(` ✖ ${s.file}`));
342
343
  (s.errors || []).forEach(e => logger.log(chalk.red(` - ${e}`)));
343
344
  }
344
345
  }
@@ -346,7 +347,7 @@ function logVerboseSystemRows(systemResults) {
346
347
  function logVerboseDatasourceRows(datasourceResults) {
347
348
  for (const d of datasourceResults || []) {
348
349
  const ok = d.valid;
349
- logger.log(ok ? chalk.green(` ${d.key} (${d.file})`) : chalk.red(` ✖ ${d.key} (${d.file})`));
350
+ logger.log(ok ? (chalk.green(' ') + formatSuccessLine(`${d.key} (${d.file})`)) : chalk.red(` ✖ ${d.key} (${d.file})`));
350
351
  (d.errors || []).forEach(e => logger.log(chalk.red(` - ${e}`)));
351
352
  (d.warnings || []).forEach(w => logger.log(chalk.yellow(` ⚠ ${w}`)));
352
353
  if (d.fieldMappingResults && d.fieldMappingResults.mappedFields) {
@@ -43,30 +43,57 @@ function unwrapPublicationResult(res) {
43
43
  * Rules: inactive/archived → Failed; MCP expected but missing → Partial; draft → Partial; published/deployed + active → Ready.
44
44
  * @param {Object} ds - ExternalDataSourceResponse-like
45
45
  * @param {boolean} generateMcpContract - From application config
46
- * @returns {'ready'|'partial'|'failed'}
46
+ * @returns {{ tier: 'ready'|'partial'|'failed', partialReason: string|null }}
47
47
  */
48
- function classifyDatasourceTierA(ds, generateMcpContract) {
48
+ function classifyDatasourceTierADetail(ds, generateMcpContract) {
49
49
  const active = ds.isActive !== false;
50
50
  const status = String(ds.status || '').toLowerCase();
51
51
  if (!active || status === 'archived') {
52
- return 'failed';
52
+ return { tier: 'failed', partialReason: null };
53
53
  }
54
54
  if (generateMcpContract === true && !ds.mcpContract) {
55
- return 'partial';
55
+ return { tier: 'partial', partialReason: 'mcp_missing' };
56
56
  }
57
57
  if (status === 'draft') {
58
- return 'partial';
58
+ return { tier: 'partial', partialReason: 'draft' };
59
59
  }
60
60
  if (status === 'published' || status === 'deployed') {
61
- return 'ready';
61
+ return { tier: 'ready', partialReason: null };
62
+ }
63
+ return { tier: 'partial', partialReason: 'unknown_status' };
64
+ }
65
+
66
+ /**
67
+ * @param {Object} ds - ExternalDataSourceResponse-like
68
+ * @param {boolean} generateMcpContract - From application config
69
+ * @returns {'ready'|'partial'|'failed'}
70
+ */
71
+ function classifyDatasourceTierA(ds, generateMcpContract) {
72
+ return classifyDatasourceTierADetail(ds, generateMcpContract).tier;
73
+ }
74
+
75
+ /**
76
+ * Short hint for CLI when Tier A is partial (machine codes from classifyDatasourceTierADetail).
77
+ * @param {string|null} partialReason
78
+ * @returns {string}
79
+ */
80
+ function formatTierAPartialHint(partialReason) {
81
+ if (partialReason === 'mcp_missing') {
82
+ return 'no MCP contract stored (OpenAPI link or generation)';
83
+ }
84
+ if (partialReason === 'draft') {
85
+ return 'status draft (trigger paths / certification gate)';
62
86
  }
63
- return 'partial';
87
+ if (partialReason === 'unknown_status') {
88
+ return 'unexpected lifecycle status';
89
+ }
90
+ return '';
64
91
  }
65
92
 
66
93
  /**
67
94
  * @param {Array<Object>} datasources - Datasource list
68
95
  * @param {boolean} generateMcpContract
69
- * @returns {{ rows: Array<{ key: string, tier: string }>, ready: number, partial: number, failed: number }}
96
+ * @returns {{ rows: Array<{ key: string, tier: string, partialReason?: string|null }>, ready: number, partial: number, failed: number }}
70
97
  */
71
98
  function summarizeDatasourceTiersA(datasources, generateMcpContract) {
72
99
  const rows = [];
@@ -75,10 +102,14 @@ function summarizeDatasourceTiersA(datasources, generateMcpContract) {
75
102
  let failed = 0;
76
103
  for (const ds of datasources || []) {
77
104
  const key = ds.key || ds.sourceKey || 'unknown';
78
- const tier = classifyDatasourceTierA(ds, generateMcpContract);
79
- rows.push({ key, tier });
80
- if (tier === 'ready') ready += 1;
81
- else if (tier === 'partial') partial += 1;
105
+ const detail = classifyDatasourceTierADetail(ds, generateMcpContract);
106
+ const row = { key, tier: detail.tier };
107
+ if (detail.tier === 'partial' && detail.partialReason) {
108
+ row.partialReason = detail.partialReason;
109
+ }
110
+ rows.push(row);
111
+ if (detail.tier === 'ready') ready += 1;
112
+ else if (detail.tier === 'partial') partial += 1;
82
113
  else failed += 1;
83
114
  }
84
115
  return { rows, ready, partial, failed };
@@ -401,7 +432,9 @@ module.exports = {
401
432
  unwrapApiData,
402
433
  unwrapPublicationResult,
403
434
  isPublicationResultShape,
435
+ classifyDatasourceTierADetail,
404
436
  classifyDatasourceTierA,
437
+ formatTierAPartialHint,
405
438
  summarizeDatasourceTiersA,
406
439
  aggregateVerdictFromCounts,
407
440
  classifyDatasourceTierB,
@@ -83,7 +83,7 @@ function logDeployProbeDatasourceSection(probeData) {
83
83
  * @param {Object|null} systemFromDataplane
84
84
  * @param {boolean} genMcp
85
85
  */
86
- function logDeployContractsSection(systemFromDataplane, genMcp) {
86
+ function logDeployContractsSection(systemFromDataplane, genMcp, dataplaneUrl, systemKey) {
87
87
  if (!systemFromDataplane) return;
88
88
  logSeparator();
89
89
  logSectionTitle('Contracts:');
@@ -94,7 +94,7 @@ function logDeployContractsSection(systemFromDataplane, genMcp) {
94
94
  } else {
95
95
  logger.log(chalk.gray('○ OpenAPI docs URL not available'));
96
96
  }
97
- logDocsBlock(systemFromDataplane);
97
+ logDocsBlock(systemFromDataplane, { dataplaneUrl, systemKey, genMcp: mcpOk });
98
98
  }
99
99
 
100
100
  /**
@@ -260,7 +260,7 @@ function logDeployReadinessSummary(ctx) {
260
260
 
261
261
  logDeployIdentityAndCredentialBlocks(systemCfg, !!probeData);
262
262
 
263
- logDeployContractsSection(systemFromDataplane, genMcp);
263
+ logDeployContractsSection(systemFromDataplane, genMcp, dataplaneUrl, systemKey);
264
264
  logDeployNextActionsSection(systemKey, probeData, summary, genMcp);
265
265
  }
266
266
 
@@ -9,7 +9,11 @@
9
9
  const chalk = require('chalk');
10
10
  const { failureGlyph, successGlyph } = require('./cli-test-layout-chalk');
11
11
  const logger = require('./logger');
12
- const { extractIdentitySummary, resolveCredentialTestEndpointDisplay } = require('./external-system-readiness-core');
12
+ const {
13
+ extractIdentitySummary,
14
+ resolveCredentialTestEndpointDisplay,
15
+ formatTierAPartialHint
16
+ } = require('./external-system-readiness-core');
13
17
 
14
18
  const SEP = chalk.gray('────────────────────────────────');
15
19
 
@@ -56,8 +60,12 @@ function logDatasourceTable(rows, counts, title) {
56
60
  logSectionTitle(title && String(title).trim() ? String(title).trim() : 'Datasources:');
57
61
  for (const r of rows) {
58
62
  const statusLabel = r.tier === 'ready' ? 'Ready' : r.tier === 'failed' ? 'Failed' : 'Partial';
63
+ const hint =
64
+ r.tier === 'partial' && r.partialReason
65
+ ? chalk.gray(` — ${formatTierAPartialHint(r.partialReason)}`)
66
+ : '';
59
67
  logger.log(
60
- `${tierGlyph(r.tier)} ${r.key.padEnd(14, ' ')} ${chalk.gray('(' + statusLabel + ')')}`
68
+ `${tierGlyph(r.tier)} ${r.key.padEnd(14, ' ')} ${chalk.gray('(' + statusLabel + ')')}${hint}`
61
69
  );
62
70
  }
63
71
  logger.log('');
@@ -120,14 +128,35 @@ function logNextActions(actions, extraLine) {
120
128
  }
121
129
  }
122
130
 
131
+ /**
132
+ * Dataplane serves MCP OpenAPI docs at /api/v1/mcp/{systemKey}/docs.
133
+ *
134
+ * @param {Object} sys - ExternalSystemResponse
135
+ * @param {{ dataplaneUrl?: string, systemKey?: string, genMcp?: boolean }} [opts]
136
+ * @returns {string|null}
137
+ */
138
+ function deriveMcpDocsPageUrl(sys, opts) {
139
+ if (!sys || !opts) return null;
140
+ if (sys.mcpDocsPageUrl) return String(sys.mcpDocsPageUrl);
141
+ const { dataplaneUrl, systemKey, genMcp } = opts;
142
+ if (!genMcp || !dataplaneUrl || !systemKey) return null;
143
+ if (!sys.mcpServerUrl) return null;
144
+ if (!sys.openApiDocsPageUrl && !sys.apiDocumentUrl) return null;
145
+ const base = String(dataplaneUrl).replace(/\/+$/, '');
146
+ return `${base}/api/v1/mcp/${encodeURIComponent(systemKey)}/docs`;
147
+ }
148
+
123
149
  /**
124
150
  * @param {Object} sys - ExternalSystemResponse
151
+ * @param {{ dataplaneUrl?: string, systemKey?: string, genMcp?: boolean }} [opts]
125
152
  */
126
- function logDocsBlock(sys) {
153
+ function logDocsBlock(sys, opts) {
127
154
  if (!sys) return;
128
155
  const urls = [];
129
156
  if (sys.openApiDocsPageUrl) urls.push({ label: 'OpenAPI Docs Page', url: sys.openApiDocsPageUrl });
130
157
  if (sys.apiDocumentUrl) urls.push({ label: 'API Docs', url: sys.apiDocumentUrl });
158
+ const mcpDocs = deriveMcpDocsPageUrl(sys, opts);
159
+ if (mcpDocs) urls.push({ label: 'MCP Docs Page', url: mcpDocs });
131
160
  if (sys.mcpServerUrl) urls.push({ label: 'MCP Server', url: sys.mcpServerUrl });
132
161
  if (urls.length === 0) return;
133
162
  logSeparator();
@@ -147,5 +176,6 @@ module.exports = {
147
176
  logIdentityBlock,
148
177
  logCredentialIntentBlock,
149
178
  logNextActions,
179
+ deriveMcpDocsPageUrl,
150
180
  logDocsBlock
151
181
  };
@@ -44,7 +44,16 @@ function logPublishResultBlock(publication) {
44
44
  }
45
45
  logSeparator();
46
46
  logSectionTitle('MCP Contract:');
47
- logger.log(genMcp ? formatSuccessLine('Generated') : chalk.gray('○ Not requested (generateMcpContract false)'));
47
+ if (genMcp) {
48
+ logger.log(formatSuccessLine('Generation requested (manifest generateMcpContract)'));
49
+ logger.log(
50
+ chalk.gray(
51
+ ' Per-datasource MCP appears when dataplane stores mcpContract (OpenAPI resolves + generation succeeds).'
52
+ )
53
+ );
54
+ } else {
55
+ logger.log(chalk.gray('○ Not requested (generateMcpContract false)'));
56
+ }
48
57
  }
49
58
 
50
59
  /**
@@ -33,10 +33,10 @@ async function validateFileExists(filePath) {
33
33
  * @param {Object} additionalFields - Additional fields
34
34
  * @returns {Promise<FormData>} FormData object
35
35
  */
36
- async function buildFormData(filePath, fieldName, additionalFields) {
36
+ async function buildFormData(filePath, fieldName, additionalFields, opts = {}) {
37
37
  const formData = new FormData();
38
38
  const fileContent = await fs.readFile(filePath);
39
- const fileName = path.basename(filePath);
39
+ const fileName = opts.filenameOverride ? String(opts.filenameOverride) : path.basename(filePath);
40
40
  const fileBlob = new Blob([fileContent], { type: 'application/octet-stream' });
41
41
  formData.append(fieldName, fileBlob, fileName);
42
42
 
@@ -74,6 +74,43 @@ async function uploadFile(url, filePath, fieldName = 'file', authConfig = {}, ad
74
74
  return await client.postFormData(endpointPath, formData);
75
75
  }
76
76
 
77
+ /**
78
+ * Upload a file using multipart/form-data but override the filename sent to the server.
79
+ * Useful when the server derives a key from the upload filename.
80
+ *
81
+ * @async
82
+ * @function uploadFileAs
83
+ * @param {string} url - Full API endpoint URL
84
+ * @param {string} filePath - Path to file to upload
85
+ * @param {string} filenameOverride - Filename to present to server (e.g. 'my-key.json')
86
+ * @param {string} fieldName - Form field name for the file (default: 'file')
87
+ * @param {Object} [authConfig] - Authentication configuration
88
+ * @param {Object} [additionalFields] - Additional form fields to include
89
+ * @returns {Promise<Object>} API response
90
+ */
91
+ async function uploadFileAs(
92
+ url,
93
+ filePath,
94
+ filenameOverride,
95
+ fieldName = 'file',
96
+ authConfig = {},
97
+ additionalFields = {}
98
+ ) {
99
+ await validateFileExists(filePath);
100
+ if (!filenameOverride || typeof filenameOverride !== 'string') {
101
+ throw new Error('filenameOverride is required and must be a string');
102
+ }
103
+
104
+ const parsed = new URL(url);
105
+ const baseUrl = parsed.origin;
106
+ const endpointPath = parsed.pathname + parsed.search;
107
+
108
+ const formData = await buildFormData(filePath, fieldName, additionalFields, { filenameOverride });
109
+ const client = new ApiClient(baseUrl, authConfig);
110
+ return await client.postFormData(endpointPath, formData);
111
+ }
112
+
77
113
  module.exports = {
78
- uploadFile
114
+ uploadFile,
115
+ uploadFileAs
79
116
  };
@@ -0,0 +1,107 @@
1
+ const chalk = require('chalk');
2
+ const { formatSuccessLine } = require('./cli-test-layout-chalk');
3
+ const logger = require('./logger');
4
+ const { execWithDockerEnv } = require('./docker-exec');
5
+
6
+ /**
7
+ * Checks if db-init container exists.
8
+ * @async
9
+ * @param {string} dbInitContainer
10
+ * @returns {Promise<boolean>}
11
+ */
12
+ async function checkDbInitContainerExists(dbInitContainer) {
13
+ try {
14
+ const { stdout } = await execWithDockerEnv(
15
+ `docker ps -a --filter "name=${dbInitContainer}" --format "{{.Names}}"`
16
+ );
17
+ return stdout.trim() === dbInitContainer;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Gets container exit code.
25
+ * @async
26
+ * @param {string} dbInitContainer
27
+ * @returns {Promise<string>}
28
+ */
29
+ async function getContainerExitCode(dbInitContainer) {
30
+ const { stdout: exitCode } = await execWithDockerEnv(
31
+ `docker inspect --format='{{.State.ExitCode}}' ${dbInitContainer}`
32
+ );
33
+ return exitCode.trim();
34
+ }
35
+
36
+ /**
37
+ * Handles exited container status.
38
+ * @async
39
+ * @param {string} dbInitContainer
40
+ * @returns {Promise<boolean>}
41
+ */
42
+ async function handleExitedContainer(dbInitContainer) {
43
+ const { stdout: status } = await execWithDockerEnv(
44
+ `docker inspect --format='{{.State.Status}}' ${dbInitContainer}`
45
+ );
46
+ if (status.trim() === 'exited') {
47
+ const exitCode = await getContainerExitCode(dbInitContainer);
48
+ if (exitCode === '0') {
49
+ logger.log(formatSuccessLine('Database initialization already completed'));
50
+ } else {
51
+ logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
52
+ }
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+
58
+ /**
59
+ * Waits for container to exit (best-effort).
60
+ * @async
61
+ * @param {string} dbInitContainer
62
+ * @param {number} maxAttempts
63
+ * @returns {Promise<void>}
64
+ */
65
+ async function waitForContainerExit(dbInitContainer, maxAttempts) {
66
+ for (let attempts = 0; attempts < maxAttempts; attempts++) {
67
+ const { stdout: currentStatus } = await execWithDockerEnv(
68
+ `docker inspect --format='{{.State.Status}}' ${dbInitContainer}`
69
+ );
70
+ if (currentStatus.trim() === 'exited') {
71
+ const exitCode = await getContainerExitCode(dbInitContainer);
72
+ if (exitCode === '0') {
73
+ logger.log(formatSuccessLine('Database initialization completed'));
74
+ } else {
75
+ logger.log(chalk.yellow(`⚠ Database initialization exited with code ${exitCode}`));
76
+ }
77
+ return;
78
+ }
79
+ await new Promise(resolve => setTimeout(resolve, 1000));
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Checks if db-init container exists and waits for it to complete.
85
+ * @async
86
+ * @param {string} appName
87
+ * @returns {Promise<void>}
88
+ */
89
+ async function waitForDbInit(appName) {
90
+ const dbInitContainer = `aifabrix-${appName}-db-init`;
91
+ try {
92
+ if (!(await checkDbInitContainerExists(dbInitContainer))) {
93
+ return;
94
+ }
95
+
96
+ if (await handleExitedContainer(dbInitContainer)) {
97
+ return;
98
+ }
99
+
100
+ logger.log(chalk.blue('Waiting for database initialization to complete...'));
101
+ await waitForContainerExit(dbInitContainer, 30);
102
+ } catch {
103
+ // db-init container might not exist, which is fine
104
+ }
105
+ }
106
+
107
+ module.exports = { waitForDbInit };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Post-success warnings when public/Traefik health URL was skipped or not verified.
3
+ *
4
+ * @fileoverview Split from health-check.js for file size limits
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const chalk = require('chalk');
10
+ const logger = require('./logger');
11
+
12
+ /**
13
+ * When Traefik URL is set, drop it if the hostname does not resolve; retain full URL for UX.
14
+ *
15
+ * @param {string} traefikUrl
16
+ * @param {boolean} debug
17
+ * @param {Function} isHostnameResolvableFn - (hostname, debug) => Promise<boolean>
18
+ * @returns {Promise<{ traefikUrl: string, skippedPublicHealthUrl: string }>}
19
+ */
20
+ async function filterTraefikUrlByDns(traefikUrl, debug, isHostnameResolvableFn) {
21
+ if (!traefikUrl) {
22
+ return { traefikUrl: '', skippedPublicHealthUrl: '' };
23
+ }
24
+ try {
25
+ const hn = new URL(traefikUrl).hostname;
26
+ const ok = await isHostnameResolvableFn(hn, debug);
27
+ if (!ok) {
28
+ return { traefikUrl: '', skippedPublicHealthUrl: traefikUrl };
29
+ }
30
+ } catch {
31
+ return { traefikUrl: '', skippedPublicHealthUrl: '' };
32
+ }
33
+ return { traefikUrl, skippedPublicHealthUrl: '' };
34
+ }
35
+
36
+ /**
37
+ * @param {Object} p
38
+ * @param {string} [p.skippedPublicHealthUrl]
39
+ * @param {string[]} [p.urlsToTry]
40
+ * @param {number} p.resolvedIndex
41
+ */
42
+ function logPublicHealthUrlWarningIfNeeded(p) {
43
+ const { skippedPublicHealthUrl, urlsToTry, resolvedIndex } = p;
44
+ if (typeof resolvedIndex !== 'number' || resolvedIndex < 0) return;
45
+
46
+ if (skippedPublicHealthUrl) {
47
+ logger.log(
48
+ chalk.yellow(
49
+ `⚠ Public URL was not verified (DNS): ${skippedPublicHealthUrl}. ` +
50
+ 'The application reported healthy via localhost only. Validate DNS names and Traefik routing for this host.'
51
+ )
52
+ );
53
+ return;
54
+ }
55
+ if (Array.isArray(urlsToTry) && urlsToTry.length > 1 && resolvedIndex > 0) {
56
+ const pub = urlsToTry[0];
57
+ logger.log(
58
+ chalk.yellow(
59
+ `⚠ Public URL was not verified: ${pub}. ` +
60
+ 'Health checks succeeded via localhost only. Validate Traefik routing, TLS, and DNS.'
61
+ )
62
+ );
63
+ }
64
+ }
65
+
66
+ module.exports = {
67
+ filterTraefikUrlByDns,
68
+ logPublicHealthUrlWarningIfNeeded
69
+ };
@@ -73,7 +73,16 @@ function frontDoorPattern(appConfig) {
73
73
  * @param {Object|null} appConfig
74
74
  * @returns {Promise<string|null>}
75
75
  */
76
- async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig) {
76
+ /**
77
+ * Public app URL (Traefik + frontDoor mount path), without the health path — for CLI summaries.
78
+ *
79
+ * @async
80
+ * @param {string} appName
81
+ * @param {number} healthCheckPort
82
+ * @param {Object|null} appConfig
83
+ * @returns {Promise<string|null>}
84
+ */
85
+ async function computeTraefikPublicAppUrl(_appName, _healthCheckPort, appConfig) {
77
86
  if (!frontDoorEnabled(appConfig)) return null;
78
87
  const pattern = frontDoorPattern(appConfig);
79
88
  if (!pattern) return null;
@@ -87,7 +96,7 @@ async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig)
87
96
  const developerIdRaw = await coreConfig.getDeveloperId();
88
97
  const developerIdNum = parseDeveloperIdNum(developerIdRaw);
89
98
 
90
- // Health checks originate from the CLI on the host, not from inside a container.
99
+ // URLs are resolved for the CLI on the host, not from inside a container.
91
100
  const profile = 'local';
92
101
  const fd = appConfig.frontDoorRouting;
93
102
  const listenPort = Number(appConfig?.port || 3000);
@@ -105,15 +114,21 @@ async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig)
105
114
  infraTlsEnabled
106
115
  });
107
116
 
108
- const healthCheckPath = appConfig?.healthCheck?.path || '/health';
109
117
  const mountPath = normalizeFrontDoorPatternForHealth(pattern);
110
- const baseWithFrontDoor = joinUrlPath(publicBase, mountPath);
118
+ return joinUrlPath(publicBase, mountPath);
119
+ }
120
+
121
+ async function computeTraefikHealthCheckUrl(appName, healthCheckPort, appConfig) {
122
+ const baseWithFrontDoor = await computeTraefikPublicAppUrl(appName, healthCheckPort, appConfig);
123
+ if (!baseWithFrontDoor) return null;
124
+ const healthCheckPath = appConfig?.healthCheck?.path || '/health';
111
125
  return joinUrlPath(baseWithFrontDoor, healthCheckPath);
112
126
  }
113
127
 
114
128
  module.exports = {
115
129
  joinUrlPath,
116
130
  normalizeFrontDoorPatternForHealth,
131
+ computeTraefikPublicAppUrl,
117
132
  computeTraefikHealthCheckUrl
118
133
  };
119
134