@aifabrix/builder 2.54.1 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +130 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +239 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/state-file.js +83 -0
  23. package/lib/agent-kit/status.js +50 -0
  24. package/lib/agent-kit/v1-detect.js +81 -0
  25. package/lib/agent-kit/validate-kit.js +87 -0
  26. package/lib/agent-kit/workspace-name.js +40 -0
  27. package/lib/agent-kit/workspace-skeleton.js +103 -0
  28. package/lib/api/role-assistant-test-pack.api.js +86 -0
  29. package/lib/api/work-search.api.js +31 -4
  30. package/lib/api/work.api.js +19 -12
  31. package/lib/app/list-external-systems.js +2 -1
  32. package/lib/app/show-display.js +33 -6
  33. package/lib/app/show.js +25 -2
  34. package/lib/channels/add-channel.js +1 -0
  35. package/lib/channels/approval-guides/claude.js +2 -2
  36. package/lib/channels/channel-add-failure.js +1 -0
  37. package/lib/channels/channel-add-remaining.js +15 -9
  38. package/lib/channels/channel-definition-status.js +115 -0
  39. package/lib/channels/channel-description.js +2 -1
  40. package/lib/channels/channel-install-envelope.js +4 -2
  41. package/lib/channels/channel-integration-generator.js +2 -0
  42. package/lib/channels/channel-runtime-resolver.js +14 -0
  43. package/lib/channels/channel-targets.js +31 -5
  44. package/lib/channels/claude-setup-values.js +4 -1
  45. package/lib/channels/slack-channel-add.js +59 -6
  46. package/lib/channels/slack-mcp-callback.js +51 -0
  47. package/lib/channels/slack-mcp-runtime.js +156 -0
  48. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  49. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  50. package/lib/cli/index.js +4 -0
  51. package/lib/cli/setup-certification-commands.js +2 -2
  52. package/lib/cli/setup-certification.help.js +3 -1
  53. package/lib/commands/agent-kit-help.js +79 -0
  54. package/lib/commands/agent-kit.js +346 -0
  55. package/lib/commands/channel-add-help.js +5 -5
  56. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  57. package/lib/commands/repair-auth-apply.js +1 -1
  58. package/lib/commands/repair-datasource-key-skip.js +39 -0
  59. package/lib/commands/repair-datasource-keys.js +24 -12
  60. package/lib/commands/repair-rbac.js +16 -10
  61. package/lib/commands/role-assistant-copy-output.js +61 -0
  62. package/lib/commands/role-assistant-copy.js +155 -0
  63. package/lib/commands/role-assistant-test-output.js +8 -9
  64. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  65. package/lib/commands/role-assistant.js +117 -48
  66. package/lib/commands/search.js +143 -0
  67. package/lib/commands/test-trust-external.js +7 -5
  68. package/lib/commands/upload-sidecar-followups.js +4 -1
  69. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  70. package/lib/commands/verify-operations-step-plan.js +6 -33
  71. package/lib/commands/verify-operations-steps.js +3 -1
  72. package/lib/datasource/capability/relate-operations.js +28 -10
  73. package/lib/datasource/capability/run-capability-relate.js +48 -25
  74. package/lib/datasource/datasource-identifier.js +13 -5
  75. package/lib/deployment/deployer-poll-run.js +117 -35
  76. package/lib/external-system/deploy-dataplane-published.js +70 -0
  77. package/lib/external-system/deploy.js +12 -2
  78. package/lib/external-system/download-role-assistant.js +41 -15
  79. package/lib/external-system/download.js +7 -1
  80. package/lib/generator/wizard-datasource-key.js +54 -0
  81. package/lib/generator/wizard.js +55 -54
  82. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  83. package/lib/integration-definition/apply-relationships.js +195 -0
  84. package/lib/integration-definition/apply-static-enterprise.js +229 -0
  85. package/lib/integration-definition/apply-system-file.js +71 -0
  86. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  87. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  88. package/lib/integration-definition/apply.js +78 -59
  89. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  90. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  91. package/lib/lifecycle/role-assistant-governance.js +56 -21
  92. package/lib/programmatic/capability-registry.js +1 -0
  93. package/lib/programmatic/index.js +2 -0
  94. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  95. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  96. package/lib/programmatic/openapi-component-schemas.js +1 -0
  97. package/lib/programmatic/openapi-descriptions.js +9 -3
  98. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  99. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  100. package/lib/programmatic/openapi-request-schemas.js +5 -0
  101. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  102. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  103. package/lib/programmatic/openapi-spec.js +1 -0
  104. package/lib/programmatic/request-timeout.js +3 -0
  105. package/lib/programmatic/route-handler-map.js +7 -0
  106. package/lib/programmatic/run-channel-catalog.js +50 -8
  107. package/lib/programmatic/run-channel-install-options.js +5 -1
  108. package/lib/programmatic/run-download.js +41 -0
  109. package/lib/protection/auth-context.js +3 -1
  110. package/lib/protection/protection-display.js +3 -2
  111. package/lib/protection/report-exit.js +50 -0
  112. package/lib/protection/run-commands.js +2 -0
  113. package/lib/role-assistant/package-validate.js +175 -0
  114. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  115. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  116. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  117. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  118. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  119. package/lib/role-assistant/test-case-copy.js +119 -0
  120. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  121. package/lib/role-assistant/test-case-validate.js +88 -0
  122. package/lib/role-assistant/test-cases-bid.js +117 -4
  123. package/lib/role-assistant/test-cases-evaluate.js +137 -30
  124. package/lib/role-assistant/test-cases-honesty.js +155 -6
  125. package/lib/role-assistant/test-cases-lifecycle.js +291 -0
  126. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  127. package/lib/role-assistant/test-cases-search.js +20 -2
  128. package/lib/role-assistant/test-cases.js +34 -168
  129. package/lib/role-assistant/test-pack-map.js +180 -0
  130. package/lib/role-assistant/test-pack-sync.js +187 -0
  131. package/lib/role-assistant/test-runner-debug.js +54 -0
  132. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  133. package/lib/role-assistant/test-runner-search.js +20 -5
  134. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  135. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  136. package/lib/role-assistant/test-runner-workhub.js +45 -21
  137. package/lib/role-assistant/test-runner.js +50 -10
  138. package/lib/schema/external-datasource.schema.json +36 -5
  139. package/lib/schema/external-system.schema.json +20 -2
  140. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  141. package/lib/search/dataplane-context.js +23 -0
  142. package/lib/search/search-args.js +211 -0
  143. package/lib/search/search-display.js +238 -0
  144. package/lib/search/search-errors.js +50 -0
  145. package/lib/search/search-prompts.js +119 -0
  146. package/lib/search/search-roles.js +85 -0
  147. package/lib/search/search-run.js +241 -0
  148. package/lib/search/search-skills.js +198 -0
  149. package/lib/utils/authentication-slot-show.js +153 -0
  150. package/lib/utils/credential-display.js +43 -1
  151. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  152. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  153. package/lib/utils/external-system-validators.js +2 -1
  154. package/lib/utils/help-builder.js +3 -1
  155. package/lib/utils/schema-loader.js +37 -0
  156. package/lib/validation/validate-external-file.js +5 -1
  157. package/lib/validation/validate.js +3 -2
  158. package/package.json +1 -1
  159. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  160. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  161. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  162. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  163. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  164. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  165. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  166. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  167. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  168. package/templates/agent-kit/agent-kit.yaml +45 -0
  169. package/templates/agent-kit/instructions/AGENTKIT.md +83 -0
  170. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  171. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  172. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  173. package/templates/agent-kit/parity-inventory.yaml +188 -0
  174. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +58 -0
  175. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  176. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  177. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  178. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  179. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +51 -0
  180. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  181. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  182. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  183. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +50 -0
  184. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  185. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  186. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  187. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +42 -0
  188. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  189. package/templates/agent-kit/skills/shared/hosts.md +78 -0
  190. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  191. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  192. package/templates/agent-kit/workspace/README.md +89 -0
  193. package/templates/applications/builder-api/Dockerfile +3 -1
  194. package/templates/applications/dataplane/application.yaml +2 -2
  195. package/templates/applications/dataplane/env.template +1 -1
  196. package/templates/applications/dataplane/rbac.yaml +28 -0
  197. package/templates/applications/keycloak/env.template +3 -3
  198. package/templates/applications/miso-controller/application.yaml +1 -1
  199. package/templates/applications/miso-controller/env.template +8 -9
  200. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  201. package/templates/channels/claude/channel.json +1 -1
  202. package/templates/channels/claude/install.template.md.hbs +1 -0
  203. package/templates/channels/claude/integration/claude-system.json +1 -1
  204. package/templates/channels/slack/channel.json +11 -0
  205. package/templates/channels/slack/install.template.md.hbs +9 -0
  206. package/templates/channels/slack/integration/slack-system.json +11 -0
  207. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  208. package/templates/channels/teams-bot/channel.json +1 -0
  209. package/templates/python/docker-compose.hbs +14 -0
  210. package/templates/typescript/docker-compose.hbs +14 -0
@@ -31,6 +31,7 @@ const { syncDeployJsonFromSources } = require('./sync-deploy-manifest');
31
31
  const { runDeployGateOrchestrator } = require('../commands/deploy-gate-orchestrator');
32
32
  const { runPostPublishDataSync } = require('../commands/run-post-publish-data-sync');
33
33
  const { uploadDigitalWorkerIfNeeded, deployRoleAssistantShellIfNeeded } = require('./deploy-helpers');
34
+ const { createDataplanePublishedCheck } = require('./deploy-dataplane-published');
34
35
 
35
36
  /**
36
37
  * Validates manifest on dataplane (dry-run) before controller deploy.
@@ -194,9 +195,10 @@ const EXTERNAL_DEPLOY_POLL_INTERVAL_MS = 2000;
194
195
  /** 300 × 2s ≈ 10 minutes — must cover controller publish-job max wait with gates. */
195
196
  const EXTERNAL_DEPLOY_POLL_MAX_ATTEMPTS = 300;
196
197
 
197
- function buildExternalDeployPollOpts(options) {
198
+ function buildExternalDeployPollOpts(options, extra = {}) {
198
199
  return {
199
200
  ...options,
201
+ ...extra,
200
202
  poll: true,
201
203
  pollInterval:
202
204
  options.pollInterval !== undefined ? options.pollInterval : EXTERNAL_DEPLOY_POLL_INTERVAL_MS,
@@ -300,7 +302,15 @@ async function executeDeployAndDisplay(manifest, controllerUrl, environment, aut
300
302
  controllerUrl,
301
303
  environment,
302
304
  authConfig,
303
- buildExternalDeployPollOpts(options)
305
+ buildExternalDeployPollOpts(options, {
306
+ isDataplanePublished: createDataplanePublishedCheck({
307
+ controllerUrl,
308
+ environment,
309
+ authConfig,
310
+ systemKey: manifest.key,
311
+ dataplaneUrl: options.knownDataplaneUrl
312
+ })
313
+ })
304
314
  );
305
315
 
306
316
  const deploymentOutcome = parseControllerDeploymentOutcome(result);
@@ -24,13 +24,23 @@ const {
24
24
  isPlatformShellKind
25
25
  } = require('../utils/platform-shell-package');
26
26
 
27
- const CONNECTED_SYSTEM_DRY_RUN_FILES = [
28
- 'deploy.json',
29
- 'application.yaml',
30
- 'system.yaml',
31
- 'env.template',
32
- 'README.md'
33
- ];
27
+ function connectedSystemDryRunFileNames(systemKey, dataSources, format) {
28
+ const { getExternalDatasourceFileName } = require('../generator/split-variables');
29
+ const ext = format === 'json' ? 'json' : 'yaml';
30
+ const names = [
31
+ `${systemKey}-deploy.json`,
32
+ `application.${ext}`,
33
+ `${systemKey}-system.${ext}`,
34
+ 'env.template',
35
+ 'README.md'
36
+ ];
37
+ const list = Array.isArray(dataSources) ? dataSources : [];
38
+ for (let i = 0; i < list.length; i += 1) {
39
+ const yamlName = getExternalDatasourceFileName(systemKey, list[i], i);
40
+ names.push(ext === 'json' ? yamlName.replace(/\.yaml$/i, '.json') : yamlName);
41
+ }
42
+ return names;
43
+ }
34
44
 
35
45
  async function maybeExportRoleAssistantEvidenceArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options) {
36
46
  try {
@@ -47,6 +57,23 @@ async function maybeExportRoleAssistantEvidenceArtifacts(systemKey, manifest, da
47
57
  }
48
58
  }
49
59
 
60
+ async function maybeExportRoleAssistantTestsArtifacts(systemKey, dataplaneUrl, authConfig, options) {
61
+ try {
62
+ const { exportRoleAssistantTests } = require('../role-assistant/test-pack-sync');
63
+ const testsResult = await exportRoleAssistantTests({
64
+ systemKey,
65
+ dataplaneUrl,
66
+ authConfig,
67
+ options
68
+ });
69
+ if (testsResult && testsResult.skipped) {
70
+ logger.log(formatWarningLine(`Role assistant tests export skipped: ${testsResult.reason}`));
71
+ }
72
+ } catch (testsErr) {
73
+ logger.log(formatWarningLine(`Role assistant tests export skipped: ${testsErr.message}`));
74
+ }
75
+ }
76
+
50
77
  async function maybeExportRoleAssistantKnowledgeArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options) {
51
78
  try {
52
79
  const knowledgeResult = await exportRoleAssistantKnowledge({
@@ -67,19 +94,17 @@ async function runRoleAssistantDownloadSidecars(params) {
67
94
  const { systemKey, manifest, dataplaneUrl, authConfig, options = {} } = params;
68
95
  await maybeExportRoleAssistantEvidenceArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options);
69
96
  await maybeExportRoleAssistantKnowledgeArtifacts(systemKey, manifest, dataplaneUrl, authConfig, options);
97
+ await maybeExportRoleAssistantTestsArtifacts(systemKey, dataplaneUrl, authConfig, options);
70
98
  }
71
99
 
72
- function logConnectedSystemDryRunFiles(systemKey) {
100
+ function logConnectedSystemDryRunFiles(systemKey, dataSources, format) {
73
101
  logger.log(metadata('Would create:'));
74
- CONNECTED_SYSTEM_DRY_RUN_FILES.forEach((name) => {
75
- const fileName = name === 'deploy.json' || name === 'system.yaml'
76
- ? `${systemKey}-${name}`
77
- : name;
102
+ connectedSystemDryRunFileNames(systemKey, dataSources, format).forEach((fileName) => {
78
103
  logger.log(metadata(` - integration/${systemKey}/${fileName}`));
79
104
  });
80
105
  }
81
106
 
82
- function handleDownloadDryRun(systemKey, dataplaneUrl, application) {
107
+ function handleDownloadDryRun(systemKey, dataplaneUrl, application, dataSources, format) {
83
108
  logger.log(formatWarningLine('Dry run mode: no files written.'));
84
109
  logger.log(metadata(`Would fetch: ${dataplaneUrl}/api/v1/external/systems/${systemKey}/config`));
85
110
  if (isEnterpriseKnowledgeKind(application)) {
@@ -91,7 +116,7 @@ function handleDownloadDryRun(systemKey, dataplaneUrl, application) {
91
116
  logger.log(metadata(` - role-assistant/${systemKey}/role-assistant.yaml`));
92
117
  return;
93
118
  }
94
- logConnectedSystemDryRunFiles(systemKey);
119
+ logConnectedSystemDryRunFiles(systemKey, dataSources, format);
95
120
  }
96
121
 
97
122
  async function downloadDigitalWorkerPackage(params) {
@@ -130,5 +155,6 @@ async function downloadPlatformShellIfNeeded(params) {
130
155
  module.exports = {
131
156
  runRoleAssistantDownloadSidecars,
132
157
  downloadPlatformShellIfNeeded,
133
- handleDownloadDryRun
158
+ handleDownloadDryRun,
159
+ connectedSystemDryRunFileNames
134
160
  };
@@ -421,7 +421,13 @@ async function downloadExternalSystem(systemKey, options = {}) {
421
421
  );
422
422
  const manifest = await downloadFullManifest(dataplaneUrl, systemKey, authConfig, options);
423
423
  if (options.dryRun) {
424
- handleDownloadDryRun(systemKey, dataplaneUrl, manifest.application);
424
+ handleDownloadDryRun(
425
+ systemKey,
426
+ dataplaneUrl,
427
+ manifest.application,
428
+ manifest.dataSources,
429
+ format
430
+ );
425
431
  return;
426
432
  }
427
433
  const platformHandled = await downloadPlatformShellIfNeeded({
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Canonical kebab datasource keys for wizard generate-config.
3
+ *
4
+ * @fileoverview Wizard datasource key segments
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ /**
10
+ * @param {string} str
11
+ * @returns {string}
12
+ */
13
+ function toKeySegment(str) {
14
+ if (!str || typeof str !== 'string') {
15
+ return 'default';
16
+ }
17
+ const withHyphens = str.replace(/([A-Z])/g, '-$1').toLowerCase();
18
+ const sanitized = withHyphens.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
19
+ return sanitized || 'default';
20
+ }
21
+
22
+ /**
23
+ * @param {string} finalSystemKey
24
+ * @param {{ key?: string, entityName?: string, entityType?: string, entityKey?: string }} ds
25
+ * @param {string} originalPrefix
26
+ * @param {Set<string>} [usedKeys]
27
+ * @returns {string}
28
+ */
29
+ function wizardCanonicalDatasourceKey(finalSystemKey, ds, originalPrefix, usedKeys) {
30
+ const dsKey = String(ds.key || '').trim();
31
+ let segment;
32
+ if (dsKey && originalPrefix && dsKey.startsWith(originalPrefix)) {
33
+ segment = toKeySegment(dsKey.substring(originalPrefix.length));
34
+ } else {
35
+ const raw = ds.entityName || dsKey || ds.entityType || ds.entityKey || 'default';
36
+ segment = toKeySegment(raw);
37
+ }
38
+ let candidate = segment.includes('-') && !segment.startsWith(`${finalSystemKey}-`)
39
+ ? segment
40
+ : `${finalSystemKey}-${segment}`;
41
+ if (usedKeys && usedKeys.has(candidate)) {
42
+ let suffix = 2;
43
+ while (usedKeys.has(`${candidate}-${suffix}`)) {
44
+ suffix += 1;
45
+ }
46
+ candidate = `${candidate}-${suffix}`;
47
+ }
48
+ return candidate;
49
+ }
50
+
51
+ module.exports = {
52
+ toKeySegment,
53
+ wizardCanonicalDatasourceKey
54
+ };
@@ -17,19 +17,7 @@ const { loadConfigFile, writeConfigFile } = require('../utils/config-format');
17
17
  const { systemKeyToKvPrefix, securityKeyToVar, isValidKvPath } = require('../utils/credential-secrets-env');
18
18
  const { generateExternalEnvTemplateContent } = require('../utils/external-env-template');
19
19
  const { generateReadme } = require('./wizard-readme');
20
-
21
- /**
22
- * Converts a string to a schema-valid key segment (lowercase letters, numbers, hyphens only).
23
- * e.g. "recordStorage" -> "record-storage", "documentStorage" -> "document-storage"
24
- * @param {string} str - Raw entity type or key segment (may be camelCase)
25
- * @returns {string} Segment matching ^[a-z0-9-]+$
26
- */
27
- function toKeySegment(str) {
28
- if (!str || typeof str !== 'string') return 'default';
29
- const withHyphens = str.replace(/([A-Z])/g, '-$1').toLowerCase();
30
- const sanitized = withHyphens.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
31
- return sanitized || 'default';
32
- }
20
+ const { toKeySegment, wizardCanonicalDatasourceKey } = require('./wizard-datasource-key');
33
21
 
34
22
  const { normalizeAuthenticationToSystemKey } = require('../utils/authentication-shape');
35
23
 
@@ -118,10 +106,46 @@ async function writeDatasourceYamlFiles(appPath, finalSystemKey, datasourceConfi
118
106
  * @param {string} [params.aiGeneratedReadme] - Optional AI-generated README content
119
107
  * @returns {Promise<Object>} Object with file paths
120
108
  */
121
- async function generateConfigFilesForWizard(params) {
122
- const { appPath, appName, finalSystemKey, systemFileName, datasourceFileNames, systemConfig, datasourceConfigs, aiGeneratedReadme, format } = params;
109
+ async function writeWizardEnvTemplate(appPath, systemConfig) {
110
+ const envTemplatePath = path.join(appPath, 'env.template');
111
+ const envTemplateContent = generateExternalEnvTemplateContent(systemConfig);
112
+ await fs.writeFile(envTemplatePath, envTemplateContent, 'utf8');
113
+ logger.log(formatSuccessLine('Generated env.template'));
114
+ try {
115
+ const secretsEnsure = require('../core/secrets-ensure');
116
+ await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, {
117
+ emptyValuesForCredentials: true
118
+ });
119
+ } catch (err) {
120
+ if (err.code !== 'ENOENT') {
121
+ logger.warn(`Could not ensure integration placeholder secrets: ${err.message}`);
122
+ }
123
+ }
124
+ return envTemplatePath;
125
+ }
123
126
 
124
- // Generate or update application config with externalIntegration block
127
+ async function writeWizardDeployManifest(appPath, appName, finalSystemKey) {
128
+ const { generateControllerManifest, toDeployJsonShape } = require('./external-controller-manifest');
129
+ const manifest = await generateControllerManifest(appName, { appPath });
130
+ const deployJson = toDeployJsonShape(manifest);
131
+ const deployManifestPath = path.join(appPath, `${finalSystemKey}-deploy.json`);
132
+ await fs.writeFile(deployManifestPath, JSON.stringify(deployJson, null, 2), 'utf8');
133
+ logger.log(formatSuccessLine(`Generated deployment manifest: ${finalSystemKey}-deploy.json`));
134
+ return deployManifestPath;
135
+ }
136
+
137
+ async function generateConfigFilesForWizard(params) {
138
+ const {
139
+ appPath,
140
+ appName,
141
+ finalSystemKey,
142
+ systemFileName,
143
+ datasourceFileNames,
144
+ systemConfig,
145
+ datasourceConfigs,
146
+ aiGeneratedReadme,
147
+ format
148
+ } = params;
125
149
  const configPath = await generateOrUpdateVariablesYaml({
126
150
  appPath,
127
151
  appName,
@@ -131,21 +155,7 @@ async function generateConfigFilesForWizard(params) {
131
155
  systemConfig,
132
156
  format: format || 'yaml'
133
157
  });
134
-
135
- // Generate env.template with Authentication and Configuration sections (Handlebars)
136
- const envTemplatePath = path.join(appPath, 'env.template');
137
- const envTemplateContent = generateExternalEnvTemplateContent(systemConfig);
138
- await fs.writeFile(envTemplatePath, envTemplateContent, 'utf8');
139
- logger.log(formatSuccessLine('Generated env.template'));
140
-
141
- try {
142
- const secretsEnsure = require('../core/secrets-ensure');
143
- await secretsEnsure.ensureSecretsFromEnvTemplate(envTemplatePath, { emptyValuesForCredentials: true });
144
- } catch (err) {
145
- if (err.code !== 'ENOENT') logger.warn(`Could not ensure integration placeholder secrets: ${err.message}`);
146
- }
147
-
148
- // Generate README.md (use AI-generated content if available)
158
+ const envTemplatePath = await writeWizardEnvTemplate(appPath, systemConfig);
149
159
  await generateReadme({
150
160
  appPath,
151
161
  appName,
@@ -155,21 +165,16 @@ async function generateConfigFilesForWizard(params) {
155
165
  aiGeneratedContent: aiGeneratedReadme,
156
166
  format
157
167
  });
158
-
159
- // Generate deployment scripts
160
- const deployScripts = await generateDeployScripts(appPath, finalSystemKey, systemFileName, datasourceFileNames);
161
-
162
- // Generate deployment manifest (<systemKey>-deploy.json) using controller format
163
- const { generateControllerManifest, toDeployJsonShape } = require('./external-controller-manifest');
164
- const manifest = await generateControllerManifest(appName, { appPath });
165
- const deployJson = toDeployJsonShape(manifest);
166
- const deployManifestPath = path.join(appPath, `${finalSystemKey}-deploy.json`);
167
- await fs.writeFile(deployManifestPath, JSON.stringify(deployJson, null, 2), 'utf8');
168
- logger.log(formatSuccessLine(`Generated deployment manifest: ${finalSystemKey}-deploy.json`));
169
-
168
+ const deployScripts = await generateDeployScripts(
169
+ appPath,
170
+ finalSystemKey,
171
+ systemFileName,
172
+ datasourceFileNames
173
+ );
174
+ const deployManifestPath = await writeWizardDeployManifest(appPath, appName, finalSystemKey);
170
175
  return {
171
176
  variablesPath: configPath,
172
- envTemplatePath: path.join(appPath, 'env.template'),
177
+ envTemplatePath,
173
178
  readmePath: path.join(appPath, 'README.md'),
174
179
  applicationSchemaPath: deployManifestPath,
175
180
  ...deployScripts
@@ -190,16 +195,10 @@ async function prepareWizardContext(appName, systemConfig, datasourceConfigs) {
190
195
  appDisplayName
191
196
  );
192
197
  const originalPrefix = `${originalSystemKey}-`;
198
+ const usedKeys = new Set();
193
199
  const updatedDatasourceConfigs = datasourceConfigs.map(ds => {
194
- let newKey;
195
- const dsKey = ds.key || '';
196
- if (dsKey && dsKey.startsWith(originalPrefix)) {
197
- newKey = `${finalSystemKey}-${dsKey.substring(originalPrefix.length)}`;
198
- } else {
199
- const entityType = ds.entityType || ds.entityKey || dsKey.split('-').pop() || 'default';
200
- const keySegment = toKeySegment(entityType);
201
- newKey = `${finalSystemKey}-${keySegment}`;
202
- }
200
+ const newKey = wizardCanonicalDatasourceKey(finalSystemKey, ds, originalPrefix, usedKeys);
201
+ usedKeys.add(newKey);
203
202
  const entityType = ds.entityType || ds.entityKey || newKey.split('-').pop() || 'default';
204
203
  const entityDisplayName = String(entityType).charAt(0).toUpperCase() + String(entityType).slice(1).replace(/-/g, ' ');
205
204
  return { ...ds, key: newKey, systemKey: finalSystemKey, displayName: ds.displayName || `${appDisplayName} ${entityDisplayName}` };
@@ -481,6 +480,8 @@ const { generateDeployScripts } = require('./deploy-script');
481
480
 
482
481
  module.exports = {
483
482
  generateWizardFiles,
484
- generateDeployScripts
483
+ generateDeployScripts,
484
+ wizardCanonicalDatasourceKey,
485
+ toKeySegment
485
486
  };
486
487
 
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Canonical tenant-global datasource keys for BID apply.
3
+ *
4
+ * @fileoverview Prefix single-token keys; keep unique kebab keys
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { isScopedKebabKey } = require('../commands/repair-datasource-key-skip');
10
+
11
+ /**
12
+ * @param {string} dsKey
13
+ * @param {string} systemKey
14
+ * @returns {string}
15
+ */
16
+ function canonicalDatasourceKey(dsKey, systemKey) {
17
+ const key = String(dsKey || '').trim();
18
+ const sys = String(systemKey || '').trim();
19
+ if (!key) {
20
+ return key;
21
+ }
22
+ if (sys && key.startsWith(`${sys}-`)) {
23
+ return key;
24
+ }
25
+ if (isScopedKebabKey(key)) {
26
+ return key;
27
+ }
28
+ return sys ? `${sys}-${key}` : key;
29
+ }
30
+
31
+ /**
32
+ * @param {Array<{ key?: string }>} datasources
33
+ * @param {string} systemKey
34
+ * @returns {Map<string, string>}
35
+ */
36
+ function canonicalDatasourceKeyMap(datasources, systemKey) {
37
+ const map = new Map();
38
+ for (const ds of datasources || []) {
39
+ const from = String(ds.key || '').trim();
40
+ if (!from) {
41
+ continue;
42
+ }
43
+ map.set(from, canonicalDatasourceKey(from, systemKey));
44
+ }
45
+ return map;
46
+ }
47
+
48
+ /**
49
+ * @param {string} systemKey
50
+ * @param {string} canonicalKey
51
+ * @returns {string}
52
+ */
53
+ function datasourceFileName(systemKey, canonicalKey) {
54
+ const sys = String(systemKey || '');
55
+ const key = String(canonicalKey || '');
56
+ const suffix = sys && key.startsWith(`${sys}-`) ? key.slice(sys.length + 1) : key;
57
+ return `${sys}-datasource-${suffix}.json`;
58
+ }
59
+
60
+ /**
61
+ * @param {string} key
62
+ * @param {Map<string, string>} keyMap
63
+ * @returns {string}
64
+ */
65
+ function remapKey(key, keyMap) {
66
+ const raw = String(key || '').trim();
67
+ return (keyMap && keyMap.get(raw)) || raw;
68
+ }
69
+
70
+ module.exports = {
71
+ canonicalDatasourceKey,
72
+ canonicalDatasourceKeyMap,
73
+ datasourceFileName,
74
+ remapKey
75
+ };
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Compile BID relationships[] into EDS foreignKeys and child type:fk dimensions.
3
+ *
4
+ * @fileoverview BID relationship compile for apply
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { remapKey } = require('./apply-datasource-keys');
10
+
11
+ /**
12
+ * @param {Record<string, unknown>} rel
13
+ * @returns {string[]}
14
+ */
15
+ function carrierFieldsFromRelationship(rel) {
16
+ if (Array.isArray(rel.fields) && rel.fields.length > 0) {
17
+ return rel.fields.map((row) => String(row || '').trim()).filter(Boolean);
18
+ }
19
+ if (typeof rel.field === 'string' && rel.field.trim()) {
20
+ return [rel.field.trim()];
21
+ }
22
+ if (typeof rel.carrier === 'string' && rel.carrier.trim()) {
23
+ return [rel.carrier.trim()];
24
+ }
25
+ return [];
26
+ }
27
+
28
+ /**
29
+ * @param {string[]} fields
30
+ * @returns {string}
31
+ */
32
+ function fkNameFromCarrier(fields) {
33
+ const raw = String((fields && fields[0]) || '').trim();
34
+ if (/^[a-z][a-zA-Z0-9]*$/.test(raw)) {
35
+ return raw;
36
+ }
37
+ const camel = raw.replace(/[-_]([a-zA-Z0-9])/g, (_, ch) => String(ch).toUpperCase());
38
+ return /^[a-z][a-zA-Z0-9]*$/.test(camel) ? camel : '';
39
+ }
40
+
41
+ /**
42
+ * @param {Record<string, unknown>} body
43
+ * @param {string} carrier
44
+ * @returns {void}
45
+ */
46
+ function carrierPropertyNode(props, carrier) {
47
+ const existing = props[carrier] && typeof props[carrier] === 'object'
48
+ ? /** @type {Record<string, unknown>} */ (props[carrier])
49
+ : { type: 'string' };
50
+ return { ...existing, type: existing.type || 'string', index: true, filter: true };
51
+ }
52
+
53
+ function ensureCarrierMappings(body, carrier) {
54
+ if (!body.fieldMappings || typeof body.fieldMappings !== 'object') {
55
+ body.fieldMappings = { attributes: {} };
56
+ }
57
+ const mappings = /** @type {Record<string, unknown>} */ (body.fieldMappings);
58
+ if (!mappings.attributes || typeof mappings.attributes !== 'object') {
59
+ mappings.attributes = {};
60
+ }
61
+ const attrs = /** @type {Record<string, unknown>} */ (mappings.attributes);
62
+ if (!attrs[carrier]) {
63
+ attrs[carrier] = { expression: `{{raw.${carrier}}}`, description: 'Join carrier' };
64
+ }
65
+ }
66
+
67
+ function ensureCarrierExposed(body, carrier) {
68
+ if (!body.exposed || typeof body.exposed !== 'object') {
69
+ body.exposed = {};
70
+ }
71
+ const exposed = /** @type {Record<string, unknown>} */ (body.exposed);
72
+ const filterable = Array.isArray(exposed.filterable) ? [...exposed.filterable] : [];
73
+ if (!filterable.includes(carrier)) {
74
+ filterable.push(carrier);
75
+ }
76
+ exposed.filterable = filterable;
77
+ }
78
+
79
+ function ensureCarrierOnChild(body, carrier) {
80
+ if (!body.metadataSchema || typeof body.metadataSchema !== 'object') {
81
+ body.metadataSchema = { type: 'object', properties: {} };
82
+ }
83
+ const schema = /** @type {Record<string, unknown>} */ (body.metadataSchema);
84
+ if (!schema.properties || typeof schema.properties !== 'object') {
85
+ schema.properties = {};
86
+ }
87
+ const props = /** @type {Record<string, unknown>} */ (schema.properties);
88
+ props[carrier] = carrierPropertyNode(props, carrier);
89
+ ensureCarrierMappings(body, carrier);
90
+ ensureCarrierExposed(body, carrier);
91
+ }
92
+
93
+ /**
94
+ * @param {Record<string, unknown>} childBody
95
+ * @param {Record<string, unknown>} parentBody
96
+ * @param {string} fkName
97
+ * @returns {void}
98
+ */
99
+ function copyParentCatalogDimsAsFk(childBody, parentBody, fkName) {
100
+ const parentDims = parentBody.dimensions;
101
+ if (!parentDims || typeof parentDims !== 'object' || Array.isArray(parentDims)) {
102
+ return;
103
+ }
104
+ if (!childBody.dimensions || typeof childBody.dimensions !== 'object') {
105
+ childBody.dimensions = {};
106
+ }
107
+ const childDims = /** @type {Record<string, unknown>} */ (childBody.dimensions);
108
+ for (const [dimKey, binding] of Object.entries(parentDims)) {
109
+ if (!binding || typeof binding !== 'object' || childDims[dimKey]) {
110
+ continue;
111
+ }
112
+ childDims[dimKey] = { type: 'fk', via: [{ fk: fkName, dimension: dimKey }] };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * @param {Array<{ name: string, body: Record<string, unknown> }>} dsFiles
118
+ * @returns {Map<string, Record<string, unknown>>}
119
+ */
120
+ function bodiesByKey(dsFiles) {
121
+ const map = new Map();
122
+ for (const file of dsFiles || []) {
123
+ const key = String(file.body && file.body.key ? file.body.key : '').trim();
124
+ if (key) {
125
+ map.set(key, file.body);
126
+ }
127
+ }
128
+ return map;
129
+ }
130
+
131
+ /**
132
+ * @param {Record<string, unknown>} childBody
133
+ * @param {string} name
134
+ * @param {string[]} fields
135
+ * @param {string} targetKey
136
+ * @returns {void}
137
+ */
138
+ function upsertForeignKey(childBody, name, fields, targetKey) {
139
+ if (!Array.isArray(childBody.foreignKeys)) {
140
+ childBody.foreignKeys = [];
141
+ }
142
+ const rows = /** @type {Array<Record<string, unknown>>} */ (childBody.foreignKeys);
143
+ const next = {
144
+ name,
145
+ fields,
146
+ targetDatasource: targetKey,
147
+ targetFields: ['externalId']
148
+ };
149
+ const idx = rows.findIndex((row) => row && row.name === name);
150
+ if (idx >= 0) {
151
+ rows[idx] = { ...rows[idx], ...next };
152
+ return;
153
+ }
154
+ rows.push(next);
155
+ }
156
+
157
+ /**
158
+ * @param {Array<{ name: string, body: Record<string, unknown> }>} dsFiles
159
+ * @param {unknown[]} relationships
160
+ * @param {Map<string, string>} keyMap
161
+ * @returns {void}
162
+ */
163
+ function applyBidRelationships(dsFiles, relationships, keyMap) {
164
+ if (!Array.isArray(relationships) || relationships.length === 0) {
165
+ return;
166
+ }
167
+ const bodies = bodiesByKey(dsFiles);
168
+ for (const rel of relationships) {
169
+ if (!rel || typeof rel !== 'object') {
170
+ continue;
171
+ }
172
+ const row = /** @type {Record<string, unknown>} */ (rel);
173
+ const fields = carrierFieldsFromRelationship(row);
174
+ const fkName = fkNameFromCarrier(fields);
175
+ if (!fkName) {
176
+ continue;
177
+ }
178
+ const fromKey = remapKey(String(row.from || ''), keyMap);
179
+ const toKey = remapKey(String(row.to || ''), keyMap);
180
+ const childBody = bodies.get(fromKey);
181
+ const parentBody = bodies.get(toKey);
182
+ if (!childBody || !parentBody) {
183
+ continue;
184
+ }
185
+ ensureCarrierOnChild(childBody, fkName);
186
+ upsertForeignKey(childBody, fkName, [fkName], toKey);
187
+ copyParentCatalogDimsAsFk(childBody, parentBody, fkName);
188
+ }
189
+ }
190
+
191
+ module.exports = {
192
+ applyBidRelationships,
193
+ carrierFieldsFromRelationship,
194
+ fkNameFromCarrier
195
+ };