@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,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel credential matrix and provisioning (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Ensure channel credentials exist; push secrets from integration env
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { listCredentials } = require('../api/credentials.api');
|
|
13
|
+
const { credentialSelection } = require('../api/wizard.api');
|
|
14
|
+
const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
|
|
15
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
16
|
+
const {
|
|
17
|
+
auditCredentialCreated,
|
|
18
|
+
auditCredentialUpdated,
|
|
19
|
+
auditFailed
|
|
20
|
+
} = require('./channel-audit');
|
|
21
|
+
|
|
22
|
+
/** @type {Record<string, { method: string, displayName: string, credentialConfig: Object }>} */
|
|
23
|
+
const CREDENTIAL_SPECS = {
|
|
24
|
+
slack: {
|
|
25
|
+
method: 'hmac',
|
|
26
|
+
credentialConfig: {
|
|
27
|
+
type: 'hmac',
|
|
28
|
+
signingSecret: 'pending-channel-install',
|
|
29
|
+
algorithm: 'sha256',
|
|
30
|
+
signatureHeader: 'X-Slack-Signature',
|
|
31
|
+
timestampHeader: 'X-Slack-Request-Timestamp',
|
|
32
|
+
signaturePrefix: 'v0=',
|
|
33
|
+
timestampMaxAge: 300
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
'teams-bot': {
|
|
37
|
+
method: 'oidc',
|
|
38
|
+
credentialConfig: {
|
|
39
|
+
type: 'oidc',
|
|
40
|
+
openIdConfigUrl: 'https://login.botframework.com/v1/.well-known/openid_configuration',
|
|
41
|
+
clientId: 'pending-channel-install',
|
|
42
|
+
validateSignature: true,
|
|
43
|
+
algorithms: ['RS256'],
|
|
44
|
+
clockSkewSeconds: 300
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
'microsoft-copilot': {
|
|
48
|
+
method: 'oauth2',
|
|
49
|
+
credentialConfig: {
|
|
50
|
+
type: 'oauth2',
|
|
51
|
+
grantType: 'client_credentials',
|
|
52
|
+
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
|
53
|
+
clientId: 'pending-channel-install',
|
|
54
|
+
clientSecret: 'pending-channel-install'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
chatgpt: {
|
|
58
|
+
method: 'apiKey',
|
|
59
|
+
credentialConfig: {
|
|
60
|
+
type: 'apiKey',
|
|
61
|
+
apiKey: 'pending-channel-install',
|
|
62
|
+
headerName: 'Authorization',
|
|
63
|
+
prefix: 'Bearer'
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
'web-embed': {
|
|
67
|
+
method: 'apiKey',
|
|
68
|
+
credentialConfig: {
|
|
69
|
+
type: 'apiKey',
|
|
70
|
+
apiKey: 'pending-channel-install',
|
|
71
|
+
headerName: 'Authorization',
|
|
72
|
+
prefix: 'Bearer'
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {Object} response
|
|
79
|
+
* @returns {Object[]}
|
|
80
|
+
*/
|
|
81
|
+
function extractCredentials(response) {
|
|
82
|
+
const body = response?.data ?? response;
|
|
83
|
+
const data = body?.data ?? body;
|
|
84
|
+
const items = data?.credentials ?? data?.items ?? (Array.isArray(data) ? data : []);
|
|
85
|
+
return Array.isArray(items) ? items : [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {string} channelTarget
|
|
90
|
+
* @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
|
|
91
|
+
* @returns {{ method: ChannelCredentialMethod, displayName: string, credentialConfig: Object }}
|
|
92
|
+
*/
|
|
93
|
+
function buildCredentialSpec(channelTarget, target) {
|
|
94
|
+
const base = CREDENTIAL_SPECS[channelTarget];
|
|
95
|
+
if (!base) {
|
|
96
|
+
throw new Error(`No credential spec for channel target "${channelTarget}"`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
method: base.method,
|
|
100
|
+
displayName: `${target.key} assistant channel`,
|
|
101
|
+
credentialConfig: base.credentialConfig
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @async
|
|
107
|
+
* @param {Object} ctx
|
|
108
|
+
* @param {string} credentialKey
|
|
109
|
+
* @returns {Promise<Object|null>}
|
|
110
|
+
*/
|
|
111
|
+
async function findCredentialByKey(ctx, credentialKey) {
|
|
112
|
+
const response = await listCredentials(ctx.dataplaneUrl, ctx.authConfig, { pageSize: 200 });
|
|
113
|
+
const items = extractCredentials(response);
|
|
114
|
+
return items.find((item) => item.key === credentialKey || item.credentialKey === credentialKey) || null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @async
|
|
119
|
+
* @param {Object} ctx
|
|
120
|
+
* @param {string} channelTarget
|
|
121
|
+
* @param {Object} target
|
|
122
|
+
* @returns {Promise<Object>}
|
|
123
|
+
*/
|
|
124
|
+
async function createChannelCredential(ctx, channelTarget, target) {
|
|
125
|
+
const spec = buildCredentialSpec(channelTarget, target);
|
|
126
|
+
const payload = {
|
|
127
|
+
action: 'create',
|
|
128
|
+
credentialConfig: {
|
|
129
|
+
key: target.credentialKey,
|
|
130
|
+
displayName: spec.displayName,
|
|
131
|
+
description: 'Assistant channel credential (secrets via kv:// env.template)',
|
|
132
|
+
credentialConfig: spec.credentialConfig,
|
|
133
|
+
isActive: true
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const response = await credentialSelection(ctx.dataplaneUrl, ctx.authConfig, payload);
|
|
137
|
+
auditCredentialCreated(channelTarget, { credentialKey: target.credentialKey, method: spec.method });
|
|
138
|
+
return { created: true, credentialIdOrKey: response?.data?.credentialIdOrKey || target.credentialKey };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @async
|
|
143
|
+
* @param {Object} ctx
|
|
144
|
+
* @param {string} channelTarget
|
|
145
|
+
* @returns {Promise<{ pushed: number }>}
|
|
146
|
+
*/
|
|
147
|
+
async function pushIntegrationSecrets(ctx, channelTarget) {
|
|
148
|
+
const integrationDir = getIntegrationPath(channelTarget);
|
|
149
|
+
const envPath = path.join(integrationDir, '.env');
|
|
150
|
+
const result = await pushCredentialSecrets(ctx.dataplaneUrl, ctx.authConfig, {
|
|
151
|
+
envFilePath: envPath,
|
|
152
|
+
appName: channelTarget,
|
|
153
|
+
quiet: true
|
|
154
|
+
});
|
|
155
|
+
return { pushed: result?.pushed ?? 0, skipped: result?.skipped === true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Ensure channel credential record exists; optionally push secrets from integration/.env.
|
|
160
|
+
* Does not rotate secrets when force is set (plan 152 V1).
|
|
161
|
+
*
|
|
162
|
+
* @async
|
|
163
|
+
* @param {Object} params
|
|
164
|
+
* @param {Object} params.ctx
|
|
165
|
+
* @param {string} params.channelTarget
|
|
166
|
+
* @param {Object} params.target
|
|
167
|
+
* @param {boolean} [params.force]
|
|
168
|
+
* @returns {Promise<Object>}
|
|
169
|
+
*/
|
|
170
|
+
async function ensureChannelCredential(params) {
|
|
171
|
+
const { ctx, channelTarget, target, force = false } = params;
|
|
172
|
+
const existing = await findCredentialByKey(ctx, target.credentialKey);
|
|
173
|
+
if (existing) {
|
|
174
|
+
auditCredentialUpdated(channelTarget, {
|
|
175
|
+
credentialKey: target.credentialKey,
|
|
176
|
+
rotated: false,
|
|
177
|
+
force: force === true
|
|
178
|
+
});
|
|
179
|
+
const secretPush = await pushIntegrationSecrets(ctx, channelTarget);
|
|
180
|
+
return { existed: true, credentialKey: target.credentialKey, secretPush };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const created = await createChannelCredential(ctx, channelTarget, target);
|
|
185
|
+
const secretPush = await pushIntegrationSecrets(ctx, channelTarget);
|
|
186
|
+
return { ...created, credentialKey: target.credentialKey, secretPush };
|
|
187
|
+
} catch (err) {
|
|
188
|
+
auditFailed(channelTarget, { phase: 'credential', error: err.message });
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
buildCredentialSpec,
|
|
195
|
+
extractCredentials,
|
|
196
|
+
findCredentialByKey,
|
|
197
|
+
createChannelCredential,
|
|
198
|
+
pushIntegrationSecrets,
|
|
199
|
+
ensureChannelCredential
|
|
200
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-scoped assistant channel display names (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Base name (e.g. Elsa) → Elsa (dev) | Elsa (tst) | Elsa in production
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const ENV_SUFFIX_PATTERN = /\s+\((dev|tst|test|pro|prod|production)\)$/i;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Strip a trailing environment suffix so --name can be re-run safely.
|
|
15
|
+
* @param {string} name
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function normalizeBaseChannelName(name) {
|
|
19
|
+
if (!name || typeof name !== 'string') {
|
|
20
|
+
throw new Error('Channel base name is required');
|
|
21
|
+
}
|
|
22
|
+
const trimmed = name.trim();
|
|
23
|
+
if (!trimmed) {
|
|
24
|
+
throw new Error('Channel base name is required');
|
|
25
|
+
}
|
|
26
|
+
return trimmed.replace(ENV_SUFFIX_PATTERN, '').trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} environment
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isProductionEnvironment(environment) {
|
|
34
|
+
const env = String(environment || '').toLowerCase();
|
|
35
|
+
return env === 'pro' || env === 'prod' || env === 'production';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve customer-visible channel name for the active environment.
|
|
40
|
+
* @param {string} baseName - Canonical product name (e.g. Elsa)
|
|
41
|
+
* @param {string} environment - dev | tst | pro
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function resolveChannelDisplayName(baseName, environment) {
|
|
45
|
+
const base = normalizeBaseChannelName(baseName);
|
|
46
|
+
const env = String(environment || 'dev').toLowerCase();
|
|
47
|
+
|
|
48
|
+
if (isProductionEnvironment(env)) {
|
|
49
|
+
return base;
|
|
50
|
+
}
|
|
51
|
+
if (env === 'dev') {
|
|
52
|
+
return `${base} (dev)`;
|
|
53
|
+
}
|
|
54
|
+
if (env === 'tst' || env === 'test') {
|
|
55
|
+
return `${base} (tst)`;
|
|
56
|
+
}
|
|
57
|
+
return `${base} (${env})`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = {
|
|
61
|
+
normalizeBaseChannelName,
|
|
62
|
+
isProductionEnvironment,
|
|
63
|
+
resolveChannelDisplayName
|
|
64
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External system channel config helpers (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Patch config.extensions.channel on deployed communication systems
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { getExternalSystem, updateExternalSystem } = require('../api/external-systems.api');
|
|
12
|
+
const { unwrapApiData } = require('../utils/external-system-readiness-core');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object|null} system
|
|
16
|
+
* @returns {Object|null}
|
|
17
|
+
*/
|
|
18
|
+
function getExistingChannelExtension(system) {
|
|
19
|
+
if (!system || typeof system !== 'object') return null;
|
|
20
|
+
const channel = system.config?.extensions?.channel;
|
|
21
|
+
return channel && typeof channel === 'object' ? channel : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Object} params
|
|
26
|
+
* @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
|
|
27
|
+
* @param {string} params.name
|
|
28
|
+
* @param {string} params.baseName
|
|
29
|
+
* @param {string} params.description
|
|
30
|
+
* @param {string} params.artifactHash
|
|
31
|
+
* @param {string} params.status
|
|
32
|
+
* @returns {Object}
|
|
33
|
+
*/
|
|
34
|
+
function buildChannelExtensionBlock(params, target) {
|
|
35
|
+
return {
|
|
36
|
+
type: target.channelType,
|
|
37
|
+
name: params.name,
|
|
38
|
+
baseName: params.baseName,
|
|
39
|
+
description: params.description,
|
|
40
|
+
capabilities: [...target.capabilities],
|
|
41
|
+
status: params.status,
|
|
42
|
+
artifactHash: params.artifactHash,
|
|
43
|
+
credentialKey: target.credentialKey
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {Object} system
|
|
49
|
+
* @param {Object} channelBlock
|
|
50
|
+
* @returns {Object}
|
|
51
|
+
*/
|
|
52
|
+
function mergeChannelIntoSystem(system, channelBlock) {
|
|
53
|
+
const config = system.config && typeof system.config === 'object' ? { ...system.config } : {};
|
|
54
|
+
const extensions =
|
|
55
|
+
config.extensions && typeof config.extensions === 'object' ? { ...config.extensions } : {};
|
|
56
|
+
extensions.channel = channelBlock;
|
|
57
|
+
config.extensions = extensions;
|
|
58
|
+
return { ...system, config, displayName: channelBlock.name };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @async
|
|
63
|
+
* @param {Object} ctx
|
|
64
|
+
* @param {string} ctx.dataplaneUrl
|
|
65
|
+
* @param {Object} ctx.authConfig
|
|
66
|
+
* @param {string} ctx.channelTarget
|
|
67
|
+
* @returns {Promise<Object>}
|
|
68
|
+
*/
|
|
69
|
+
async function fetchCommunicationSystem(ctx) {
|
|
70
|
+
const res = await getExternalSystem(ctx.dataplaneUrl, ctx.channelTarget, ctx.authConfig);
|
|
71
|
+
const system = unwrapApiData(res);
|
|
72
|
+
if (!system || typeof system !== 'object') {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Communication external system "${ctx.channelTarget}" was not found. ` +
|
|
75
|
+
`Run: aifabrix deploy ${ctx.channelTarget}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const kind = system.systemKind || system.systemKindKey;
|
|
79
|
+
if (kind && kind !== 'communication') {
|
|
80
|
+
throw new Error(
|
|
81
|
+
`External system "${ctx.channelTarget}" is not systemKind communication (found: ${kind}).`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
return system;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @async
|
|
89
|
+
* @param {Object} ctx
|
|
90
|
+
* @param {Object} channelBlock
|
|
91
|
+
* @returns {Promise<Object>}
|
|
92
|
+
*/
|
|
93
|
+
async function patchChannelExtension(ctx, channelBlock) {
|
|
94
|
+
const system = await fetchCommunicationSystem(ctx);
|
|
95
|
+
const merged = mergeChannelIntoSystem(system, channelBlock);
|
|
96
|
+
const res = await updateExternalSystem(ctx.dataplaneUrl, ctx.channelTarget, ctx.authConfig, merged);
|
|
97
|
+
return unwrapApiData(res) || merged;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
getExistingChannelExtension,
|
|
102
|
+
buildChannelExtensionBlock,
|
|
103
|
+
mergeChannelIntoSystem,
|
|
104
|
+
fetchCommunicationSystem,
|
|
105
|
+
patchChannelExtension
|
|
106
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate integration/<channelTarget>/ for communication external systems (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Builder generator for channel deploy step 1 scaffolds
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { getChannelTarget } = require('./channel-targets');
|
|
14
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
15
|
+
|
|
16
|
+
/** @type {Record<string, Object>} */
|
|
17
|
+
const AUTH_BLOCKS = {
|
|
18
|
+
slack: {
|
|
19
|
+
authType: 'credential',
|
|
20
|
+
credential: {
|
|
21
|
+
credentialKey: 'slack-app',
|
|
22
|
+
displayName: 'Slack assistant channel',
|
|
23
|
+
method: 'hmac',
|
|
24
|
+
variables: {},
|
|
25
|
+
security: {
|
|
26
|
+
signingSecret: 'kv://slack/signingSecret',
|
|
27
|
+
clientId: 'kv://slack/clientId',
|
|
28
|
+
clientSecret: 'kv://slack/clientSecret'
|
|
29
|
+
},
|
|
30
|
+
writeOnce: true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
'teams-bot': {
|
|
34
|
+
authType: 'credential',
|
|
35
|
+
credential: {
|
|
36
|
+
credentialKey: 'teams-bot',
|
|
37
|
+
displayName: 'Microsoft Teams assistant bot',
|
|
38
|
+
method: 'oidc',
|
|
39
|
+
variables: {
|
|
40
|
+
openIdConfigUrl: 'https://login.botframework.com/v1/.well-known/openid_configuration',
|
|
41
|
+
clientId: 'kv://teams-bot/botAppId',
|
|
42
|
+
expectedIssuer: 'https://api.botframework.com'
|
|
43
|
+
},
|
|
44
|
+
security: {},
|
|
45
|
+
writeOnce: true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
'microsoft-copilot': {
|
|
49
|
+
authType: 'credential',
|
|
50
|
+
credential: {
|
|
51
|
+
credentialKey: 'microsoft-copilot-channel',
|
|
52
|
+
displayName: 'Microsoft Copilot assistant channel',
|
|
53
|
+
method: 'oauth2',
|
|
54
|
+
variables: {
|
|
55
|
+
tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
|
56
|
+
grantType: 'client_credentials'
|
|
57
|
+
},
|
|
58
|
+
security: {
|
|
59
|
+
clientId: 'kv://microsoft-copilot/clientId',
|
|
60
|
+
clientSecret: 'kv://microsoft-copilot/clientSecret'
|
|
61
|
+
},
|
|
62
|
+
writeOnce: true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
chatgpt: {
|
|
66
|
+
authType: 'credential',
|
|
67
|
+
credential: {
|
|
68
|
+
credentialKey: 'chatgpt-channel',
|
|
69
|
+
displayName: 'ChatGPT assistant channel',
|
|
70
|
+
method: 'apikey',
|
|
71
|
+
variables: {},
|
|
72
|
+
security: {
|
|
73
|
+
apiKey: 'kv://chatgpt/apiKey'
|
|
74
|
+
},
|
|
75
|
+
writeOnce: true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
'web-embed': {
|
|
79
|
+
authType: 'credential',
|
|
80
|
+
credential: {
|
|
81
|
+
credentialKey: 'web-embed-channel',
|
|
82
|
+
displayName: 'Web embed assistant channel',
|
|
83
|
+
method: 'apikey',
|
|
84
|
+
variables: {},
|
|
85
|
+
security: {
|
|
86
|
+
apiKey: 'kv://web-embed/apiKey'
|
|
87
|
+
},
|
|
88
|
+
writeOnce: true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} channelTarget
|
|
95
|
+
* @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
|
|
96
|
+
* @returns {Object}
|
|
97
|
+
*/
|
|
98
|
+
function buildAuthenticationBlock(channelTarget, target) {
|
|
99
|
+
const block = AUTH_BLOCKS[channelTarget];
|
|
100
|
+
if (!block) {
|
|
101
|
+
throw new Error(`No authentication block for channel target "${channelTarget}"`);
|
|
102
|
+
}
|
|
103
|
+
const auth = JSON.parse(JSON.stringify(block));
|
|
104
|
+
auth.credential.credentialKey = target.credentialKey;
|
|
105
|
+
return auth;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {string} channelTarget
|
|
110
|
+
* @returns {Object}
|
|
111
|
+
*/
|
|
112
|
+
function buildSystemJson(channelTarget) {
|
|
113
|
+
const target = getChannelTarget(channelTarget);
|
|
114
|
+
const system = {
|
|
115
|
+
key: channelTarget,
|
|
116
|
+
displayName: 'Microsoft Teams assistant bot',
|
|
117
|
+
description: 'Bot Framework assistant channel (not full Teams / Graph integration)',
|
|
118
|
+
type: 'custom',
|
|
119
|
+
systemKind: 'communication',
|
|
120
|
+
enabled: true,
|
|
121
|
+
authentication: buildAuthenticationBlock(channelTarget, target),
|
|
122
|
+
configuration: [],
|
|
123
|
+
dataSources: []
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (channelTarget === 'teams-bot') {
|
|
127
|
+
system.displayName = 'Microsoft Teams assistant bot';
|
|
128
|
+
system.endpoints = [
|
|
129
|
+
{
|
|
130
|
+
type: 'messages',
|
|
131
|
+
path: '/messages',
|
|
132
|
+
router: 'teams_bot_router',
|
|
133
|
+
description: 'Bot Framework messaging endpoint',
|
|
134
|
+
active: true
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: 'notifications',
|
|
138
|
+
path: '/notifications',
|
|
139
|
+
router: 'teams_notifications_router',
|
|
140
|
+
description: 'Proactive notification endpoint',
|
|
141
|
+
active: true
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'config',
|
|
145
|
+
path: '/bot-config',
|
|
146
|
+
router: 'teams_config_router',
|
|
147
|
+
description: 'Bot configuration endpoint',
|
|
148
|
+
active: true
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
system.endpointsActive = true;
|
|
152
|
+
} else {
|
|
153
|
+
system.displayName = `${target.key} assistant channel`;
|
|
154
|
+
system.description = 'Communication external system for assistant channel install (plan 152)';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return system;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} channelTarget
|
|
162
|
+
* @returns {Object}
|
|
163
|
+
*/
|
|
164
|
+
function buildApplicationJson(channelTarget) {
|
|
165
|
+
const target = getChannelTarget(channelTarget);
|
|
166
|
+
const systemFile = `${channelTarget}-system.json`;
|
|
167
|
+
return {
|
|
168
|
+
app: {
|
|
169
|
+
key: channelTarget,
|
|
170
|
+
displayName: `${target.key} assistant channel`,
|
|
171
|
+
description: 'Assistant communication channel — deploy then app add-channel',
|
|
172
|
+
type: 'external'
|
|
173
|
+
},
|
|
174
|
+
externalIntegration: {
|
|
175
|
+
schemaBasePath: './',
|
|
176
|
+
systems: [systemFile],
|
|
177
|
+
dataSources: [],
|
|
178
|
+
autopublish: true,
|
|
179
|
+
version: '1.0.0'
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @param {string} channelTarget
|
|
186
|
+
* @returns {string}
|
|
187
|
+
*/
|
|
188
|
+
function buildEnvTemplate(channelTarget) {
|
|
189
|
+
const kvPrefix = channelTarget === 'web-embed' ? 'web-embed' : channelTarget;
|
|
190
|
+
const lines = [
|
|
191
|
+
`# ${channelTarget} assistant channel — resolve secrets before deploy/add-channel`,
|
|
192
|
+
'# kv:// paths are pushed via aifabrix credential push or add-channel secret sync',
|
|
193
|
+
''
|
|
194
|
+
];
|
|
195
|
+
if (channelTarget === 'slack') {
|
|
196
|
+
lines.push('KV_SLACK_SIGNINGSECRET=kv://slack/signingSecret');
|
|
197
|
+
lines.push('KV_SLACK_CLIENTID=kv://slack/clientId');
|
|
198
|
+
lines.push('KV_SLACK_CLIENTSECRET=kv://slack/clientSecret');
|
|
199
|
+
} else if (channelTarget === 'teams-bot') {
|
|
200
|
+
lines.push('KV_TEAMS_BOT_BOTAPPID=kv://teams-bot/botAppId');
|
|
201
|
+
} else if (channelTarget === 'microsoft-copilot') {
|
|
202
|
+
lines.push('KV_MICROSOFT_COPILOT_CLIENTID=kv://microsoft-copilot/clientId');
|
|
203
|
+
lines.push('KV_MICROSOFT_COPILOT_CLIENTSECRET=kv://microsoft-copilot/clientSecret');
|
|
204
|
+
} else {
|
|
205
|
+
lines.push(`KV_${kvPrefix.toUpperCase().replace(/-/g, '_')}_APIKEY=kv://${kvPrefix}/apiKey`);
|
|
206
|
+
}
|
|
207
|
+
lines.push('');
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {string} channelTarget
|
|
213
|
+
* @param {Object} [options]
|
|
214
|
+
* @param {boolean} [options.force]
|
|
215
|
+
* @param {boolean} [options.allowExperimental]
|
|
216
|
+
* @param {string} [options.projectRoot]
|
|
217
|
+
* @returns {{ integrationDir: string, files: string[] }}
|
|
218
|
+
*/
|
|
219
|
+
function scaffoldChannelIntegration(channelTarget, options = {}) {
|
|
220
|
+
const target = getChannelTarget(channelTarget);
|
|
221
|
+
if (target.experimental && !options.allowExperimental) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Channel target "${channelTarget}" is experimental. Pass --allow-experimental to scaffold.`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const integrationDir = options.projectRoot
|
|
228
|
+
? path.join(path.resolve(options.projectRoot), 'integration', channelTarget)
|
|
229
|
+
: getIntegrationPath(channelTarget);
|
|
230
|
+
if (fs.existsSync(integrationDir) && !options.force) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Integration folder already exists: ${integrationDir}. Use --force to overwrite generated files.`
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fs.mkdirSync(integrationDir, { recursive: true });
|
|
237
|
+
const systemPath = path.join(integrationDir, `${channelTarget}-system.json`);
|
|
238
|
+
const appPath = path.join(integrationDir, 'application.json');
|
|
239
|
+
const envPath = path.join(integrationDir, 'env.template');
|
|
240
|
+
const readmePath = path.join(integrationDir, 'README.md');
|
|
241
|
+
|
|
242
|
+
fs.writeFileSync(systemPath, `${JSON.stringify(buildSystemJson(channelTarget), null, 2)}\n`, 'utf8');
|
|
243
|
+
fs.writeFileSync(appPath, `${JSON.stringify(buildApplicationJson(channelTarget), null, 2)}\n`, 'utf8');
|
|
244
|
+
fs.writeFileSync(envPath, buildEnvTemplate(channelTarget), 'utf8');
|
|
245
|
+
fs.writeFileSync(
|
|
246
|
+
readmePath,
|
|
247
|
+
[
|
|
248
|
+
`# ${channelTarget} assistant channel`,
|
|
249
|
+
'',
|
|
250
|
+
'## Two-step install',
|
|
251
|
+
'',
|
|
252
|
+
'```bash',
|
|
253
|
+
`aifabrix deploy ${channelTarget}`,
|
|
254
|
+
`aifabrix app add-channel ${channelTarget} --name "Your Assistant"`,
|
|
255
|
+
'```',
|
|
256
|
+
'',
|
|
257
|
+
'Generated by `aifabrix channel scaffold`. Secrets belong in kv:// env.template only.',
|
|
258
|
+
''
|
|
259
|
+
].join('\n'),
|
|
260
|
+
'utf8'
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
integrationDir,
|
|
265
|
+
files: [systemPath, appPath, envPath, readmePath]
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = {
|
|
270
|
+
buildSystemJson,
|
|
271
|
+
buildApplicationJson,
|
|
272
|
+
buildEnvTemplate,
|
|
273
|
+
scaffoldChannelIntegration
|
|
274
|
+
};
|