@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
package/lib/cli/infra-guided.js
CHANGED
|
@@ -8,8 +8,6 @@ const chalk = require('chalk');
|
|
|
8
8
|
const logger = require('../utils/logger');
|
|
9
9
|
const config = require('../core/config');
|
|
10
10
|
const devConfig = require('../utils/dev-config');
|
|
11
|
-
const pathsUtil = require('../utils/paths');
|
|
12
|
-
const { loadConfigFile } = require('../utils/config-format');
|
|
13
11
|
const { withMutedLogger } = require('../utils/with-muted-logger');
|
|
14
12
|
const infra = require('../infrastructure');
|
|
15
13
|
const { execWithDockerEnv } = require('../utils/docker-exec');
|
|
@@ -41,12 +39,72 @@ const {
|
|
|
41
39
|
logUpPlatformForceCleanSummary,
|
|
42
40
|
logStoppedServices
|
|
43
41
|
} = require('./infra-guided-footers');
|
|
44
|
-
const healthCheck = require('../utils/health-check');
|
|
45
42
|
const { prepareUrlsLocalRegistryForUpPlatform } = require('../commands/up-common');
|
|
46
43
|
const { assertDevInfraUp } = require('../commands/dev-infra-gate');
|
|
47
44
|
const {
|
|
48
45
|
emitSystemBuilderAppManifestLineIfTTY
|
|
49
46
|
} = require('../utils/manifest-source-emit');
|
|
47
|
+
const { flushDeferredPublicUrlWarnings } = require('./infra-guided-public-url-warn');
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Guided Keycloak → Miso Controller start with one spinner per app (no second health wait).
|
|
51
|
+
* @param {Object} options
|
|
52
|
+
* @param {Function} handleUpMiso
|
|
53
|
+
* @returns {Promise<string[]>} Deferred public-URL warning messages
|
|
54
|
+
*/
|
|
55
|
+
async function runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso) {
|
|
56
|
+
const deferredWarnings = [];
|
|
57
|
+
const misoSpinnerRef = { current: null };
|
|
58
|
+
emitSystemBuilderAppManifestLineIfTTY(logger, 'keycloak');
|
|
59
|
+
const kcSpin = startSpinner('Starting Keycloak...');
|
|
60
|
+
// Fully mute inner run logs — any newline (✔ / Manifest / blank) splits the ora line.
|
|
61
|
+
await withMutedLogger(
|
|
62
|
+
() =>
|
|
63
|
+
handleUpMiso({
|
|
64
|
+
...options,
|
|
65
|
+
platformInstall: true,
|
|
66
|
+
onAfterKeycloak: () => {
|
|
67
|
+
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
68
|
+
logger.log('');
|
|
69
|
+
emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
|
|
70
|
+
misoSpinnerRef.current = startSpinner('Starting Miso Controller...');
|
|
71
|
+
},
|
|
72
|
+
onMisoPollText: (text) => {
|
|
73
|
+
const spin = misoSpinnerRef.current;
|
|
74
|
+
if (spin) {
|
|
75
|
+
// Keep app name; append health progress so the spinner does not look like a new step.
|
|
76
|
+
spin.text = `Starting Miso Controller… ${stripHealthPrefix(text)}`;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
logger.log(text);
|
|
80
|
+
},
|
|
81
|
+
onDeferredPublicUrlWarning: (message) => deferredWarnings.push(message)
|
|
82
|
+
}),
|
|
83
|
+
{ allow: () => false }
|
|
84
|
+
);
|
|
85
|
+
if (misoSpinnerRef.current) {
|
|
86
|
+
stopSpinnerSuccess(misoSpinnerRef.current, 'Miso Controller ready');
|
|
87
|
+
} else {
|
|
88
|
+
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
89
|
+
}
|
|
90
|
+
flushDeferredPublicUrlWarnings(deferredWarnings);
|
|
91
|
+
logger.log('');
|
|
92
|
+
return deferredWarnings;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** ESC + CSI SGR — built without a control-char regex literal (no-control-regex). */
|
|
96
|
+
const ANSI_SGR_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, 'g');
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Turn "Waiting for health check… (4/45)" into "(4/45)" for nested spinner text.
|
|
100
|
+
* @param {string} text
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
function stripHealthPrefix(text) {
|
|
104
|
+
const raw = String(text || '');
|
|
105
|
+
const match = raw.match(/\((\d+\/\d+)\)/);
|
|
106
|
+
return match ? `(${match[1]})` : raw.replace(ANSI_SGR_RE, '').trim();
|
|
107
|
+
}
|
|
50
108
|
|
|
51
109
|
async function getRunningContainerNameForApp(appName) {
|
|
52
110
|
const apps = await infra.getAppStatus();
|
|
@@ -86,31 +144,6 @@ async function validateAppErrorLogs(appNames) {
|
|
|
86
144
|
logger.log('');
|
|
87
145
|
}
|
|
88
146
|
|
|
89
|
-
async function waitForAppReady(appName, opts = {}) {
|
|
90
|
-
const timeoutSeconds = Number.isFinite(opts.timeoutSeconds) ? opts.timeoutSeconds : 90;
|
|
91
|
-
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
92
|
-
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
93
|
-
const appConfig = loadConfigFile(configPath) || {};
|
|
94
|
-
|
|
95
|
-
const apps = await infra.getAppStatus();
|
|
96
|
-
const hit = (apps || []).find(a => a && a.name === appName && typeof a.url === 'string');
|
|
97
|
-
const portMatch = hit && hit.url ? hit.url.match(/:(\d+)$/) : null;
|
|
98
|
-
const hostPort = portMatch ? parseInt(portMatch[1], 10) : null;
|
|
99
|
-
if (!hostPort) {
|
|
100
|
-
throw new Error(`Could not determine host port for ${appName}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const userCfg = await config.getConfig();
|
|
104
|
-
const healthRunOpts = { profile: 'docker' };
|
|
105
|
-
if (userCfg && userCfg.traefik === true) {
|
|
106
|
-
healthRunOpts.traefikEnabled = true;
|
|
107
|
-
}
|
|
108
|
-
if (typeof opts.onPollText === 'function') {
|
|
109
|
-
healthRunOpts.onPollText = opts.onPollText;
|
|
110
|
-
}
|
|
111
|
-
await healthCheck.waitForHealthCheck(appName, timeoutSeconds, hostPort, appConfig, false, healthRunOpts);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
147
|
function resolveInfraHost(remoteServer) {
|
|
115
148
|
const raw = String(remoteServer || '').trim().replace(/\/+$/, '');
|
|
116
149
|
if (!raw) return 'localhost';
|
|
@@ -179,16 +212,7 @@ async function runGuidedUpMiso(options, handleUpMiso) {
|
|
|
179
212
|
logger.log('');
|
|
180
213
|
logPlatformSetupDurationHint('platform');
|
|
181
214
|
logger.log('');
|
|
182
|
-
|
|
183
|
-
await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
|
|
184
|
-
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
185
|
-
logger.log('');
|
|
186
|
-
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
187
|
-
await waitForAppReady('miso-controller', {
|
|
188
|
-
timeoutSeconds: 120,
|
|
189
|
-
onPollText: createSpinnerTextUpdater(mcSpin)
|
|
190
|
-
});
|
|
191
|
-
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
215
|
+
await runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso);
|
|
192
216
|
await logMisoReadyFooter();
|
|
193
217
|
await validateAppErrorLogs(['miso-controller']);
|
|
194
218
|
}
|
|
@@ -241,22 +265,6 @@ async function refreshDataplaneVersionAfterPlatformInstall() {
|
|
|
241
265
|
}
|
|
242
266
|
}
|
|
243
267
|
|
|
244
|
-
async function runGuidedKeycloakAndMisoControllerPhase(options, handleUpMiso) {
|
|
245
|
-
emitSystemBuilderAppManifestLineIfTTY(logger, 'keycloak');
|
|
246
|
-
const kcSpin = startSpinner('Starting Keycloak...');
|
|
247
|
-
await withMutedLogger(() => handleUpMiso({ ...options, platformInstall: true }));
|
|
248
|
-
stopSpinnerSuccess(kcSpin, 'Keycloak ready');
|
|
249
|
-
logger.log('');
|
|
250
|
-
emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
|
|
251
|
-
const mcSpin = startSpinner('Starting Miso Controller...');
|
|
252
|
-
await waitForAppReady('miso-controller', {
|
|
253
|
-
timeoutSeconds: 120,
|
|
254
|
-
onPollText: createSpinnerTextUpdater(mcSpin)
|
|
255
|
-
});
|
|
256
|
-
stopSpinnerSuccess(mcSpin, 'Miso Controller ready');
|
|
257
|
-
logger.log('');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
268
|
async function runGuidedDataplaneInstallPhase(options, handleUpDataplane, handleLoginFn, controllerUrl) {
|
|
261
269
|
logger.log('');
|
|
262
270
|
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
@@ -310,7 +318,7 @@ async function runGuidedUpPlatform(options, handleUpMiso, handleUpDataplane, han
|
|
|
310
318
|
await runGuidedAuthStep(handleLogin, authOpts, { startSpinner, stopSpinnerSuccess });
|
|
311
319
|
const controllerUrl = await resolveGuidedAuthControllerUrl(authOpts);
|
|
312
320
|
await runGuidedDataplaneInstallPhase(options, handleUpDataplane, handleLogin, controllerUrl);
|
|
313
|
-
const baSpin = startSpinner('Starting
|
|
321
|
+
const baSpin = startSpinner('Starting Builder API...');
|
|
314
322
|
await withMutedLogger(() => handleUpBuilderApi({ ...options, platformInstall: true }));
|
|
315
323
|
stopSpinnerSuccess(baSpin, 'Builder API ready');
|
|
316
324
|
await validateAppErrorLogs(['miso-controller', 'dataplane', 'builder-api']);
|
|
@@ -323,23 +331,30 @@ async function runGuidedDownInfra(appName, options, infraMod, appLib) {
|
|
|
323
331
|
logger.log(SEPARATOR);
|
|
324
332
|
logger.log('');
|
|
325
333
|
|
|
334
|
+
const targetDeveloperId = options && options.developerId;
|
|
326
335
|
const spin = startSpinner('Stopping infrastructure...');
|
|
327
336
|
await withMutedLogger(async() => {
|
|
328
337
|
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
329
|
-
await appLib.downApp(appName, {
|
|
338
|
+
await appLib.downApp(appName, {
|
|
339
|
+
volumes: !!options.volumes,
|
|
340
|
+
developerId: targetDeveloperId
|
|
341
|
+
});
|
|
330
342
|
return;
|
|
331
343
|
}
|
|
332
|
-
if (options.volumes) await infraMod.stopInfraWithVolumes();
|
|
333
|
-
else await infraMod.stopInfra();
|
|
344
|
+
if (options.volumes) await infraMod.stopInfraWithVolumes(targetDeveloperId);
|
|
345
|
+
else await infraMod.stopInfra(targetDeveloperId);
|
|
334
346
|
});
|
|
335
347
|
stopSpinnerSuccess(spin, 'Infrastructure stopped');
|
|
336
348
|
|
|
337
|
-
const developerId =
|
|
338
|
-
|
|
349
|
+
const developerId = String(
|
|
350
|
+
targetDeveloperId !== undefined && targetDeveloperId !== null && targetDeveloperId !== ''
|
|
351
|
+
? targetDeveloperId
|
|
352
|
+
: await config.getDeveloperId()
|
|
353
|
+
);
|
|
339
354
|
const cfg = await config.getConfig();
|
|
340
355
|
|
|
341
356
|
logFooterStart('Stopped');
|
|
342
|
-
logSection('Developer', `dev${
|
|
357
|
+
logSection('Developer', `dev${developerId.padStart(2, '0')} (id: ${developerId})`);
|
|
343
358
|
logStoppedServices(cfg);
|
|
344
359
|
if (options.volumes) {
|
|
345
360
|
logger.log(chalk.yellow('⚠ Volumes removed: all local data deleted'));
|
|
@@ -353,5 +368,6 @@ module.exports = {
|
|
|
353
368
|
runGuidedUpMiso,
|
|
354
369
|
runGuidedUpDataplane,
|
|
355
370
|
runGuidedUpPlatform,
|
|
356
|
-
runGuidedDownInfra
|
|
371
|
+
runGuidedDownInfra,
|
|
372
|
+
stripHealthPrefix
|
|
357
373
|
};
|
|
@@ -11,6 +11,10 @@ Examples:
|
|
|
11
11
|
$ aifabrix test hubspot -v
|
|
12
12
|
$ aifabrix test hubspot -d
|
|
13
13
|
|
|
14
|
+
# Role Assistant (role-assistant/ra-<roleKey>/) — lifecycle checks
|
|
15
|
+
$ aifabrix test ra-business-transformation
|
|
16
|
+
$ aifabrix role-assistant test business-transformation
|
|
17
|
+
|
|
14
18
|
# Builder app (builder/<app>/) — runs in container
|
|
15
19
|
$ aifabrix test myapp -e dev
|
|
16
20
|
$ aifabrix test myapp -e tst
|
|
@@ -12,9 +12,56 @@ const {
|
|
|
12
12
|
TEST_E2E_HELP_AFTER
|
|
13
13
|
} = require('./setup-app.help');
|
|
14
14
|
|
|
15
|
+
/** External packages that use local unit validation (not builder container tests). */
|
|
16
|
+
function isExternalUnitTestWorkspace(baseDir) {
|
|
17
|
+
return baseDir === 'integration' || baseDir === 'enterprise-knowledge';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Dispatch aifabrix test by detected workspace (builder / integration / role-assistant).
|
|
22
|
+
* @param {string} appName
|
|
23
|
+
* @param {Object} options
|
|
24
|
+
* @param {Object} cmd
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
async function runTestCommandAction(appName, options, cmd) {
|
|
28
|
+
const rawArgs = Array.isArray(cmd?.rawArgs) ? cmd.rawArgs : [];
|
|
29
|
+
const envExplicit = rawArgs.includes('-e') || rawArgs.includes('--env');
|
|
30
|
+
const pathsUtil = require('../utils/paths');
|
|
31
|
+
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
32
|
+
if (options.sync === true) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
'Option --sync is not supported for aifabrix test (local or container runs do not publish to the dataplane). ' +
|
|
35
|
+
'Use: aifabrix upload <systemKey>, then aifabrix test-integration <systemKey> --sync or aifabrix datasource test-integration <datasourceKey> --sync.'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (appType && appType.baseDir === 'role-assistant') {
|
|
39
|
+
const { runRoleAssistantTestAction } = require('../commands/role-assistant');
|
|
40
|
+
await runRoleAssistantTestAction(appName, options, cmd);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (appType && isExternalUnitTestWorkspace(appType.baseDir)) {
|
|
44
|
+
const test = require('../external-system/test');
|
|
45
|
+
const externalOpts = {
|
|
46
|
+
...options,
|
|
47
|
+
// Keep help default but don't override auth/env behavior unless user explicitly set it.
|
|
48
|
+
env: envExplicit ? options.env : undefined
|
|
49
|
+
};
|
|
50
|
+
const results = await test.testExternalSystem(appName, externalOpts);
|
|
51
|
+
test.displayTestResults(results, options.verbose, appName);
|
|
52
|
+
if (!results.valid) process.exit(1);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const { runAppTest } = require('../commands/app-test');
|
|
56
|
+
await runAppTest(appName, { env: options.env });
|
|
57
|
+
}
|
|
58
|
+
|
|
15
59
|
function setupTestCommand(program) {
|
|
16
60
|
program.command('test <appKey|systemKey>')
|
|
17
|
-
.description(
|
|
61
|
+
.description(
|
|
62
|
+
'Tests: builder appKey in container; integration/EK = local validation; ' +
|
|
63
|
+
'role-assistant = lifecycle (same as role-assistant test)'
|
|
64
|
+
)
|
|
18
65
|
.option('-e, --env <env>', 'For builder app: dev (running container) or tst (ephemeral)', 'dev')
|
|
19
66
|
.option('-v, --verbose', 'Verbose output')
|
|
20
67
|
.option('-d, --debug', 'Certification debug + log to integration/<systemKey>/logs/ (external only)')
|
|
@@ -25,30 +72,7 @@ function setupTestCommand(program) {
|
|
|
25
72
|
.addHelpText('after', TEST_HELP_AFTER)
|
|
26
73
|
.action(async(appName, options, cmd) => {
|
|
27
74
|
try {
|
|
28
|
-
|
|
29
|
-
const envExplicit = rawArgs.includes('-e') || rawArgs.includes('--env');
|
|
30
|
-
const pathsUtil = require('../utils/paths');
|
|
31
|
-
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
32
|
-
if (options.sync === true) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
'Option --sync is not supported for aifabrix test (local or container runs do not publish to the dataplane). ' +
|
|
35
|
-
'Use: aifabrix upload <systemKey>, then aifabrix test-integration <systemKey> --sync or aifabrix datasource test-integration <datasourceKey> --sync.'
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
if (appType && appType.baseDir === 'integration') {
|
|
39
|
-
const test = require('../external-system/test');
|
|
40
|
-
const externalOpts = {
|
|
41
|
-
...options,
|
|
42
|
-
// Keep help default but don't override auth/env behavior unless user explicitly set it.
|
|
43
|
-
env: envExplicit ? options.env : undefined
|
|
44
|
-
};
|
|
45
|
-
const results = await test.testExternalSystem(appName, externalOpts);
|
|
46
|
-
test.displayTestResults(results, options.verbose, appName);
|
|
47
|
-
if (!results.valid) process.exit(1);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const { runAppTest } = require('../commands/app-test');
|
|
51
|
-
await runAppTest(appName, { env: options.env });
|
|
75
|
+
await runTestCommandAction(appName, options, cmd);
|
|
52
76
|
} catch (error) {
|
|
53
77
|
handleCommandError(error, 'test');
|
|
54
78
|
process.exit(1);
|
|
@@ -106,7 +130,14 @@ async function runTestE2ECommand(appName, options, cmd) {
|
|
|
106
130
|
'Remove --no-publish for builder app E2E.'
|
|
107
131
|
);
|
|
108
132
|
}
|
|
109
|
-
if (appType && appType.baseDir === '
|
|
133
|
+
if (appType && appType.baseDir === 'role-assistant') {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`'${appName}' is a Role Assistant under role-assistant/. ` +
|
|
136
|
+
'Datasource E2E does not apply. Use: aifabrix role-assistant test <roleKey> ' +
|
|
137
|
+
'(or: aifabrix test <roleKey>).'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (appType && isExternalUnitTestWorkspace(appType.baseDir)) {
|
|
110
141
|
await runExternalIntegrationE2EAndExit(appName, options, cmd);
|
|
111
142
|
return;
|
|
112
143
|
}
|
|
@@ -122,7 +153,7 @@ function setupInstallCommand(program) {
|
|
|
122
153
|
try {
|
|
123
154
|
const pathsUtil = require('../utils/paths');
|
|
124
155
|
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
125
|
-
if (appType && appType.baseDir === '
|
|
156
|
+
if (appType && (isExternalUnitTestWorkspace(appType.baseDir) || appType.baseDir === 'role-assistant')) {
|
|
126
157
|
logger.log(
|
|
127
158
|
chalk.gray('Install is for builder appKey only. Use aifabrix shell <appKey> for builder apps or External Systems commands for integrations.')
|
|
128
159
|
);
|
|
@@ -186,7 +217,7 @@ function setupLintCommand(program) {
|
|
|
186
217
|
try {
|
|
187
218
|
const pathsUtil = require('../utils/paths');
|
|
188
219
|
const appType = await pathsUtil.detectAppType(appName).catch(() => null);
|
|
189
|
-
if (appType && appType.baseDir === '
|
|
220
|
+
if (appType && (isExternalUnitTestWorkspace(appType.baseDir) || appType.baseDir === 'role-assistant')) {
|
|
190
221
|
logger.log(chalk.gray('lint is for builder appKey only. Use aifabrix shell <appKey> then make lint or pnpm lint.'));
|
|
191
222
|
return;
|
|
192
223
|
}
|
|
@@ -159,10 +159,44 @@ function setupDeleteCommand(program) {
|
|
|
159
159
|
* @param {Object} options - CLI options
|
|
160
160
|
* @returns {Promise<boolean>} True if builder test was run, false otherwise
|
|
161
161
|
*/
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* @param {string} appName
|
|
164
|
+
* @param {Object} options
|
|
165
|
+
* @returns {Promise<boolean>} True if a builder-app integration test was started
|
|
166
|
+
*/
|
|
167
|
+
async function tryRunBuilderAppIntegration(appName, options) {
|
|
163
168
|
const fsSync = require('fs');
|
|
169
|
+
const { getBuilderPath } = require('../utils/paths');
|
|
170
|
+
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
171
|
+
const builderPath = getBuilderPath(appName);
|
|
172
|
+
if (!fsSync.existsSync(builderPath)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
let builderConfigPath;
|
|
176
|
+
try {
|
|
177
|
+
builderConfigPath = resolveApplicationConfigPath(builderPath);
|
|
178
|
+
} catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (!fsSync.existsSync(builderConfigPath)) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const { runAppTestIntegration } = require('../commands/app-test');
|
|
185
|
+
await runAppTestIntegration(appName, {
|
|
186
|
+
env: options.env || options.environment || 'dev'
|
|
187
|
+
});
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Prefer builder-app container tests when no externalIntegration folder exists.
|
|
193
|
+
* @param {string} appName - App or system key
|
|
194
|
+
* @param {Object} options - CLI options
|
|
195
|
+
* @returns {Promise<boolean>} True if builder test was run, false otherwise
|
|
196
|
+
*/
|
|
197
|
+
async function tryBuilderTestIntegration(appName, options) {
|
|
164
198
|
const pathsUtil = require('../utils/paths');
|
|
165
|
-
const { getIntegrationPath
|
|
199
|
+
const { getIntegrationPath } = pathsUtil;
|
|
166
200
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
167
201
|
const { loadConfigFile } = require('../utils/config-format');
|
|
168
202
|
const integrationPath = getIntegrationPath(appName);
|
|
@@ -173,17 +207,10 @@ async function tryBuilderTestIntegration(appName, options) {
|
|
|
173
207
|
} catch {
|
|
174
208
|
// integration path or config missing
|
|
175
209
|
}
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
const builderConfigPath = resolveApplicationConfigPath(builderPath);
|
|
179
|
-
if (fsSync.existsSync(builderPath) && fsSync.existsSync(builderConfigPath)) {
|
|
180
|
-
const { runAppTestIntegration } = require('../commands/app-test');
|
|
181
|
-
const opts = { env: options.env || options.environment || 'dev' };
|
|
182
|
-
await runAppTestIntegration(appName, opts);
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
210
|
+
if (hasExternalIntegration) {
|
|
211
|
+
return false;
|
|
185
212
|
}
|
|
186
|
-
return
|
|
213
|
+
return tryRunBuilderAppIntegration(appName, options);
|
|
187
214
|
}
|
|
188
215
|
|
|
189
216
|
/**
|
|
@@ -233,6 +260,17 @@ async function runTestIntegrationCommand(appName, options) {
|
|
|
233
260
|
await runAppTestIntegration(appName, opts);
|
|
234
261
|
return;
|
|
235
262
|
}
|
|
263
|
+
// Role Assistant / EK / integration packages must use the dataplane path — do not
|
|
264
|
+
// probe builder/<app> (resolveApplicationConfigPath throws when the folder is absent).
|
|
265
|
+
if (
|
|
266
|
+
appType &&
|
|
267
|
+
(appType.baseDir === 'role-assistant' ||
|
|
268
|
+
appType.baseDir === 'enterprise-knowledge' ||
|
|
269
|
+
appType.baseDir === 'integration')
|
|
270
|
+
) {
|
|
271
|
+
await runExternalSystemTestIntegration(appName, options);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
236
274
|
const ranBuilder = await tryBuilderTestIntegration(appName, options);
|
|
237
275
|
if (ranBuilder) {
|
|
238
276
|
if (options.sync === true) {
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -31,6 +31,9 @@ const {
|
|
|
31
31
|
getRestartableInfraServiceNames,
|
|
32
32
|
buildRestartInfraHelpLines
|
|
33
33
|
} = require('../constants/infra-compose-service-names');
|
|
34
|
+
const {
|
|
35
|
+
resolveTransientDeveloperIdFromOptions
|
|
36
|
+
} = require('../utils/transient-developer-id');
|
|
34
37
|
|
|
35
38
|
const {
|
|
36
39
|
computeEffectiveInfraOptionalFlags,
|
|
@@ -97,6 +100,18 @@ async function applyDeveloperIdFromUpInfra(options) {
|
|
|
97
100
|
return id;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
const DOWN_INFRA_HELP_AFTER = `
|
|
104
|
+
Examples:
|
|
105
|
+
$ aifabrix down-infra
|
|
106
|
+
$ aifabrix down-infra --volumes
|
|
107
|
+
$ aifabrix down-infra --developer-id 02
|
|
108
|
+
$ aifabrix down-infra -d 02 --volumes
|
|
109
|
+
$ aifabrix down-infra myapp --developer-id 02
|
|
110
|
+
|
|
111
|
+
--developer-id / -d target another developer's stack for this command only.
|
|
112
|
+
They do not change developer-id in config.
|
|
113
|
+
`;
|
|
114
|
+
|
|
100
115
|
/**
|
|
101
116
|
* @param {Object} options
|
|
102
117
|
* @param {Object} cfg - Mutable config from getConfig()
|
|
@@ -302,20 +317,32 @@ function setupUpBuilderApiCommand(program) {
|
|
|
302
317
|
function setupDownInfraCommand(program) {
|
|
303
318
|
program.command('down-infra [service|app]')
|
|
304
319
|
.description('Stop all infra, or stop one app; use -v to remove volumes')
|
|
320
|
+
.addHelpText('after', DOWN_INFRA_HELP_AFTER)
|
|
305
321
|
.option('-v, --volumes', 'Remove volumes (deletes all data)')
|
|
306
322
|
.option('--verbose', 'Show full orchestration output (default is guided summary)')
|
|
323
|
+
.option(
|
|
324
|
+
'-d, --developer <id>',
|
|
325
|
+
'Target developer ID for this stop only (does not change config)'
|
|
326
|
+
)
|
|
327
|
+
.option(
|
|
328
|
+
'--developer-id <id>',
|
|
329
|
+
'Same as --developer; target another developer without changing config'
|
|
330
|
+
)
|
|
307
331
|
.action(async(appName, options) => {
|
|
308
332
|
try {
|
|
333
|
+
const developerId = resolveTransientDeveloperIdFromOptions(options);
|
|
334
|
+
const opts = { ...options, developerId };
|
|
309
335
|
if (!options.verbose) {
|
|
310
|
-
await runGuidedDownInfra(appName,
|
|
336
|
+
await runGuidedDownInfra(appName, opts, infra, appLib);
|
|
311
337
|
return;
|
|
312
338
|
}
|
|
313
339
|
|
|
314
340
|
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
315
|
-
await appLib.downApp(appName, { volumes: !!options.volumes });
|
|
341
|
+
await appLib.downApp(appName, { volumes: !!options.volumes, developerId });
|
|
342
|
+
} else if (options.volumes) {
|
|
343
|
+
await infra.stopInfraWithVolumes(developerId);
|
|
316
344
|
} else {
|
|
317
|
-
|
|
318
|
-
else await infra.stopInfra();
|
|
345
|
+
await infra.stopInfra(developerId);
|
|
319
346
|
}
|
|
320
347
|
} catch (error) {
|
|
321
348
|
handleCommandError(error, 'down-infra');
|
|
@@ -9,29 +9,45 @@
|
|
|
9
9
|
const chalk = require('chalk');
|
|
10
10
|
const logger = require('../utils/logger');
|
|
11
11
|
const { formatBlockingError } = require('../utils/cli-test-layout-chalk');
|
|
12
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
formatSupportedChannelTargetsHelp,
|
|
14
|
+
isChannelTargetKey
|
|
15
|
+
} = require('../channels/channel-targets');
|
|
13
16
|
const { addChannelCommand } = require('../channels/add-channel');
|
|
14
17
|
|
|
15
18
|
const CHANNEL_ADD_HELP_AFTER = `
|
|
19
|
+
${formatSupportedChannelTargetsHelp()}
|
|
20
|
+
|
|
21
|
+
One command covers scaffold → upload → install:
|
|
22
|
+
$ aifabrix channel add slack --name "Elsa" --probe
|
|
16
23
|
|
|
17
24
|
Examples:
|
|
18
|
-
# After upload/deploy of the communication external system
|
|
19
25
|
$ aifabrix channel add slack --name "Elsa"
|
|
20
26
|
$ aifabrix channel add teams-bot --name "Elsa" --description "Enterprise AI assistant for employees"
|
|
27
|
+
$ aifabrix channel add chatgpt --name "Your Assistant"
|
|
28
|
+
$ aifabrix channel add microsoft-copilot --name "Your Assistant" \\
|
|
29
|
+
--oauth-plugin-vault-reference-id "<Teams-Entra-SSO-auth-config-id>"
|
|
21
30
|
|
|
22
31
|
# Slack slash-command prefix (registers /{prefix}-help in manifest)
|
|
23
32
|
$ aifabrix channel add slack --name "Elsa" --command-prefix af
|
|
24
33
|
|
|
25
|
-
# Generate artifacts only; no dataplane patch
|
|
34
|
+
# Generate artifacts only; no dataplane patch / upload
|
|
26
35
|
$ aifabrix channel add slack --name "Elsa" --dry-run
|
|
27
36
|
|
|
28
|
-
#
|
|
37
|
+
# Re-upload communication system + regenerate channel config (does not rotate secrets)
|
|
29
38
|
$ aifabrix channel add slack --name "Elsa" --force
|
|
30
39
|
|
|
31
40
|
# Validate callbacks and assistant catalog after install
|
|
32
41
|
$ aifabrix channel add slack --name "Elsa" --probe
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
Behavior:
|
|
44
|
+
- Scaffolds integration/<target>/ when missing (does not overwrite existing files)
|
|
45
|
+
- Uploads when the system is not on the dataplane; with --force uploads every time
|
|
46
|
+
- Then configures the channel install (artifacts, credentials, admin guide)
|
|
47
|
+
- microsoft-copilot uses Entra SSO (trustedIssuer); pass --oauth-plugin-vault-reference-id
|
|
48
|
+
from Teams Developer Portal. Does not create Fabrix oauth2 channel credentials.
|
|
49
|
+
|
|
50
|
+
Requires: aifabrix login
|
|
35
51
|
`;
|
|
36
52
|
|
|
37
53
|
/**
|
|
@@ -49,7 +65,7 @@ async function runChannelAddAction(channelTarget, options, meta = {}) {
|
|
|
49
65
|
}
|
|
50
66
|
if (!isChannelTargetKey(channelTarget)) {
|
|
51
67
|
throw new Error(
|
|
52
|
-
`Unknown channel target "${channelTarget}".
|
|
68
|
+
`Unknown channel target "${channelTarget}". ${formatSupportedChannelTargetsHelp()}`
|
|
53
69
|
);
|
|
54
70
|
}
|
|
55
71
|
await addChannelCommand(channelTarget, {
|
|
@@ -58,6 +74,7 @@ async function runChannelAddAction(channelTarget, options, meta = {}) {
|
|
|
58
74
|
icon: options.icon,
|
|
59
75
|
template: options.template,
|
|
60
76
|
commandPrefix: options.commandPrefix,
|
|
77
|
+
oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId,
|
|
61
78
|
dryRun: options.dryRun === true,
|
|
62
79
|
force: options.force === true,
|
|
63
80
|
probe: options.probe === true
|
|
@@ -71,7 +88,7 @@ async function runChannelAddAction(channelTarget, options, meta = {}) {
|
|
|
71
88
|
*/
|
|
72
89
|
function registerChannelAddCommand(parent, meta = {}) {
|
|
73
90
|
const description =
|
|
74
|
-
'
|
|
91
|
+
'Scaffold (if needed), upload (if needed/--force), then configure channel install';
|
|
75
92
|
const commandName = meta.commandName || 'add <channelTarget>';
|
|
76
93
|
const cmd = meta.hidden
|
|
77
94
|
? parent.command(commandName, { hidden: true })
|
|
@@ -82,12 +99,19 @@ function registerChannelAddCommand(parent, meta = {}) {
|
|
|
82
99
|
.option('-d, --description <text>', 'Channel description (default generated)')
|
|
83
100
|
.option('--icon <path>', 'Path to channel icon asset')
|
|
84
101
|
.option('--template <name>', 'Template override (default from channel target)')
|
|
85
|
-
.option('--dry-run', 'Generate artifacts only; do not patch
|
|
86
|
-
.option(
|
|
102
|
+
.option('--dry-run', 'Generate artifacts only; do not upload, patch, or set credentials')
|
|
103
|
+
.option(
|
|
104
|
+
'--force',
|
|
105
|
+
'Re-upload communication system and regenerate channel config (does not rotate secrets)'
|
|
106
|
+
)
|
|
87
107
|
.option(
|
|
88
108
|
'--command-prefix <key>',
|
|
89
109
|
'Slack slash alias prefix (default af) — registers /{prefix}-help etc. in manifest'
|
|
90
110
|
)
|
|
111
|
+
.option(
|
|
112
|
+
'--oauth-plugin-vault-reference-id <id>',
|
|
113
|
+
'Teams Developer Portal Entra SSO OAuth auth-config id (microsoft-copilot)'
|
|
114
|
+
)
|
|
91
115
|
.option('--probe', 'After install, validate callbacks and assistant catalog')
|
|
92
116
|
.addHelpText('after', CHANNEL_ADD_HELP_AFTER)
|
|
93
117
|
.action(async(channelTarget, options) => {
|
|
@@ -95,7 +119,8 @@ function registerChannelAddCommand(parent, meta = {}) {
|
|
|
95
119
|
await runChannelAddAction(channelTarget, options, meta);
|
|
96
120
|
} catch (error) {
|
|
97
121
|
if (!error.message?.includes('Authentication required')) {
|
|
98
|
-
|
|
122
|
+
const detail = error.message || String(error);
|
|
123
|
+
logger.error(formatBlockingError(`Channel install failed:\n${detail}`));
|
|
99
124
|
}
|
|
100
125
|
process.exit(1);
|
|
101
126
|
}
|