@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.
- package/README.md +8 -0
- package/lib/agent-kit/apply-ops.js +82 -0
- package/lib/agent-kit/checksums.js +28 -0
- package/lib/agent-kit/classify.js +113 -0
- package/lib/agent-kit/claude-shim.js +61 -0
- package/lib/agent-kit/constants.js +55 -0
- package/lib/agent-kit/file-list.js +103 -0
- package/lib/agent-kit/host-approve.js +130 -0
- package/lib/agent-kit/hosting/github.js +63 -0
- package/lib/agent-kit/hosts.js +71 -0
- package/lib/agent-kit/init.js +319 -0
- package/lib/agent-kit/kit-root.js +17 -0
- package/lib/agent-kit/managed-section.js +79 -0
- package/lib/agent-kit/package-load.js +45 -0
- package/lib/agent-kit/plan-ops.js +69 -0
- package/lib/agent-kit/remote-prompts.js +239 -0
- package/lib/agent-kit/render-host-markdown.js +40 -0
- package/lib/agent-kit/repo-guard.js +47 -0
- package/lib/agent-kit/report.js +107 -0
- package/lib/agent-kit/run.js +82 -0
- package/lib/agent-kit/scm/git.js +122 -0
- package/lib/agent-kit/state-file.js +83 -0
- package/lib/agent-kit/status.js +50 -0
- package/lib/agent-kit/v1-detect.js +81 -0
- package/lib/agent-kit/validate-kit.js +87 -0
- package/lib/agent-kit/workspace-name.js +40 -0
- package/lib/agent-kit/workspace-skeleton.js +103 -0
- package/lib/api/role-assistant-test-pack.api.js +86 -0
- package/lib/api/work-search.api.js +31 -4
- package/lib/api/work.api.js +19 -12
- package/lib/app/list-external-systems.js +2 -1
- package/lib/app/show-display.js +33 -6
- package/lib/app/show.js +25 -2
- package/lib/channels/add-channel.js +1 -0
- package/lib/channels/approval-guides/claude.js +2 -2
- package/lib/channels/channel-add-failure.js +1 -0
- package/lib/channels/channel-add-remaining.js +15 -9
- package/lib/channels/channel-definition-status.js +115 -0
- package/lib/channels/channel-description.js +2 -1
- package/lib/channels/channel-install-envelope.js +4 -2
- package/lib/channels/channel-integration-generator.js +2 -0
- package/lib/channels/channel-runtime-resolver.js +14 -0
- package/lib/channels/channel-targets.js +31 -5
- package/lib/channels/claude-setup-values.js +4 -1
- package/lib/channels/slack-channel-add.js +59 -6
- package/lib/channels/slack-mcp-callback.js +51 -0
- package/lib/channels/slack-mcp-runtime.js +156 -0
- package/lib/channels/slack-provisioning/install-cli.js +14 -2
- package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-certification-commands.js +2 -2
- package/lib/cli/setup-certification.help.js +3 -1
- package/lib/commands/agent-kit-help.js +79 -0
- package/lib/commands/agent-kit.js +346 -0
- package/lib/commands/channel-add-help.js +5 -5
- package/lib/commands/datasource-capability-relate-cli.js +5 -4
- package/lib/commands/repair-auth-apply.js +1 -1
- package/lib/commands/repair-datasource-key-skip.js +39 -0
- package/lib/commands/repair-datasource-keys.js +24 -12
- package/lib/commands/repair-rbac.js +16 -10
- package/lib/commands/role-assistant-copy-output.js +61 -0
- package/lib/commands/role-assistant-copy.js +155 -0
- package/lib/commands/role-assistant-test-output.js +8 -9
- package/lib/commands/role-assistant-test-result-rows.js +42 -0
- package/lib/commands/role-assistant.js +117 -48
- package/lib/commands/search.js +143 -0
- package/lib/commands/test-trust-external.js +7 -5
- package/lib/commands/upload-sidecar-followups.js +4 -1
- package/lib/commands/verify-operations-skip-e2e.js +131 -0
- package/lib/commands/verify-operations-step-plan.js +6 -33
- package/lib/commands/verify-operations-steps.js +3 -1
- package/lib/datasource/capability/relate-operations.js +28 -10
- package/lib/datasource/capability/run-capability-relate.js +48 -25
- package/lib/datasource/datasource-identifier.js +13 -5
- package/lib/deployment/deployer-poll-run.js +117 -35
- package/lib/external-system/deploy-dataplane-published.js +70 -0
- package/lib/external-system/deploy.js +12 -2
- package/lib/external-system/download-role-assistant.js +41 -15
- package/lib/external-system/download.js +7 -1
- package/lib/generator/wizard-datasource-key.js +54 -0
- package/lib/generator/wizard.js +55 -54
- package/lib/integration-definition/apply-datasource-keys.js +75 -0
- package/lib/integration-definition/apply-relationships.js +195 -0
- package/lib/integration-definition/apply-static-enterprise.js +229 -0
- package/lib/integration-definition/apply-system-file.js +71 -0
- package/lib/integration-definition/apply-test-endpoint.js +89 -0
- package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
- package/lib/integration-definition/apply.js +78 -59
- package/lib/integration-definition/enrich-openapi-operations.js +36 -10
- package/lib/integration-definition/enrich-openapi-pick.js +87 -0
- package/lib/lifecycle/role-assistant-governance.js +56 -21
- package/lib/programmatic/capability-registry.js +1 -0
- package/lib/programmatic/index.js +2 -0
- package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
- package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
- package/lib/programmatic/openapi-component-schemas.js +1 -0
- package/lib/programmatic/openapi-descriptions.js +9 -3
- package/lib/programmatic/openapi-envelope-examples.js +7 -0
- package/lib/programmatic/openapi-operation-bindings.js +5 -0
- package/lib/programmatic/openapi-request-schemas.js +5 -0
- package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
- package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
- package/lib/programmatic/openapi-spec.js +1 -0
- package/lib/programmatic/request-timeout.js +3 -0
- package/lib/programmatic/route-handler-map.js +7 -0
- package/lib/programmatic/run-channel-catalog.js +50 -8
- package/lib/programmatic/run-channel-install-options.js +5 -1
- package/lib/programmatic/run-download.js +41 -0
- package/lib/protection/auth-context.js +3 -1
- package/lib/protection/protection-display.js +3 -2
- package/lib/protection/report-exit.js +50 -0
- package/lib/protection/run-commands.js +2 -0
- package/lib/role-assistant/package-validate.js +175 -0
- package/lib/role-assistant/test-case-assertion-row.js +36 -0
- package/lib/role-assistant/test-case-copy-bind.js +77 -0
- package/lib/role-assistant/test-case-copy-expected.js +127 -0
- package/lib/role-assistant/test-case-copy-from-work.js +90 -0
- package/lib/role-assistant/test-case-copy-interactions.js +67 -0
- package/lib/role-assistant/test-case-copy.js +119 -0
- package/lib/role-assistant/test-case-expected-catalog.js +135 -0
- package/lib/role-assistant/test-case-validate.js +88 -0
- package/lib/role-assistant/test-cases-bid.js +117 -4
- package/lib/role-assistant/test-cases-evaluate.js +137 -30
- package/lib/role-assistant/test-cases-honesty.js +155 -6
- package/lib/role-assistant/test-cases-lifecycle.js +291 -0
- package/lib/role-assistant/test-cases-search-graph.js +122 -0
- package/lib/role-assistant/test-cases-search.js +20 -2
- package/lib/role-assistant/test-cases.js +34 -168
- package/lib/role-assistant/test-pack-map.js +180 -0
- package/lib/role-assistant/test-pack-sync.js +187 -0
- package/lib/role-assistant/test-runner-debug.js +54 -0
- package/lib/role-assistant/test-runner-respond-body.js +36 -0
- package/lib/role-assistant/test-runner-search.js +20 -5
- package/lib/role-assistant/test-runner-start-payload.js +63 -10
- package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
- package/lib/role-assistant/test-runner-workhub.js +45 -21
- package/lib/role-assistant/test-runner.js +50 -10
- package/lib/schema/external-datasource.schema.json +36 -5
- package/lib/schema/external-system.schema.json +20 -2
- package/lib/schema/role-assistant-test-case.schema.json +408 -0
- package/lib/search/dataplane-context.js +23 -0
- package/lib/search/search-args.js +211 -0
- package/lib/search/search-display.js +238 -0
- package/lib/search/search-errors.js +50 -0
- package/lib/search/search-prompts.js +119 -0
- package/lib/search/search-roles.js +85 -0
- package/lib/search/search-run.js +241 -0
- package/lib/search/search-skills.js +198 -0
- package/lib/utils/authentication-slot-show.js +153 -0
- package/lib/utils/credential-display.js +43 -1
- package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
- package/lib/utils/external-system-readiness-compact-display.js +30 -1
- package/lib/utils/external-system-validators.js +2 -1
- package/lib/utils/help-builder.js +3 -1
- package/lib/utils/schema-loader.js +37 -0
- package/lib/validation/validate-external-file.js +5 -1
- package/lib/validation/validate.js +3 -2
- package/package.json +1 -1
- package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
- package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
- package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
- package/templates/agent-kit/agent-kit.yaml +45 -0
- package/templates/agent-kit/instructions/AGENTKIT.md +83 -0
- package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
- package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
- package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
- package/templates/agent-kit/parity-inventory.yaml +188 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +58 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
- package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +51 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
- package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +50 -0
- package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
- package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
- package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +42 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
- package/templates/agent-kit/skills/shared/hosts.md +78 -0
- package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
- package/templates/agent-kit/workspace/LICENSE.mit +21 -0
- package/templates/agent-kit/workspace/README.md +89 -0
- package/templates/applications/builder-api/Dockerfile +3 -1
- package/templates/applications/dataplane/application.yaml +2 -2
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/dataplane/rbac.yaml +28 -0
- package/templates/applications/keycloak/env.template +3 -3
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +8 -9
- package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
- package/templates/channels/claude/channel.json +1 -1
- package/templates/channels/claude/install.template.md.hbs +1 -0
- package/templates/channels/claude/integration/claude-system.json +1 -1
- package/templates/channels/slack/channel.json +11 -0
- package/templates/channels/slack/install.template.md.hbs +9 -0
- package/templates/channels/slack/integration/slack-system.json +11 -0
- package/templates/channels/slack/manifest.template.json.hbs +6 -3
- package/templates/channels/teams-bot/channel.json +1 -0
- package/templates/python/docker-compose.hbs +14 -0
- 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
|
-
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
};
|
package/lib/generator/wizard.js
CHANGED
|
@@ -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
|
|
122
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
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
|
+
};
|