@aifabrix/builder 2.46.0 → 2.47.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/.npmrc.token +1 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +18 -2
- package/jest.projects.js +24 -0
- package/lib/api/dataplane-health.api.js +19 -7
- package/lib/api/lifecycle.api.js +22 -1
- package/lib/api/resource-types.api.js +107 -0
- package/lib/api/runtime-configuration.api.js +72 -0
- package/lib/api/types/resource-types.types.js +45 -0
- package/lib/api/user-groups.api.js +9 -9
- package/lib/api/users.api.js +8 -8
- package/lib/api/wizard.api.js +10 -2
- package/lib/app/down.js +58 -26
- package/lib/app/index.js +2 -2
- package/lib/app/run-resolve-image.js +2 -1
- package/lib/app/run-stop-containers.js +113 -0
- package/lib/app/run.js +1 -31
- package/lib/app/show-display.js +35 -0
- package/lib/app/show.js +32 -1
- package/lib/build/index.js +17 -5
- package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
- package/lib/channels/add-channel.js +348 -0
- package/lib/channels/channel-artifact-generator.js +141 -0
- package/lib/channels/channel-audit.js +101 -0
- package/lib/channels/channel-auth-context.js +37 -0
- package/lib/channels/channel-credential.js +200 -0
- package/lib/channels/channel-display-name.js +64 -0
- package/lib/channels/channel-external-system.js +106 -0
- package/lib/channels/channel-integration-generator.js +274 -0
- package/lib/channels/channel-probe.js +89 -0
- package/lib/channels/channel-runtime-resolver.js +152 -0
- package/lib/channels/channel-status-display.js +141 -0
- package/lib/channels/channel-targets.js +115 -0
- package/lib/cli/index.js +16 -4
- package/lib/cli/setup-app.js +54 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
- package/lib/cli/setup-infra-up-platform-action.js +2 -1
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-help-after.js +4 -1
- package/lib/cli/setup-utility-resolve.js +8 -5
- package/lib/commands/app-logs.js +54 -26
- package/lib/commands/app.js +41 -0
- package/lib/commands/auth-status-dataplane-version.js +97 -61
- package/lib/commands/auth-status.js +2 -7
- package/lib/commands/channel.js +58 -0
- package/lib/commands/datasource.js +1 -1
- package/lib/commands/identity-apply-sync.js +49 -8
- package/lib/commands/identity-group-cli.js +66 -26
- package/lib/commands/identity-membership-role-cache.js +58 -37
- package/lib/commands/identity-shared.js +22 -1
- package/lib/commands/identity-user-cli.js +80 -34
- package/lib/commands/repair-auth-apply.js +30 -4
- package/lib/commands/repair-auth-config.js +8 -13
- package/lib/commands/repair-datasource-com-v2.js +15 -0
- package/lib/commands/repair-datasource-service-contract.js +206 -0
- package/lib/commands/repair-datasource.js +53 -18
- package/lib/commands/repair-env-template.js +12 -12
- package/lib/commands/repair-openapi-sync.js +26 -4
- package/lib/commands/repair-steps.js +28 -13
- package/lib/commands/repair-system-alignment.js +7 -4
- package/lib/commands/resource-type.js +371 -0
- package/lib/commands/setup-modes.js +1 -1
- package/lib/commands/setup-prompts-platform-mode.js +24 -21
- package/lib/commands/test-trust-external.js +39 -90
- package/lib/commands/up-common.js +33 -1
- package/lib/commands/up-dataplane.js +8 -4
- package/lib/commands/upload.js +24 -3
- package/lib/commands/verify-trust-command-action.js +4 -1
- package/lib/commands/verify-trust-external.js +12 -20
- package/lib/core/config-setup-platform-mode.js +3 -3
- package/lib/core/setup-platform-mode.js +41 -6
- package/lib/datasource/capability/capability-diff-slice.js +2 -2
- package/lib/datasource/capability/capability-manifest.js +254 -0
- package/lib/datasource/capability/capability-resolve.js +3 -4
- package/lib/datasource/capability/copy-operations.js +5 -3
- package/lib/datasource/capability/remove-operations.js +2 -1
- package/lib/datasource/capability/run-capability-copy.js +32 -4
- package/lib/datasource/capability/run-capability-diff.js +3 -3
- package/lib/datasource/capability/run-capability-dimension.js +2 -4
- package/lib/datasource/capability/run-capability-edit.js +4 -7
- package/lib/datasource/capability/run-capability-relate.js +2 -4
- package/lib/datasource/capability/run-capability-remove.js +2 -4
- package/lib/datasource/capability/validate-capability-slice.js +2 -1
- package/lib/datasource/datasource-identifier.js +2 -1
- package/lib/datasource/datasource-validate-display.js +1 -1
- package/lib/datasource/field-reference-validator.js +24 -3
- package/lib/datasource/orphan-profiles-validator.js +98 -0
- package/lib/datasource/validate.js +41 -23
- package/lib/external-system/download-helpers.js +87 -0
- package/lib/external-system/download.js +61 -122
- package/lib/external-system/generator.js +1 -59
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +11 -1
- package/lib/generator/external-controller-manifest.js +5 -2
- package/lib/generator/split.js +3 -1
- package/lib/generator/wizard.js +1 -28
- package/lib/identity/fk-reference.js +65 -0
- package/lib/identity/identity-apply-core.js +13 -58
- package/lib/identity/identity-apply-phases.js +5 -1
- package/lib/identity/identity-display.js +243 -0
- package/lib/identity/identity-response-helpers.js +52 -0
- package/lib/identity/identity-sync-errors.js +62 -0
- package/lib/identity/resolve-user-id.js +129 -0
- package/lib/identity/username-default.js +31 -0
- package/lib/lifecycle/certification-progress.js +2 -2
- package/lib/lifecycle/report-display-trust.js +70 -0
- package/lib/lifecycle/report-display.js +14 -13
- package/lib/resolvers/datasource-resolver.js +2 -3
- package/lib/resolvers/resource-type-file.js +159 -0
- package/lib/schema/application-schema.json +16 -0
- package/lib/schema/deployment-rules.yaml +2 -1
- package/lib/schema/external-datasource.schema.json +372 -58
- package/lib/schema/external-system.schema.json +331 -2
- package/lib/schema/type/document-storage.json +9 -5
- package/lib/utils/app-run-containers.js +25 -2
- package/lib/utils/authentication-shape.js +224 -0
- package/lib/utils/build-copy.js +36 -5
- package/lib/utils/build-resolve-image.js +66 -12
- package/lib/utils/celery-companion-apps.js +53 -0
- package/lib/utils/cli-utils.js +4 -0
- package/lib/utils/compose-celery-worker.js +23 -0
- package/lib/utils/compose-generate-docker-compose.js +9 -1
- package/lib/utils/configuration-env-resolver.js +36 -0
- package/lib/utils/credential-secrets-env-kv.js +216 -0
- package/lib/utils/credential-secrets-env.js +52 -262
- package/lib/utils/dataplane-resolver.js +4 -115
- package/lib/utils/declarative-url-ports.js +24 -1
- package/lib/utils/docker-build.js +1 -4
- package/lib/utils/external-env-template.js +10 -3
- package/lib/utils/external-system-validators.js +8 -2
- package/lib/utils/help-builder.js +2 -0
- package/lib/utils/paths.js +25 -0
- package/lib/utils/platform-controller-url.js +2 -1
- package/lib/utils/platform-kv-ref.js +57 -0
- package/lib/utils/runtime-configuration-download.js +189 -0
- package/lib/utils/runtime-configuration-values.js +119 -0
- package/lib/utils/schema-loader.js +19 -4
- package/lib/utils/worker-sidecars.js +142 -0
- package/lib/validation/external-manifest-validator.js +2 -0
- package/lib/validation/runtime-configuration-rules.js +98 -0
- package/lib/validation/validate-display-dimensions.js +154 -0
- package/lib/validation/validate-display-sections.js +276 -0
- package/lib/validation/validate-display-steps.js +67 -0
- package/lib/validation/validate-display.js +24 -402
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +5 -0
- package/templates/applications/dataplane/env.template +26 -13
- package/templates/applications/dataplane/rbac.yaml +108 -1
- package/templates/applications/miso-controller/application.yaml +2 -2
- package/templates/applications/miso-controller/env.template +37 -122
- package/templates/applications/miso-controller/rbac.yaml +28 -13
- package/templates/channels/chatgpt/install.template.md.hbs +49 -0
- package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
- package/templates/channels/slack/install.template.md.hbs +49 -0
- package/templates/channels/slack/manifest.template.json.hbs +66 -0
- package/templates/channels/teams-bot/install.template.md.hbs +49 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
- package/templates/channels/web-embed/install.template.md.hbs +49 -0
- package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
- package/templates/python/docker-compose.hbs +56 -0
- package/templates/typescript/docker-compose.hbs +52 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orchestrate aifabrix app add-channel (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel install after communication external system deploy
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
|
|
12
|
+
const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
|
|
13
|
+
const { generateChannelArtifact } = require('./channel-artifact-generator');
|
|
14
|
+
const {
|
|
15
|
+
getExistingChannelExtension,
|
|
16
|
+
buildChannelExtensionBlock,
|
|
17
|
+
fetchCommunicationSystem,
|
|
18
|
+
patchChannelExtension
|
|
19
|
+
} = require('./channel-external-system');
|
|
20
|
+
const {
|
|
21
|
+
auditArtifactGenerated,
|
|
22
|
+
auditConfigPatched,
|
|
23
|
+
auditApprovalRequired,
|
|
24
|
+
auditValidated,
|
|
25
|
+
auditFailed
|
|
26
|
+
} = require('./channel-audit');
|
|
27
|
+
const { runChannelProbe } = require('./channel-probe');
|
|
28
|
+
const { resolveChannelAuthContext } = require('./channel-auth-context');
|
|
29
|
+
const { ensureChannelCredential } = require('./channel-credential');
|
|
30
|
+
const {
|
|
31
|
+
normalizeBaseChannelName,
|
|
32
|
+
resolveChannelDisplayName
|
|
33
|
+
} = require('./channel-display-name');
|
|
34
|
+
const {
|
|
35
|
+
displayExistingChannelStatus,
|
|
36
|
+
displayChannelInstallResult,
|
|
37
|
+
displayChannelError
|
|
38
|
+
} = require('./channel-status-display');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
|
|
42
|
+
* @param {Object} channelExt
|
|
43
|
+
* @param {Object} [context]
|
|
44
|
+
* @returns {string|null}
|
|
45
|
+
*/
|
|
46
|
+
function resolveApprovalUrl(target, channelExt, context = {}) {
|
|
47
|
+
if (!target.approvalRequired) return null;
|
|
48
|
+
if (channelExt?.status === 'installed' || channelExt?.status === 'validated') return null;
|
|
49
|
+
if (target.key === 'slack' && context.artifactPath) {
|
|
50
|
+
return `https://api.slack.com/apps/new — upload manifest: ${context.artifactPath}`;
|
|
51
|
+
}
|
|
52
|
+
if (target.key === 'teams-bot') {
|
|
53
|
+
return 'https://admin.teams.microsoft.com/ — configure Bot Channel Registration for this tenant';
|
|
54
|
+
}
|
|
55
|
+
return `https://platform-admin.example/configure/${target.key}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Object} target
|
|
60
|
+
* @param {Object} channelExt
|
|
61
|
+
* @param {boolean} dryRun
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function resolveInstallStatus(target, channelExt, dryRun) {
|
|
65
|
+
if (dryRun) return 'generated';
|
|
66
|
+
if (channelExt?.status === 'approvalRequired') return 'approvalRequired';
|
|
67
|
+
if (target.approvalRequired && !channelExt) return 'approvalRequired';
|
|
68
|
+
if (target.approvalRequired && channelExt?.status !== 'installed') return 'approvalRequired';
|
|
69
|
+
return 'installed';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {Object|null} validation
|
|
74
|
+
* @returns {Object|null}
|
|
75
|
+
*/
|
|
76
|
+
function toValidationDisplay(validation) {
|
|
77
|
+
if (!validation) return null;
|
|
78
|
+
return {
|
|
79
|
+
callbackReachable: validation.callbackReachable,
|
|
80
|
+
credentialValid: validation.credentialValid,
|
|
81
|
+
assistantCatalogReachable: validation.assistantCatalogReachable,
|
|
82
|
+
inboundVerification: Object.values(validation.inboundVerification).every(Boolean)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {Object} params
|
|
88
|
+
* @returns {Object|null}
|
|
89
|
+
*/
|
|
90
|
+
function showExistingChannelStatus(params) {
|
|
91
|
+
const { existing, options, target, channelTarget, name, baseName } = params;
|
|
92
|
+
if (!existing || options.force || options.dryRun) return null;
|
|
93
|
+
const sameInstall =
|
|
94
|
+
(existing.baseName && existing.baseName === baseName) ||
|
|
95
|
+
(!existing.baseName && existing.name === name);
|
|
96
|
+
if (!sameInstall) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Channel already configured as "${existing.name}". ` +
|
|
99
|
+
`Use --force to replace with "${name}".`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
displayExistingChannelStatus({
|
|
103
|
+
channelTarget,
|
|
104
|
+
name: existing.name || name,
|
|
105
|
+
channelExt: existing,
|
|
106
|
+
approvalUrl: resolveApprovalUrl(target, existing)
|
|
107
|
+
});
|
|
108
|
+
return { skipped: true, channelExt: existing };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} channelTarget
|
|
113
|
+
* @param {Object} artifactParams
|
|
114
|
+
* @returns {Object}
|
|
115
|
+
*/
|
|
116
|
+
function generateArtifactWithAudit(channelTarget, artifactParams) {
|
|
117
|
+
try {
|
|
118
|
+
const artifact = generateChannelArtifact(artifactParams);
|
|
119
|
+
auditArtifactGenerated(channelTarget, {
|
|
120
|
+
artifactHash: artifact.artifactHash,
|
|
121
|
+
outputPath: artifact.outputPath,
|
|
122
|
+
dryRun: artifactParams.dryRun === true
|
|
123
|
+
});
|
|
124
|
+
return artifact;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
auditFailed(channelTarget, { phase: 'artifact', error: err.message });
|
|
127
|
+
throw err;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {Object} ctx
|
|
133
|
+
* @param {string} channelTarget
|
|
134
|
+
* @param {Object} channelBlock
|
|
135
|
+
* @param {string} status
|
|
136
|
+
* @param {string} artifactHash
|
|
137
|
+
* @returns {Promise<void>}
|
|
138
|
+
*/
|
|
139
|
+
async function patchConfigWithAudit(ctx, channelTarget, channelBlock, status, artifactHash) {
|
|
140
|
+
try {
|
|
141
|
+
await patchChannelExtension(
|
|
142
|
+
{ dataplaneUrl: ctx.dataplaneUrl, authConfig: ctx.authConfig, channelTarget },
|
|
143
|
+
channelBlock
|
|
144
|
+
);
|
|
145
|
+
auditConfigPatched(channelTarget, { status, artifactHash });
|
|
146
|
+
} catch (err) {
|
|
147
|
+
auditFailed(channelTarget, { phase: 'patch', error: err.message });
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Object} ctx
|
|
154
|
+
* @param {Object} channelBlock
|
|
155
|
+
* @param {string} channelTarget
|
|
156
|
+
* @param {boolean} probe
|
|
157
|
+
* @returns {Promise<Object|null>}
|
|
158
|
+
*/
|
|
159
|
+
async function maybeRunProbe(ctx, channelBlock, channelTarget, probe) {
|
|
160
|
+
if (!probe) return null;
|
|
161
|
+
const validation = await runChannelProbe(
|
|
162
|
+
{ dataplaneUrl: ctx.dataplaneUrl, authConfig: ctx.authConfig },
|
|
163
|
+
channelBlock
|
|
164
|
+
);
|
|
165
|
+
if (validation.passed) {
|
|
166
|
+
auditValidated(channelTarget, { probe: true });
|
|
167
|
+
} else {
|
|
168
|
+
auditFailed(channelTarget, { phase: 'probe', validation });
|
|
169
|
+
}
|
|
170
|
+
return validation;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} channelTarget
|
|
175
|
+
* @param {Object} options
|
|
176
|
+
* @returns {Promise<{ target: Object, name: string, baseName: string, description: string, ctx: Object, existing: Object|null }>}
|
|
177
|
+
*/
|
|
178
|
+
async function prepareChannelContext(channelTarget, options) {
|
|
179
|
+
const target = getChannelTarget(channelTarget);
|
|
180
|
+
if (target.experimental) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`Channel target "${channelTarget}" is experimental. Confirm product requirement before use.`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (!options.name || typeof options.name !== 'string' || !options.name.trim()) {
|
|
186
|
+
throw new Error('--name is required for app add-channel');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const baseName = normalizeBaseChannelName(options.name);
|
|
190
|
+
const ctx = await resolveChannelAuthContext(channelTarget, { silent: options.quiet });
|
|
191
|
+
const name = resolveChannelDisplayName(baseName, ctx.environment);
|
|
192
|
+
const description =
|
|
193
|
+
options.description && String(options.description).trim()
|
|
194
|
+
? String(options.description).trim()
|
|
195
|
+
: getDefaultChannelDescription();
|
|
196
|
+
|
|
197
|
+
const system = await fetchCommunicationSystem({
|
|
198
|
+
dataplaneUrl: ctx.dataplaneUrl,
|
|
199
|
+
authConfig: ctx.authConfig,
|
|
200
|
+
channelTarget
|
|
201
|
+
});
|
|
202
|
+
const existing = getExistingChannelExtension(system);
|
|
203
|
+
return { target, name, baseName, description, ctx, existing };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {Object} params
|
|
208
|
+
* @returns {Promise<Object>}
|
|
209
|
+
*/
|
|
210
|
+
async function completeLiveInstall(params) {
|
|
211
|
+
const { ctx, channelTarget, channelBlock, status, artifact, target, name, baseName, options } = params;
|
|
212
|
+
|
|
213
|
+
await ensureChannelCredential({
|
|
214
|
+
ctx,
|
|
215
|
+
channelTarget,
|
|
216
|
+
target,
|
|
217
|
+
force: options.force === true
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await patchConfigWithAudit(ctx, channelTarget, channelBlock, status, artifact.artifactHash);
|
|
221
|
+
|
|
222
|
+
const approvalUrl =
|
|
223
|
+
status === 'approvalRequired'
|
|
224
|
+
? resolveApprovalUrl(target, channelBlock, { artifactPath: artifact.outputPath })
|
|
225
|
+
: null;
|
|
226
|
+
if (approvalUrl) {
|
|
227
|
+
auditApprovalRequired(channelTarget, { approvalUrl });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const validation = await maybeRunProbe(ctx, channelBlock, channelTarget, options.probe);
|
|
231
|
+
|
|
232
|
+
displayChannelInstallResult({
|
|
233
|
+
channelTarget,
|
|
234
|
+
name,
|
|
235
|
+
baseName,
|
|
236
|
+
statusLabel: status === 'approvalRequired' ? 'Approval required' : 'Installed',
|
|
237
|
+
credentialKey: target.credentialKey,
|
|
238
|
+
capabilities: target.capabilities,
|
|
239
|
+
artifactPath: artifact.outputPath,
|
|
240
|
+
installGuidePath: artifact.installPath,
|
|
241
|
+
validation: toValidationDisplay(validation),
|
|
242
|
+
approvalUrl
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return { channelBlock, artifact, validation, approvalUrl };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param {Object} params
|
|
250
|
+
* @returns {{ dryRun: true, channelBlock: Object, artifact: Object }}
|
|
251
|
+
*/
|
|
252
|
+
function finishDryRunInstall(params) {
|
|
253
|
+
displayChannelInstallResult({
|
|
254
|
+
channelTarget: params.channelTarget,
|
|
255
|
+
name: params.name,
|
|
256
|
+
baseName: params.baseName,
|
|
257
|
+
statusLabel: 'Generated (dry-run)',
|
|
258
|
+
credentialKey: params.target.credentialKey,
|
|
259
|
+
capabilities: params.target.capabilities,
|
|
260
|
+
artifactPath: params.artifact.outputPath,
|
|
261
|
+
installGuidePath: params.artifact.installPath
|
|
262
|
+
});
|
|
263
|
+
return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @param {string} channelTarget
|
|
268
|
+
* @param {Object} options
|
|
269
|
+
* @returns {Promise<Object>}
|
|
270
|
+
*/
|
|
271
|
+
async function runAddChannel(channelTarget, options = {}) {
|
|
272
|
+
const { target, name, baseName, description, ctx, existing } = await prepareChannelContext(
|
|
273
|
+
channelTarget,
|
|
274
|
+
options
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const skipped = showExistingChannelStatus({
|
|
278
|
+
existing,
|
|
279
|
+
options,
|
|
280
|
+
target,
|
|
281
|
+
channelTarget,
|
|
282
|
+
name,
|
|
283
|
+
baseName
|
|
284
|
+
});
|
|
285
|
+
if (skipped) return skipped;
|
|
286
|
+
|
|
287
|
+
const runtimeValues = resolveChannelRuntimeValues({
|
|
288
|
+
dataplaneBaseUrl: ctx.dataplaneUrl,
|
|
289
|
+
channelTarget,
|
|
290
|
+
appName: name,
|
|
291
|
+
baseName,
|
|
292
|
+
environment: ctx.environment,
|
|
293
|
+
appDescription: description,
|
|
294
|
+
icon: options.icon
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
298
|
+
const artifact = generateArtifactWithAudit(channelTarget, {
|
|
299
|
+
projectRoot,
|
|
300
|
+
channelTarget,
|
|
301
|
+
runtimeValues,
|
|
302
|
+
dryRun: options.dryRun === true
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const status = resolveInstallStatus(target, existing, options.dryRun === true);
|
|
306
|
+
const channelBlock = buildChannelExtensionBlock(
|
|
307
|
+
{ name, baseName, description, artifactHash: artifact.artifactHash, status },
|
|
308
|
+
target
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
if (options.dryRun) {
|
|
312
|
+
return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return completeLiveInstall({
|
|
316
|
+
ctx,
|
|
317
|
+
channelTarget,
|
|
318
|
+
channelBlock,
|
|
319
|
+
status,
|
|
320
|
+
artifact,
|
|
321
|
+
target,
|
|
322
|
+
name,
|
|
323
|
+
baseName,
|
|
324
|
+
options
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* @param {string} channelTarget
|
|
330
|
+
* @param {Object} options
|
|
331
|
+
*/
|
|
332
|
+
async function addChannelCommand(channelTarget, options = {}) {
|
|
333
|
+
try {
|
|
334
|
+
await runAddChannel(channelTarget, options);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
displayChannelError(err.message || String(err));
|
|
337
|
+
throw err;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = {
|
|
342
|
+
runAddChannel,
|
|
343
|
+
addChannelCommand,
|
|
344
|
+
resolveApprovalUrl,
|
|
345
|
+
resolveInstallStatus,
|
|
346
|
+
normalizeBaseChannelName,
|
|
347
|
+
resolveChannelDisplayName
|
|
348
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate channel artifacts from Handlebars templates (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel artifact generation — dist/channels, sha256 hash, no secrets
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const crypto = require('crypto');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const Handlebars = require('handlebars');
|
|
15
|
+
const { getChannelTarget } = require('./channel-targets');
|
|
16
|
+
|
|
17
|
+
const SECRET_PATTERNS = [/xoxb-/i, /xoxp-/i, /client_secret/i, /signing_secret/i, /Bearer\s+/i];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} content
|
|
21
|
+
* @throws {Error}
|
|
22
|
+
*/
|
|
23
|
+
function assertNoSecretsInArtifact(content) {
|
|
24
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
25
|
+
if (pattern.test(content)) {
|
|
26
|
+
throw new Error('Generated artifact must not contain secret-like values');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (content.includes('your-domain.com')) {
|
|
30
|
+
throw new Error('Generated artifact still contains placeholder domain your-domain.com');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string} projectRoot
|
|
36
|
+
* @param {string} channelTarget
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function getTemplatesDir(projectRoot, channelTarget) {
|
|
40
|
+
const target = getChannelTarget(channelTarget);
|
|
41
|
+
return path.join(projectRoot, 'templates', 'channels', target.templateDir);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} projectRoot
|
|
46
|
+
* @param {string} channelTarget
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function getArtifactOutputPath(projectRoot, channelTarget) {
|
|
50
|
+
const target = getChannelTarget(channelTarget);
|
|
51
|
+
return path.join(
|
|
52
|
+
projectRoot,
|
|
53
|
+
'dist',
|
|
54
|
+
'channels',
|
|
55
|
+
channelTarget,
|
|
56
|
+
target.artifactFileName
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} templatePath
|
|
62
|
+
* @param {Object} context
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
function renderTemplateFile(templatePath, context) {
|
|
66
|
+
if (!fs.existsSync(templatePath)) {
|
|
67
|
+
throw new Error(`Channel template not found: ${templatePath}`);
|
|
68
|
+
}
|
|
69
|
+
const raw = fs.readFileSync(templatePath, 'utf8');
|
|
70
|
+
const template = Handlebars.compile(raw);
|
|
71
|
+
return template(context);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string} templatesDir
|
|
76
|
+
* @param {Object} runtimeValues
|
|
77
|
+
* @param {string} outputDir
|
|
78
|
+
* @param {boolean} dryRun
|
|
79
|
+
* @returns {{ installPath: string, content: string }|null}
|
|
80
|
+
*/
|
|
81
|
+
function renderInstallGuideIfPresent(templatesDir, runtimeValues, outputDir, dryRun) {
|
|
82
|
+
const installTemplate = path.join(templatesDir, 'install.template.md.hbs');
|
|
83
|
+
if (!fs.existsSync(installTemplate)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const content = renderTemplateFile(installTemplate, runtimeValues);
|
|
87
|
+
assertNoSecretsInArtifact(content);
|
|
88
|
+
const installPath = path.join(outputDir, 'INSTALL.md');
|
|
89
|
+
if (!dryRun) {
|
|
90
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
91
|
+
fs.writeFileSync(installPath, content, 'utf8');
|
|
92
|
+
}
|
|
93
|
+
return { installPath, content };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} params
|
|
98
|
+
* @param {string} params.projectRoot
|
|
99
|
+
* @param {string} params.channelTarget
|
|
100
|
+
* @param {Object} params.runtimeValues
|
|
101
|
+
* @param {boolean} [params.dryRun]
|
|
102
|
+
* @returns {{ outputPath: string, artifactHash: string, content: string, installPath?: string }}
|
|
103
|
+
*/
|
|
104
|
+
function generateChannelArtifact(params) {
|
|
105
|
+
const templatesDir = getTemplatesDir(params.projectRoot, params.channelTarget);
|
|
106
|
+
const manifestTemplate = path.join(templatesDir, 'manifest.template.json.hbs');
|
|
107
|
+
const content = renderTemplateFile(manifestTemplate, params.runtimeValues);
|
|
108
|
+
assertNoSecretsInArtifact(content);
|
|
109
|
+
|
|
110
|
+
const outputPath = getArtifactOutputPath(params.projectRoot, params.channelTarget);
|
|
111
|
+
const artifactHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
112
|
+
const outputDir = path.dirname(outputPath);
|
|
113
|
+
|
|
114
|
+
if (!params.dryRun) {
|
|
115
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
116
|
+
fs.writeFileSync(outputPath, content, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const install = renderInstallGuideIfPresent(
|
|
120
|
+
templatesDir,
|
|
121
|
+
params.runtimeValues,
|
|
122
|
+
outputDir,
|
|
123
|
+
params.dryRun === true
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
outputPath,
|
|
128
|
+
artifactHash,
|
|
129
|
+
content,
|
|
130
|
+
installPath: install?.installPath
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
assertNoSecretsInArtifact,
|
|
136
|
+
getTemplatesDir,
|
|
137
|
+
getArtifactOutputPath,
|
|
138
|
+
renderTemplateFile,
|
|
139
|
+
renderInstallGuideIfPresent,
|
|
140
|
+
generateChannelArtifact
|
|
141
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured audit logging for channel install (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel install audit events — no secrets in payloads
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} eventName
|
|
15
|
+
* @param {Object} payload
|
|
16
|
+
*/
|
|
17
|
+
function emitChannelAuditEvent(eventName, payload) {
|
|
18
|
+
logger.log(
|
|
19
|
+
JSON.stringify({
|
|
20
|
+
event: eventName,
|
|
21
|
+
...payload,
|
|
22
|
+
timestamp: new Date().toISOString()
|
|
23
|
+
})
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} channelTarget
|
|
29
|
+
* @param {Object} details
|
|
30
|
+
*/
|
|
31
|
+
function auditArtifactGenerated(channelTarget, details) {
|
|
32
|
+
emitChannelAuditEvent('channelArtifactGenerated', { channelTarget, ...details });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} channelTarget
|
|
37
|
+
* @param {Object} details
|
|
38
|
+
*/
|
|
39
|
+
function auditConfigPatched(channelTarget, details) {
|
|
40
|
+
emitChannelAuditEvent('channelConfigPatched', { channelTarget, ...details });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} channelTarget
|
|
45
|
+
* @param {Object} details
|
|
46
|
+
*/
|
|
47
|
+
function auditCredentialCreated(channelTarget, details) {
|
|
48
|
+
emitChannelAuditEvent('channelCredentialCreated', { channelTarget, ...details });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} channelTarget
|
|
53
|
+
* @param {Object} details
|
|
54
|
+
*/
|
|
55
|
+
function auditCredentialUpdated(channelTarget, details) {
|
|
56
|
+
emitChannelAuditEvent('channelCredentialUpdated', { channelTarget, ...details });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string} channelTarget
|
|
61
|
+
* @param {Object} details
|
|
62
|
+
*/
|
|
63
|
+
function auditApprovalRequired(channelTarget, details) {
|
|
64
|
+
emitChannelAuditEvent('channelApprovalRequired', { channelTarget, ...details });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} channelTarget
|
|
69
|
+
* @param {Object} details
|
|
70
|
+
*/
|
|
71
|
+
function auditApprovalCompleted(channelTarget, details) {
|
|
72
|
+
emitChannelAuditEvent('channelApprovalCompleted', { channelTarget, ...details });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} channelTarget
|
|
77
|
+
* @param {Object} details
|
|
78
|
+
*/
|
|
79
|
+
function auditValidated(channelTarget, details) {
|
|
80
|
+
emitChannelAuditEvent('channelValidated', { channelTarget, ...details });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} channelTarget
|
|
85
|
+
* @param {Object} details
|
|
86
|
+
*/
|
|
87
|
+
function auditFailed(channelTarget, details) {
|
|
88
|
+
emitChannelAuditEvent('channelFailed', { channelTarget, ...details });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
emitChannelAuditEvent,
|
|
93
|
+
auditArtifactGenerated,
|
|
94
|
+
auditConfigPatched,
|
|
95
|
+
auditCredentialCreated,
|
|
96
|
+
auditCredentialUpdated,
|
|
97
|
+
auditApprovalRequired,
|
|
98
|
+
auditApprovalCompleted,
|
|
99
|
+
auditValidated,
|
|
100
|
+
auditFailed
|
|
101
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve controller, dataplane, and auth for channel install.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel install auth context (plan 152)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const config = require('../core/config');
|
|
12
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
13
|
+
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
14
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @async
|
|
18
|
+
* @param {string} channelTarget
|
|
19
|
+
* @param {{ silent?: boolean }} [opts]
|
|
20
|
+
* @returns {Promise<{ controllerUrl: string, environment: string, dataplaneUrl: string, authConfig: Object }>}
|
|
21
|
+
*/
|
|
22
|
+
async function resolveChannelAuthContext(channelTarget, opts = {}) {
|
|
23
|
+
const controllerUrl = await resolveControllerUrl();
|
|
24
|
+
const environment = (await config.getEnvironment()) || 'dev';
|
|
25
|
+
const authConfig = await getDeploymentAuth(controllerUrl, environment, channelTarget);
|
|
26
|
+
if (!authConfig) {
|
|
27
|
+
throw new Error('Authentication required. Run "aifabrix login" first.');
|
|
28
|
+
}
|
|
29
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig, {
|
|
30
|
+
silent: opts.silent === true
|
|
31
|
+
});
|
|
32
|
+
return { controllerUrl, environment, dataplaneUrl, authConfig };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
resolveChannelAuthContext
|
|
37
|
+
};
|