@aifabrix/builder 2.51.1 → 2.52.1
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/jest.isolated-projects.js +25 -2
- package/jest.projects.js +4 -1
- package/lib/api/datasources-extended.api.js +8 -8
- package/lib/api/keycloak.api.js +92 -0
- package/lib/api/token-providers.api.js +77 -0
- package/lib/api/types/keycloak.types.js +23 -0
- package/lib/api/types/token-providers.types.js +65 -0
- package/lib/app/down.js +16 -1
- package/lib/app/run-container-start.js +4 -0
- package/lib/app/run-deploy-job.js +135 -27
- package/lib/app/run-env-compose.js +32 -0
- package/lib/app/run-env-recovery.js +35 -0
- package/lib/app/run-helpers.js +5 -6
- package/lib/app/run.js +11 -3
- package/lib/channels/add-channel.js +126 -19
- package/lib/channels/approval-guides/chatgpt.js +12 -9
- package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
- package/lib/channels/channel-add-ensure.js +128 -0
- package/lib/channels/channel-artifact-generator.js +11 -1
- package/lib/channels/channel-branding.js +94 -0
- package/lib/channels/channel-credential.js +8 -0
- package/lib/channels/channel-discovery.js +330 -0
- package/lib/channels/channel-external-system.js +1 -1
- package/lib/channels/channel-runtime-resolver.js +103 -14
- package/lib/channels/channel-status-display.js +7 -1
- package/lib/channels/channel-targets.js +18 -1
- package/lib/channels/chatgpt-runtime-contract.js +1 -1
- package/lib/channels/microsoft-copilot-channel-add.js +138 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
- package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
- package/lib/channels/microsoft-copilot-icons.js +74 -0
- package/lib/channels/microsoft-copilot-limits.js +84 -0
- package/lib/channels/microsoft-copilot-package.js +130 -49
- package/lib/channels/microsoft-copilot-validate.js +197 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/infra-guided-footers.js +1 -1
- package/lib/cli/infra-guided-public-url-warn.js +55 -0
- package/lib/cli/infra-guided.js +78 -62
- package/lib/cli/setup-app.help.js +4 -0
- package/lib/cli/setup-app.test-commands.js +59 -28
- package/lib/cli/setup-external-system.js +50 -12
- package/lib/cli/setup-infra.js +31 -4
- package/lib/commands/channel-add-setup.js +35 -10
- package/lib/commands/channel.js +27 -12
- package/lib/commands/deploy-gate-orchestrator.js +3 -2
- package/lib/commands/governance-verify-external.js +2 -5
- package/lib/commands/integration-definition.js +158 -0
- package/lib/commands/lifecycle-external.js +14 -7
- package/lib/commands/repair-auth-apply.js +19 -5
- package/lib/commands/repair-datasource-sync-operations.js +4 -1
- package/lib/commands/role-assistant-test-output.js +200 -0
- package/lib/commands/role-assistant.js +131 -114
- package/lib/commands/test-governance-external.js +2 -5
- package/lib/commands/up-miso.js +61 -24
- package/lib/commands/upload-data-sync-finalize.js +20 -3
- package/lib/commands/verify-governance-external.js +2 -5
- package/lib/commands/verify-operations-external.js +2 -5
- package/lib/commands/verify-operations-step-plan.js +31 -0
- package/lib/commands/verify-trust-command-action.js +2 -5
- package/lib/datasource/capability/capability-resolve.js +15 -5
- package/lib/datasource/capability/copy-operations.js +14 -4
- package/lib/datasource/datasource-identifier.js +4 -3
- package/lib/datasource/integration-context.js +3 -3
- package/lib/datasource/resolve-app.js +90 -8
- package/lib/datasource/test-e2e.js +2 -2
- package/lib/datasource/unified-validation-run-body.js +15 -1
- package/lib/datasource/unified-validation-run-resolve.js +2 -2
- package/lib/external-system/deploy-helpers.js +9 -5
- package/lib/external-system/deploy.js +2 -1
- package/lib/external-system/test-system-level.js +21 -3
- package/lib/external-system/test.js +4 -4
- package/lib/generator/builders.js +9 -15
- package/lib/infrastructure/index.js +19 -4
- package/lib/integration-definition/apply.js +478 -0
- package/lib/integration-definition/canonical-json.js +51 -0
- package/lib/integration-definition/emit-suite-bid.js +94 -0
- package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
- package/lib/integration-definition/enrich-openapi-operations.js +434 -0
- package/lib/integration-definition/from-discovery.js +339 -0
- package/lib/integration-definition/hash.js +37 -0
- package/lib/integration-definition/index.js +37 -0
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
- package/lib/integration-definition/resource-type-catalog.js +59 -0
- package/lib/integration-definition/system-kind-enum.js +40 -0
- package/lib/integration-definition/validate.js +148 -0
- package/lib/programmatic/builder-api-request.js +5 -1
- package/lib/programmatic/builder-help-governance.js +3 -2
- package/lib/programmatic/capability-registry.js +19 -0
- package/lib/programmatic/openapi-component-schemas.js +6 -1
- package/lib/programmatic/openapi-descriptions.js +40 -0
- package/lib/programmatic/openapi-envelope-examples.js +269 -0
- package/lib/programmatic/openapi-operation-bindings.js +45 -0
- package/lib/programmatic/openapi-request-schemas.js +14 -0
- package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
- package/lib/programmatic/openapi-result-schemas.js +46 -99
- package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
- package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
- package/lib/programmatic/openapi-schema-helpers.js +9 -9
- package/lib/programmatic/openapi-spec.js +11 -1
- package/lib/programmatic/orchestration-openapi-meta.js +10 -0
- package/lib/programmatic/route-handler-map.js +29 -1
- package/lib/programmatic/run-channel-package.js +236 -0
- package/lib/programmatic/run-integration-definition.js +69 -0
- package/lib/role-assistant/knowledge-markdown-frontmatter.js +75 -0
- package/lib/role-assistant/knowledge-sync-logging.js +58 -0
- package/lib/role-assistant/knowledge-sync.js +104 -89
- package/lib/role-assistant/test-cases-bid.js +155 -0
- package/lib/role-assistant/test-cases-evaluate.js +9 -1
- package/lib/role-assistant/test-cases-honesty.js +24 -10
- package/lib/role-assistant/test-cases.js +28 -0
- package/lib/role-assistant/test-runner-bid-apply.js +122 -0
- package/lib/role-assistant/test-runner-bid-document.js +278 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +289 -46
- package/lib/role-assistant/test-runner-workhub.js +121 -25
- package/lib/role-assistant/test-suite.js +119 -0
- package/lib/schema/application-schema.json +64 -32
- package/lib/schema/external-datasource.schema.json +103 -17
- package/lib/schema/external-system.schema.json +54 -3
- package/lib/schema/infra.parameter.yaml +0 -20
- package/lib/schema/integration-definition.schema.json +195 -0
- package/lib/utils/authentication-shape.js +6 -0
- package/lib/utils/certify-external-workspace.js +40 -0
- package/lib/utils/docker-build.js +3 -3
- package/lib/utils/docker-run-ephemeral.js +77 -17
- package/lib/utils/health-check-public-warn.js +31 -13
- package/lib/utils/health-check.js +11 -1
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/paths.js +13 -15
- package/lib/utils/promote-dev-docker-image.js +97 -18
- package/lib/utils/role-assistant-paths.js +76 -14
- package/lib/utils/transient-developer-id.js +28 -0
- package/lib/utils/upload-sync-options.js +7 -4
- package/lib/validation/reserved-system-key-prefixes.js +56 -0
- package/lib/validation/validate.js +54 -19
- package/package.json +1 -1
- package/scripts/pnpm/af.mjs +24 -9
- package/scripts/pnpm/help.mjs +1 -1
- package/templates/applications/builder-api/application.yaml +3 -3
- package/templates/applications/dataplane/application.yaml +5 -5
- package/templates/applications/dataplane/env.template +8 -7
- package/templates/applications/dataplane/rbac.yaml +10 -10
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/env.template +5 -2
- package/templates/applications/miso-controller/application.yaml +25 -11
- package/templates/applications/miso-controller/env.template +24 -5
- package/templates/channels/README.md +11 -1
- package/templates/channels/brand/color-192.png +0 -0
- package/templates/channels/brand/dark/128.png +0 -0
- package/templates/channels/brand/dark/192.png +0 -0
- package/templates/channels/brand/dark/256.png +0 -0
- package/templates/channels/brand/dark/32.png +0 -0
- package/templates/channels/brand/dark/512.png +0 -0
- package/templates/channels/brand/dark/64.png +0 -0
- package/templates/channels/brand/light/128.png +0 -0
- package/templates/channels/brand/light/192.png +0 -0
- package/templates/channels/brand/light/256.png +0 -0
- package/templates/channels/brand/light/32.png +0 -0
- package/templates/channels/brand/light/512.png +0 -0
- package/templates/channels/brand/light/64.png +0 -0
- package/templates/channels/brand/mono-white/128.png +0 -0
- package/templates/channels/brand/mono-white/192.png +0 -0
- package/templates/channels/brand/mono-white/256.png +0 -0
- package/templates/channels/brand/mono-white/32.png +0 -0
- package/templates/channels/brand/mono-white/512.png +0 -0
- package/templates/channels/brand/mono-white/64.png +0 -0
- package/templates/channels/brand/outline-32.png +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +76 -36
- package/templates/channels/chatgpt/integration/README.md +4 -4
- package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
- package/templates/channels/chatgpt/manifest.template.json.hbs +3 -3
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
- package/templates/channels/microsoft-copilot/integration/README.md +2 -3
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
- package/templates/channels/slack/install.template.md.hbs +6 -1
- package/templates/channels/slack/integration/README.md +2 -4
- package/templates/channels/teams-bot/install.template.md.hbs +4 -0
- package/templates/channels/teams-bot/integration/README.md +3 -5
- package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
- package/templates/python/docker-compose.hbs +10 -0
- package/templates/typescript/docker-compose.hbs +10 -0
- /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Last-moment run env recovery for the shared applications workspace.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Builds the callback used immediately before Docker Compose starts
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const runEnvCompose = require('./run-env-compose');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build a recovery hook for run env files in the shared workspace.
|
|
15
|
+
* @param {string} appName - Application name
|
|
16
|
+
* @param {Object} prepared - Prepared run context
|
|
17
|
+
* @returns {Function} Async recovery hook
|
|
18
|
+
*/
|
|
19
|
+
function buildRunEnvRecovery(appName, prepared) {
|
|
20
|
+
return () =>
|
|
21
|
+
runEnvCompose.ensureRunEnvFilesPresent(
|
|
22
|
+
appName,
|
|
23
|
+
prepared.appConfig,
|
|
24
|
+
prepared.runEnvPath,
|
|
25
|
+
prepared.runEnvAdminPath,
|
|
26
|
+
prepared.appConfig.developerId,
|
|
27
|
+
{
|
|
28
|
+
runEnvKey: prepared.mergedRunOptions.env,
|
|
29
|
+
effectiveEnvironmentScopedResources:
|
|
30
|
+
prepared.mergedRunOptions.effectiveEnvironmentScopedResources
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { buildRunEnvRecovery };
|
package/lib/app/run-helpers.js
CHANGED
|
@@ -253,18 +253,17 @@ async function checkInfraHealthOrThrow(debug, appConfig) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
/**
|
|
256
|
-
*
|
|
256
|
+
* Refresh the shared developer run directory for the current application.
|
|
257
|
+
*
|
|
258
|
+
* The applications-dev-* directory is shared by all applications. Always copy
|
|
259
|
+
* the selected app so files from a previous run cannot leak into this run.
|
|
257
260
|
* @async
|
|
258
261
|
* @param {string} appName - Application name
|
|
259
262
|
* @param {string} developerId - Developer ID
|
|
260
263
|
* @returns {Promise<string>} Dev directory path
|
|
261
264
|
*/
|
|
262
265
|
async function ensureDevDirectory(appName, developerId) {
|
|
263
|
-
|
|
264
|
-
if (!fsSync.existsSync(devDir)) {
|
|
265
|
-
await buildCopy.copyBuilderToDevDirectory(appName, developerId);
|
|
266
|
-
}
|
|
267
|
-
return devDir;
|
|
266
|
+
return buildCopy.copyBuilderToDevDirectory(appName, developerId);
|
|
268
267
|
}
|
|
269
268
|
|
|
270
269
|
/**
|
package/lib/app/run.js
CHANGED
|
@@ -28,6 +28,7 @@ const helpers = require('./run-helpers');
|
|
|
28
28
|
const { ensureReloadSync, logReloadDevSummary } = require('./run-reload-sync');
|
|
29
29
|
const { logRestartDevMountSummary } = require('./restart-display');
|
|
30
30
|
const { checkAndStopContainer } = require('./run-stop-containers');
|
|
31
|
+
const { buildRunEnvRecovery } = require('./run-env-recovery');
|
|
31
32
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
32
33
|
const { resolveRunProxyPersistEnabled, resolveRunEffectiveNoProxy } = require('../utils/run-cli-flags');
|
|
33
34
|
|
|
@@ -178,7 +179,8 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
|
|
|
178
179
|
runEnvPath = null,
|
|
179
180
|
runEnvAdminPath = null,
|
|
180
181
|
runOptions = {},
|
|
181
|
-
devMountPath = null
|
|
182
|
+
devMountPath = null,
|
|
183
|
+
ensureRunEnvFiles = null
|
|
182
184
|
} = opts;
|
|
183
185
|
const misoEnvironment = opts.misoEnvironment ?? composeGenerator.resolveMisoEnvironment(runOptions);
|
|
184
186
|
try {
|
|
@@ -188,6 +190,7 @@ async function startAppContainer(appName, tempComposePath, hostPort, appConfig,
|
|
|
188
190
|
runEnvAdminPath,
|
|
189
191
|
runOptions,
|
|
190
192
|
devMountPath,
|
|
193
|
+
ensureRunEnvFiles,
|
|
191
194
|
misoEnvironment
|
|
192
195
|
});
|
|
193
196
|
await helpers.displayRunStatus(
|
|
@@ -410,15 +413,20 @@ async function runApp(appName, options = {}) {
|
|
|
410
413
|
fullImage: prepared.mergedRunOptions.image,
|
|
411
414
|
runEnvPath: prepared.runEnvPath,
|
|
412
415
|
devMountPath: prepared.devMountPath || null,
|
|
413
|
-
debug
|
|
416
|
+
debug,
|
|
417
|
+
// Guided installers already show a top-level spinner; keep migrate/deployJob logs out of TTY.
|
|
418
|
+
quiet:
|
|
419
|
+
options.silentPoll === true || typeof options.onPollText === 'function'
|
|
414
420
|
});
|
|
421
|
+
const ensureRunEnvFiles = buildRunEnvRecovery(appName, prepared);
|
|
415
422
|
await startAppContainer(appName, prepared.tempComposePath, prepared.hostPort, prepared.appConfig, {
|
|
416
423
|
debug,
|
|
417
424
|
runEnvPath: prepared.runEnvPath,
|
|
418
425
|
runEnvAdminPath: prepared.runEnvAdminPath,
|
|
419
426
|
runOptions: prepared.mergedRunOptions,
|
|
420
427
|
devMountPath: prepared.devMountPath,
|
|
421
|
-
runScopeOpts: prepared.runScopeOpts
|
|
428
|
+
runScopeOpts: prepared.runScopeOpts,
|
|
429
|
+
ensureRunEnvFiles
|
|
422
430
|
});
|
|
423
431
|
} catch (error) {
|
|
424
432
|
if (debug) {
|
|
@@ -10,9 +10,15 @@
|
|
|
10
10
|
|
|
11
11
|
const { getChannelTarget, getDefaultChannelDescription } = require('./channel-targets');
|
|
12
12
|
const { resolveAppDescription } = require('./channel-description');
|
|
13
|
+
const path = require('path');
|
|
13
14
|
const { resolveChannelRuntimeValues } = require('./channel-runtime-resolver');
|
|
14
15
|
const { generateChannelArtifact } = require('./channel-artifact-generator');
|
|
15
16
|
const { fetchRuntimeLayerBToolNames } = require('./runtime-tool-catalog');
|
|
17
|
+
const { loadChannelPackageContext } = require('./channel-discovery');
|
|
18
|
+
const {
|
|
19
|
+
readChannelBranding,
|
|
20
|
+
mergeChannelBranding
|
|
21
|
+
} = require('./channel-branding');
|
|
16
22
|
const {
|
|
17
23
|
getExistingChannelExtension,
|
|
18
24
|
buildChannelExtensionBlock,
|
|
@@ -39,9 +45,14 @@ const {
|
|
|
39
45
|
} = require('./channel-display-name');
|
|
40
46
|
const {
|
|
41
47
|
displayExistingChannelStatus,
|
|
42
|
-
displayChannelInstallResult
|
|
43
|
-
displayChannelError
|
|
48
|
+
displayChannelInstallResult
|
|
44
49
|
} = require('./channel-status-display');
|
|
50
|
+
const { ensureChannelReadyForAdd } = require('./channel-add-ensure');
|
|
51
|
+
const {
|
|
52
|
+
assertMicrosoftCopilotOauthReference,
|
|
53
|
+
assertMicrosoftCopilotOauthReferenceBeforeForce,
|
|
54
|
+
loadMicrosoftCopilotEntraContext
|
|
55
|
+
} = require('./microsoft-copilot-channel-add');
|
|
45
56
|
|
|
46
57
|
/**
|
|
47
58
|
* @param {typeof import('./channel-targets').CHANNEL_TARGETS[string]} target
|
|
@@ -252,6 +263,9 @@ async function completeLiveInstall(params) {
|
|
|
252
263
|
capabilities: target.capabilities,
|
|
253
264
|
artifactPath: artifact.outputPath,
|
|
254
265
|
installGuidePath: artifact.installPath,
|
|
266
|
+
zipPath: artifact.zipPath,
|
|
267
|
+
installReady: artifact.installReady,
|
|
268
|
+
blocker: artifact.blocker,
|
|
255
269
|
validation: toValidationDisplay(validation),
|
|
256
270
|
approvalGuide
|
|
257
271
|
});
|
|
@@ -272,7 +286,10 @@ function finishDryRunInstall(params) {
|
|
|
272
286
|
credentialKey: params.target.credentialKey,
|
|
273
287
|
capabilities: params.target.capabilities,
|
|
274
288
|
artifactPath: params.artifact.outputPath,
|
|
275
|
-
installGuidePath: params.artifact.installPath
|
|
289
|
+
installGuidePath: params.artifact.installPath,
|
|
290
|
+
zipPath: params.artifact.zipPath,
|
|
291
|
+
installReady: params.artifact.installReady,
|
|
292
|
+
blocker: params.artifact.blocker
|
|
276
293
|
});
|
|
277
294
|
return { dryRun: true, channelBlock: params.channelBlock, artifact: params.artifact };
|
|
278
295
|
}
|
|
@@ -281,12 +298,78 @@ function finishDryRunInstall(params) {
|
|
|
281
298
|
* @param {Object} params
|
|
282
299
|
* @returns {Promise<{ artifact: Object, channelBlock: Object, status: string }>}
|
|
283
300
|
*/
|
|
301
|
+
/**
|
|
302
|
+
* @param {Object|null} packageContext
|
|
303
|
+
* @param {Object} options
|
|
304
|
+
* @param {'iconColorPath'|'iconOutlinePath'} key
|
|
305
|
+
* @returns {string|undefined}
|
|
306
|
+
*/
|
|
307
|
+
function brandingIconOrOption(packageContext, options, key) {
|
|
308
|
+
if (typeof options[key] === 'string' && options[key].trim()) {
|
|
309
|
+
return options[key].trim();
|
|
310
|
+
}
|
|
311
|
+
if (packageContext && typeof packageContext[key] === 'string' && packageContext[key].trim()) {
|
|
312
|
+
return packageContext[key].trim();
|
|
313
|
+
}
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function resolvePackageContextForInstall(params) {
|
|
318
|
+
const { channelTarget, options, name, description, ctx } = params;
|
|
319
|
+
const packagingTargets = new Set(['chatgpt', 'microsoft-copilot', 'slack', 'teams-bot']);
|
|
320
|
+
if (!packagingTargets.has(channelTarget)) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
324
|
+
const workspaceDir = path.join(projectRoot, 'integration', channelTarget);
|
|
325
|
+
const branding = mergeChannelBranding(readChannelBranding(workspaceDir), {
|
|
326
|
+
displayName: options.overrideDisplayName || undefined,
|
|
327
|
+
description: options.overrideDescription || undefined,
|
|
328
|
+
iconColorPath: options.iconColorPath || undefined,
|
|
329
|
+
iconOutlinePath: options.iconOutlinePath || undefined
|
|
330
|
+
});
|
|
331
|
+
let packageContext;
|
|
332
|
+
try {
|
|
333
|
+
packageContext = await loadChannelPackageContext({
|
|
334
|
+
dataplaneBaseUrl: ctx.dataplaneUrl,
|
|
335
|
+
authConfig: ctx.authConfig,
|
|
336
|
+
channelTarget,
|
|
337
|
+
branding
|
|
338
|
+
});
|
|
339
|
+
} catch {
|
|
340
|
+
packageContext = {
|
|
341
|
+
displayName: name,
|
|
342
|
+
description,
|
|
343
|
+
governedToolNames: [],
|
|
344
|
+
mcpServerUrl: null,
|
|
345
|
+
installReady: false,
|
|
346
|
+
blocker: 'discoveryUnavailable',
|
|
347
|
+
authentication: null,
|
|
348
|
+
authenticationCapabilities: []
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
...packageContext,
|
|
353
|
+
iconColorPath: branding.iconColorPath,
|
|
354
|
+
iconOutlinePath: branding.iconOutlinePath
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
284
358
|
async function buildInstallArtifacts(params) {
|
|
285
359
|
const { channelTarget, options, target, existing, name, baseName, description, ctx } = params;
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
360
|
+
const packageContext = await resolvePackageContextForInstall(params);
|
|
361
|
+
let runtimeToolNames = [];
|
|
362
|
+
if (channelTarget === 'chatgpt' || channelTarget === 'microsoft-copilot') {
|
|
363
|
+
runtimeToolNames =
|
|
364
|
+
packageContext?.governedToolNames?.length > 0
|
|
365
|
+
? packageContext.governedToolNames
|
|
366
|
+
: await fetchRuntimeLayerBToolNames(ctx.dataplaneUrl, ctx.authConfig, channelTarget);
|
|
367
|
+
}
|
|
368
|
+
let entraProvider = null;
|
|
369
|
+
let entraIdentityProvider = null;
|
|
370
|
+
if (channelTarget === 'microsoft-copilot') {
|
|
371
|
+
({ entraProvider, entraIdentityProvider } = await loadMicrosoftCopilotEntraContext(ctx));
|
|
372
|
+
}
|
|
290
373
|
const runtimeValues = resolveChannelRuntimeValues({
|
|
291
374
|
dataplaneBaseUrl: ctx.dataplaneUrl,
|
|
292
375
|
channelTarget,
|
|
@@ -296,7 +379,14 @@ async function buildInstallArtifacts(params) {
|
|
|
296
379
|
appDescription: description,
|
|
297
380
|
icon: options.icon,
|
|
298
381
|
commandPrefix: options.commandPrefix,
|
|
299
|
-
layerBToolNames: runtimeToolNames
|
|
382
|
+
layerBToolNames: runtimeToolNames,
|
|
383
|
+
packageContext,
|
|
384
|
+
entraProvider,
|
|
385
|
+
entraIdentityProvider,
|
|
386
|
+
oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId,
|
|
387
|
+
scopes: options.scopes,
|
|
388
|
+
iconColorPath: brandingIconOrOption(packageContext, options, 'iconColorPath'),
|
|
389
|
+
iconOutlinePath: brandingIconOrOption(packageContext, options, 'iconOutlinePath')
|
|
300
390
|
});
|
|
301
391
|
const projectRoot = options.projectRoot || process.cwd();
|
|
302
392
|
const artifact = generateArtifactWithAudit(channelTarget, {
|
|
@@ -313,17 +403,39 @@ async function buildInstallArtifacts(params) {
|
|
|
313
403
|
return { artifact, channelBlock, status };
|
|
314
404
|
}
|
|
315
405
|
|
|
406
|
+
/**
|
|
407
|
+
* @param {string} channelTarget
|
|
408
|
+
* @param {Object} options
|
|
409
|
+
* @returns {Object}
|
|
410
|
+
*/
|
|
411
|
+
function assertChannelAddOptions(channelTarget, options) {
|
|
412
|
+
const target = getChannelTarget(channelTarget);
|
|
413
|
+
if (target.experimental) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Channel target "${channelTarget}" is experimental. Confirm product requirement before use.`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
if (!options.name || typeof options.name !== 'string' || !options.name.trim()) {
|
|
419
|
+
throw new Error('--name is required for channel add');
|
|
420
|
+
}
|
|
421
|
+
return target;
|
|
422
|
+
}
|
|
423
|
+
|
|
316
424
|
/**
|
|
317
425
|
* @param {string} channelTarget
|
|
318
426
|
* @param {Object} options
|
|
319
427
|
* @returns {Promise<Object>}
|
|
320
428
|
*/
|
|
321
429
|
async function runAddChannel(channelTarget, options = {}) {
|
|
322
|
-
const
|
|
430
|
+
const target = assertChannelAddOptions(channelTarget, options);
|
|
431
|
+
if (channelTarget === 'microsoft-copilot' && options.force === true) {
|
|
432
|
+
await assertMicrosoftCopilotOauthReferenceBeforeForce(options);
|
|
433
|
+
}
|
|
434
|
+
await ensureChannelReadyForAdd(channelTarget, options);
|
|
435
|
+
const { name, baseName, description, ctx, existing } = await prepareChannelContext(
|
|
323
436
|
channelTarget,
|
|
324
437
|
options
|
|
325
438
|
);
|
|
326
|
-
|
|
327
439
|
const skipped = showExistingChannelStatus({
|
|
328
440
|
existing,
|
|
329
441
|
options,
|
|
@@ -333,7 +445,9 @@ async function runAddChannel(channelTarget, options = {}) {
|
|
|
333
445
|
ctx
|
|
334
446
|
});
|
|
335
447
|
if (skipped) return skipped;
|
|
336
|
-
|
|
448
|
+
if (channelTarget === 'microsoft-copilot') {
|
|
449
|
+
await assertMicrosoftCopilotOauthReference({ options, ctx, name, baseName, description });
|
|
450
|
+
}
|
|
337
451
|
const { artifact, channelBlock, status } = await buildInstallArtifacts({
|
|
338
452
|
channelTarget,
|
|
339
453
|
options,
|
|
@@ -344,11 +458,9 @@ async function runAddChannel(channelTarget, options = {}) {
|
|
|
344
458
|
description,
|
|
345
459
|
ctx
|
|
346
460
|
});
|
|
347
|
-
|
|
348
461
|
if (options.dryRun) {
|
|
349
462
|
return finishDryRunInstall({ channelTarget, name, baseName, target, artifact, channelBlock });
|
|
350
463
|
}
|
|
351
|
-
|
|
352
464
|
return completeLiveInstall({
|
|
353
465
|
ctx,
|
|
354
466
|
channelTarget,
|
|
@@ -367,12 +479,7 @@ async function runAddChannel(channelTarget, options = {}) {
|
|
|
367
479
|
* @param {Object} options
|
|
368
480
|
*/
|
|
369
481
|
async function addChannelCommand(channelTarget, options = {}) {
|
|
370
|
-
|
|
371
|
-
await runAddChannel(channelTarget, options);
|
|
372
|
-
} catch (err) {
|
|
373
|
-
displayChannelError(err.message || String(err));
|
|
374
|
-
throw err;
|
|
375
|
-
}
|
|
482
|
+
await runAddChannel(channelTarget, options);
|
|
376
483
|
}
|
|
377
484
|
|
|
378
485
|
module.exports = {
|
|
@@ -16,17 +16,20 @@ const { formatCredentialPortalStep } = require('./credential-portal');
|
|
|
16
16
|
*/
|
|
17
17
|
function buildChatGptApprovalGuide(ctx) {
|
|
18
18
|
return {
|
|
19
|
-
portalUrl: 'https://
|
|
19
|
+
portalUrl: 'https://chatgpt.com/',
|
|
20
20
|
steps: [
|
|
21
|
-
'Sign in to
|
|
22
|
-
'Enable Developer mode
|
|
23
|
-
'Create a custom MCP app
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
formatCredentialPortalStep(
|
|
27
|
-
|
|
21
|
+
'Sign in to the ChatGPT web app as a workspace admin or owner.',
|
|
22
|
+
'Enable Developer mode for custom MCP apps (Business: Workspace settings → Apps, or Settings → Apps → Advanced settings; Enterprise/Edu: grant Developer mode under Permissions & roles → Connected data first).',
|
|
23
|
+
'Create a custom MCP app with the Remote MCP URL from INSTALL.md (channel-chatgpt.json is a deployment reference, not a plugin archive).',
|
|
24
|
+
'Select OAuth (authorization-code + PKCE S256). Do not configure a custom API key for a remote custom MCP app.',
|
|
25
|
+
'Scan tools, complete OAuth if prompted, review discovered governed tools, then save the app as a draft.',
|
|
26
|
+
formatCredentialPortalStep(
|
|
27
|
+
ctx,
|
|
28
|
+
`${ctx.credentialKey || 'channel'} credential references (not secrets to paste into ChatGPT)`
|
|
29
|
+
),
|
|
30
|
+
'Test representative workflows, then publish the app and configure workspace access and action controls.',
|
|
28
31
|
`After ChatGPT lists the app as active, re-run: ${ctx.resumeCommand}`,
|
|
29
|
-
'Optional: use --probe for AI Fabrix-side
|
|
32
|
+
'Optional: use --probe for AI Fabrix-side Runtime checks; ChatGPT Scan tools / OAuth / publication remain workspace-admin responsibilities.'
|
|
30
33
|
]
|
|
31
34
|
};
|
|
32
35
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Microsoft Copilot channel
|
|
2
|
+
* Microsoft Copilot channel install / approval steps.
|
|
3
3
|
*
|
|
4
|
-
* @fileoverview
|
|
4
|
+
* @fileoverview Teams custom-app upload guide aligned with install templates
|
|
5
5
|
* @author AI Fabrix Team
|
|
6
6
|
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
ENTRA_SSO_REGISTRATION_URL
|
|
13
|
+
} = require('../microsoft-copilot-entra-sso-form');
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* @param {Object} ctx
|
|
@@ -16,16 +18,18 @@ const { formatCredentialPortalStep } = require('./credential-portal');
|
|
|
16
18
|
*/
|
|
17
19
|
function buildMicrosoftCopilotApprovalGuide(ctx) {
|
|
18
20
|
return {
|
|
19
|
-
portalUrl:
|
|
21
|
+
portalUrl: ENTRA_SSO_REGISTRATION_URL,
|
|
20
22
|
steps: [
|
|
21
|
-
'
|
|
22
|
-
'Upload
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
`
|
|
28
|
-
'
|
|
23
|
+
'Open Microsoft Teams with your target Microsoft 365 account.',
|
|
24
|
+
'Go to Apps → Manage your apps → Upload an app → Upload a custom app.',
|
|
25
|
+
'Upload only appPackage.<env>.zip (do not upload individual manifest files or channel-microsoft-copilot.json).',
|
|
26
|
+
'Review permissions, select Add, and open the installed app. Users stay on the Microsoft 365 Entra session — no separate AI Fabrix sign-in.',
|
|
27
|
+
'If prompted, complete Entra admin consent for the Runtime MCP delegated scope.',
|
|
28
|
+
'If Upload a custom app is unavailable, ask a Teams admin to enable custom app uploads or deploy the package centrally.',
|
|
29
|
+
`Register Entra SSO first at ${ENTRA_SSO_REGISTRATION_URL} using the Base URL / Client ID / Scope values printed by the CLI (or INSTALL.md). Pass the registration id as --oauth-plugin-vault-reference-id.`,
|
|
30
|
+
'Do not configure AI Fabrix OAuth client secrets under Connected Systems → Authentication for this Entra SSO flow.',
|
|
31
|
+
'Confirm Runtime MCP and governed tools using INSTALL.md, then re-run with --probe after Teams install.',
|
|
32
|
+
`After the app is available in Teams, re-run: ${ctx.resumeCommand}`
|
|
29
33
|
]
|
|
30
34
|
};
|
|
31
35
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure local scaffold + dataplane upload before channel add install.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview One-command channel add prelude (scaffold → upload if needed)
|
|
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 chalk = require('chalk');
|
|
14
|
+
const logger = require('../utils/logger');
|
|
15
|
+
const { formatSuccessLine, metadata } = require('../utils/cli-test-layout-chalk');
|
|
16
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
17
|
+
const { getExternalSystem } = require('../api/external-systems.api');
|
|
18
|
+
const { unwrapApiData } = require('../utils/external-system-readiness-core');
|
|
19
|
+
const { scaffoldChannelIntegration } = require('./channel-integration-generator');
|
|
20
|
+
const { resolveChannelAuthContext } = require('./channel-auth-context');
|
|
21
|
+
const { uploadExternalSystem } = require('../commands/upload');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} channelTarget
|
|
25
|
+
* @param {string} [projectRoot]
|
|
26
|
+
* @returns {string}
|
|
27
|
+
*/
|
|
28
|
+
function resolveIntegrationDir(channelTarget, projectRoot) {
|
|
29
|
+
if (projectRoot) {
|
|
30
|
+
return path.join(path.resolve(projectRoot), 'integration', channelTarget);
|
|
31
|
+
}
|
|
32
|
+
return getIntegrationPath(channelTarget);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} ctx
|
|
37
|
+
* @param {string} channelTarget
|
|
38
|
+
* @returns {Promise<boolean>}
|
|
39
|
+
*/
|
|
40
|
+
async function isCommunicationSystemUploaded(ctx, channelTarget) {
|
|
41
|
+
const res = await getExternalSystem(ctx.dataplaneUrl, channelTarget, ctx.authConfig);
|
|
42
|
+
const system = unwrapApiData(res);
|
|
43
|
+
return !!(system && typeof system === 'object');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Scaffold integration/<target>/ when missing (does not overwrite).
|
|
48
|
+
*
|
|
49
|
+
* @param {string} channelTarget
|
|
50
|
+
* @param {Object} options
|
|
51
|
+
* @returns {Promise<{ scaffolded: boolean, integrationDir: string }>}
|
|
52
|
+
*/
|
|
53
|
+
async function ensureChannelScaffold(channelTarget, options = {}) {
|
|
54
|
+
const integrationDir = resolveIntegrationDir(channelTarget, options.projectRoot);
|
|
55
|
+
if (fs.existsSync(integrationDir)) {
|
|
56
|
+
return { scaffolded: false, integrationDir };
|
|
57
|
+
}
|
|
58
|
+
const result = await scaffoldChannelIntegration(channelTarget, {
|
|
59
|
+
projectRoot: options.projectRoot,
|
|
60
|
+
allowExperimental: options.allowExperimental === true
|
|
61
|
+
});
|
|
62
|
+
if (options.quiet !== true) {
|
|
63
|
+
logger.log(formatSuccessLine(`Scaffolded integration at ${result.integrationDir}`));
|
|
64
|
+
for (const file of result.files) {
|
|
65
|
+
logger.log(metadata(file));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return { scaffolded: true, integrationDir: result.integrationDir };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Upload when missing on dataplane; with --force upload every time.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} channelTarget
|
|
75
|
+
* @param {Object} options
|
|
76
|
+
* @returns {Promise<{ uploaded: boolean, skipped: boolean, ctx: Object|null }>}
|
|
77
|
+
*/
|
|
78
|
+
async function ensureChannelUploaded(channelTarget, options = {}) {
|
|
79
|
+
if (options.dryRun === true) {
|
|
80
|
+
return { uploaded: false, skipped: true, ctx: null };
|
|
81
|
+
}
|
|
82
|
+
const ctx = await resolveChannelAuthContext(channelTarget, { silent: options.quiet === true });
|
|
83
|
+
const force = options.force === true;
|
|
84
|
+
const alreadyUploaded = force ? false : await isCommunicationSystemUploaded(ctx, channelTarget);
|
|
85
|
+
if (alreadyUploaded) {
|
|
86
|
+
if (options.quiet !== true) {
|
|
87
|
+
logger.log(chalk.gray(` Communication system "${channelTarget}" already on dataplane`));
|
|
88
|
+
}
|
|
89
|
+
return { uploaded: false, skipped: true, ctx };
|
|
90
|
+
}
|
|
91
|
+
if (options.quiet !== true) {
|
|
92
|
+
const reason = force ? 'force upload' : 'not found on dataplane';
|
|
93
|
+
logger.log(chalk.white(`Uploading ${channelTarget} (${reason})…`));
|
|
94
|
+
}
|
|
95
|
+
await uploadExternalSystem(channelTarget, {
|
|
96
|
+
force: true,
|
|
97
|
+
minimal: options.quiet === true || options.minimal !== false
|
|
98
|
+
});
|
|
99
|
+
if (options.quiet !== true) {
|
|
100
|
+
logger.log(formatSuccessLine(`Uploaded ${channelTarget}`));
|
|
101
|
+
}
|
|
102
|
+
return { uploaded: true, skipped: false, ctx };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Scaffold if needed, then upload if missing (or always when --force).
|
|
107
|
+
*
|
|
108
|
+
* @param {string} channelTarget
|
|
109
|
+
* @param {Object} options
|
|
110
|
+
* @returns {Promise<{ scaffolded: boolean, uploaded: boolean, integrationDir: string }>}
|
|
111
|
+
*/
|
|
112
|
+
async function ensureChannelReadyForAdd(channelTarget, options = {}) {
|
|
113
|
+
const scaffold = await ensureChannelScaffold(channelTarget, options);
|
|
114
|
+
const upload = await ensureChannelUploaded(channelTarget, options);
|
|
115
|
+
return {
|
|
116
|
+
scaffolded: scaffold.scaffolded,
|
|
117
|
+
uploaded: upload.uploaded,
|
|
118
|
+
integrationDir: scaffold.integrationDir
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
resolveIntegrationDir,
|
|
124
|
+
isCommunicationSystemUploaded,
|
|
125
|
+
ensureChannelScaffold,
|
|
126
|
+
ensureChannelUploaded,
|
|
127
|
+
ensureChannelReadyForAdd
|
|
128
|
+
};
|
|
@@ -147,11 +147,21 @@ function generateChannelArtifact(params) {
|
|
|
147
147
|
params.dryRun === true
|
|
148
148
|
);
|
|
149
149
|
|
|
150
|
+
const installReady = params.runtimeValues?.installReady === true;
|
|
151
|
+
const blocker = params.runtimeValues?.discoveryBlocker || null;
|
|
150
152
|
return {
|
|
151
153
|
outputPath,
|
|
152
154
|
artifactHash,
|
|
153
155
|
content,
|
|
154
|
-
installPath: install?.installPath
|
|
156
|
+
installPath: install?.installPath,
|
|
157
|
+
zipPath: null,
|
|
158
|
+
links: {
|
|
159
|
+
readme: install?.installPath || null,
|
|
160
|
+
zip: null,
|
|
161
|
+
filesRoot: outputDir
|
|
162
|
+
},
|
|
163
|
+
installReady,
|
|
164
|
+
blocker
|
|
155
165
|
};
|
|
156
166
|
}
|
|
157
167
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional channel package branding in Builder workspace (plan 167.0).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Read/write channel-branding.json under integration/<systemKey>/
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('../internal/fs-real-sync');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const BRANDING_FILENAME = 'channel-branding.json';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} workspaceSystemDir
|
|
18
|
+
* @returns {string}
|
|
19
|
+
*/
|
|
20
|
+
function brandingFilePath(workspaceSystemDir) {
|
|
21
|
+
return path.join(workspaceSystemDir, BRANDING_FILENAME);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {string} workspaceSystemDir
|
|
26
|
+
* @returns {{ displayName?: string, description?: string, iconColorPath?: string, iconOutlinePath?: string }}
|
|
27
|
+
*/
|
|
28
|
+
function readChannelBranding(workspaceSystemDir) {
|
|
29
|
+
const filePath = brandingFilePath(workspaceSystemDir);
|
|
30
|
+
if (!fs.existsSync(filePath)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
const raw = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
34
|
+
if (!raw || typeof raw !== 'object') {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
/** @type {{ displayName?: string, description?: string, iconColorPath?: string, iconOutlinePath?: string }} */
|
|
38
|
+
const out = {};
|
|
39
|
+
if (typeof raw.displayName === 'string' && raw.displayName.trim()) {
|
|
40
|
+
out.displayName = raw.displayName.trim();
|
|
41
|
+
}
|
|
42
|
+
if (typeof raw.description === 'string' && raw.description.trim()) {
|
|
43
|
+
out.description = raw.description.trim();
|
|
44
|
+
}
|
|
45
|
+
if (typeof raw.iconColorPath === 'string' && raw.iconColorPath.trim()) {
|
|
46
|
+
out.iconColorPath = raw.iconColorPath.trim();
|
|
47
|
+
}
|
|
48
|
+
if (typeof raw.iconOutlinePath === 'string' && raw.iconOutlinePath.trim()) {
|
|
49
|
+
out.iconOutlinePath = raw.iconOutlinePath.trim();
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} workspaceSystemDir
|
|
56
|
+
* @param {Object} branding
|
|
57
|
+
* @returns {Object}
|
|
58
|
+
*/
|
|
59
|
+
function writeChannelBranding(workspaceSystemDir, branding) {
|
|
60
|
+
fs.mkdirSync(workspaceSystemDir, { recursive: true });
|
|
61
|
+
const next = {
|
|
62
|
+
...readChannelBranding(workspaceSystemDir),
|
|
63
|
+
...(branding && typeof branding === 'object' ? branding : {})
|
|
64
|
+
};
|
|
65
|
+
const cleaned = {};
|
|
66
|
+
for (const key of ['displayName', 'description', 'iconColorPath', 'iconOutlinePath']) {
|
|
67
|
+
if (typeof next[key] === 'string' && next[key].trim()) {
|
|
68
|
+
cleaned[key] = next[key].trim();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
fs.writeFileSync(brandingFilePath(workspaceSystemDir), `${JSON.stringify(cleaned, null, 2)}\n`, 'utf8');
|
|
72
|
+
return cleaned;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Merge CLI/API overrides over workspace branding.
|
|
77
|
+
* @param {Object} [workspaceBranding]
|
|
78
|
+
* @param {Object} [overrides]
|
|
79
|
+
* @returns {Object}
|
|
80
|
+
*/
|
|
81
|
+
function mergeChannelBranding(workspaceBranding, overrides) {
|
|
82
|
+
return {
|
|
83
|
+
...(workspaceBranding && typeof workspaceBranding === 'object' ? workspaceBranding : {}),
|
|
84
|
+
...(overrides && typeof overrides === 'object' ? overrides : {})
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
BRANDING_FILENAME,
|
|
90
|
+
brandingFilePath,
|
|
91
|
+
readChannelBranding,
|
|
92
|
+
writeChannelBranding,
|
|
93
|
+
mergeChannelBranding
|
|
94
|
+
};
|