@aifabrix/builder 2.53.0 → 2.54.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/bin/aifabrix.js +2 -2
- package/lib/api/assistants.api.js +8 -5
- package/lib/api/channel-identity.api.js +41 -0
- package/lib/api/evidence-authoring.api.js +5 -1
- package/lib/api/evidence.api.js +2 -0
- package/lib/api/types/evidence.types.js +2 -0
- package/lib/api/work-search.api.js +136 -0
- package/lib/channels/add-channel.js +163 -126
- package/lib/channels/approval-guides/chatgpt.js +14 -18
- package/lib/channels/approval-guides/claude.js +29 -0
- package/lib/channels/approval-guides/index.js +88 -22
- package/lib/channels/approval-guides/mcp-client.js +27 -0
- package/lib/channels/approval-guides/microsoft-copilot.js +52 -3
- package/lib/channels/approval-guides/slack.js +10 -10
- package/lib/channels/approval-guides/teams-bot.js +8 -12
- package/lib/channels/channel-add-continuations.js +91 -0
- package/lib/channels/channel-add-ensure.js +124 -27
- package/lib/channels/channel-add-existing.js +160 -28
- package/lib/channels/channel-add-failure.js +60 -0
- package/lib/channels/channel-add-remaining.js +486 -0
- package/lib/channels/channel-add-tty.js +190 -0
- package/lib/channels/channel-add-wizard-config.js +138 -0
- package/lib/channels/channel-add-wizard.js +250 -0
- package/lib/channels/channel-artifact-generator.js +27 -13
- package/lib/channels/channel-cardinality.js +83 -0
- package/lib/channels/channel-catalog-icon.js +45 -0
- package/lib/channels/channel-cli-instructions.js +75 -0
- package/lib/channels/channel-configuration-map.js +213 -0
- package/lib/channels/channel-credential.js +11 -14
- package/lib/channels/channel-definition-project.js +101 -0
- package/lib/channels/channel-definition-registry.js +154 -0
- package/lib/channels/channel-description.js +59 -2
- package/lib/channels/channel-discovery.js +16 -2
- package/lib/channels/channel-external-system.js +86 -9
- package/lib/channels/channel-install-envelope-actions.js +88 -0
- package/lib/channels/channel-install-envelope-microsoft.js +155 -0
- package/lib/channels/channel-install-envelope.js +356 -0
- package/lib/channels/channel-install-help-md.js +172 -0
- package/lib/channels/channel-install-http-payload.js +62 -0
- package/lib/channels/channel-install-inventory.js +115 -0
- package/lib/channels/channel-install-profiles.js +90 -0
- package/lib/channels/channel-instance-collision.js +118 -0
- package/lib/channels/channel-instance-key.js +100 -0
- package/lib/channels/channel-instance-materialize.js +240 -0
- package/lib/channels/channel-integration-generator.js +16 -9
- package/lib/channels/channel-integration-template.js +101 -22
- package/lib/channels/channel-inventory-fetch.js +79 -0
- package/lib/channels/channel-oauth-preflight.js +104 -0
- package/lib/channels/channel-package-version.js +76 -0
- package/lib/channels/channel-predefined-client.js +154 -0
- package/lib/channels/channel-probe.js +7 -2
- package/lib/channels/channel-runtime-resolver.js +180 -72
- package/lib/channels/channel-status-display.js +86 -16
- package/lib/channels/channel-targets.js +106 -92
- package/lib/channels/chatgpt-callback.js +318 -0
- package/lib/channels/chatgpt-channel-add.js +229 -0
- package/lib/channels/chatgpt-preflight.js +396 -0
- package/lib/channels/chatgpt-runtime-contract.js +17 -0
- package/lib/channels/chatgpt-setup-values.js +124 -0
- package/lib/channels/claude-callback.js +67 -0
- package/lib/channels/claude-channel-add.js +254 -0
- package/lib/channels/claude-runtime-contract.js +40 -0
- package/lib/channels/claude-runtime.js +58 -0
- package/lib/channels/claude-setup-values.js +89 -0
- package/lib/channels/mcp-client/config-merge.js +148 -0
- package/lib/channels/mcp-client/ensure-client.js +231 -0
- package/lib/channels/mcp-client/paths.js +53 -0
- package/lib/channels/mcp-client/profiles.js +122 -0
- package/lib/channels/mcp-client/redirect-uris.js +99 -0
- package/lib/channels/mcp-client/runtime.js +59 -0
- package/lib/channels/mcp-client-channel-add.js +186 -0
- package/lib/channels/microsoft-copilot-channel-add.js +117 -7
- package/lib/channels/microsoft-copilot-entra-identifier-uris.js +187 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +106 -18
- package/lib/channels/microsoft-copilot-entra-sso.js +31 -2
- package/lib/channels/microsoft-copilot-package-prior.js +107 -0
- package/lib/channels/microsoft-copilot-package.js +29 -28
- package/lib/channels/microsoft-provisioning/a365.js +80 -0
- package/lib/channels/microsoft-provisioning/auth-config.js +240 -0
- package/lib/channels/microsoft-provisioning/auth.js +179 -0
- package/lib/channels/microsoft-provisioning/cli.js +71 -0
- package/lib/channels/microsoft-provisioning/constants.js +75 -0
- package/lib/channels/microsoft-provisioning/errors.js +117 -0
- package/lib/channels/microsoft-provisioning/index.js +55 -0
- package/lib/channels/microsoft-provisioning/install-atk.js +158 -0
- package/lib/channels/microsoft-provisioning/install-teams.js +115 -0
- package/lib/channels/microsoft-provisioning/login-portal-device.js +236 -0
- package/lib/channels/microsoft-provisioning/login-portal-parse.js +71 -0
- package/lib/channels/microsoft-provisioning/login-portal-teams.js +193 -0
- package/lib/channels/microsoft-provisioning/login-portal.js +230 -0
- package/lib/channels/microsoft-provisioning/persist.js +110 -0
- package/lib/channels/microsoft-provisioning/teams-bot-args.js +119 -0
- package/lib/channels/microsoft-provisioning/teams-bot-controller.js +123 -0
- package/lib/channels/microsoft-provisioning/teams-bot-parse.js +134 -0
- package/lib/channels/microsoft-provisioning/teams-bot-persist.js +113 -0
- package/lib/channels/microsoft-provisioning/teams-bot-provision.js +234 -0
- package/lib/channels/microsoft-provisioning/teams-bot-secrets.js +45 -0
- package/lib/channels/microsoft-provisioning/teams-cli.js +82 -0
- package/lib/channels/runtime-tool-catalog.js +5 -2
- package/lib/channels/slack-channel-add-portal.js +130 -0
- package/lib/channels/slack-channel-add-tty-auth.js +95 -0
- package/lib/channels/slack-channel-add.js +258 -0
- package/lib/channels/slack-provisioning/apps.js +181 -0
- package/lib/channels/slack-provisioning/auth-store.js +110 -0
- package/lib/channels/slack-provisioning/auth-ticket.js +185 -0
- package/lib/channels/slack-provisioning/auth.js +115 -0
- package/lib/channels/slack-provisioning/callbacks.js +41 -0
- package/lib/channels/slack-provisioning/cli.js +226 -0
- package/lib/channels/slack-provisioning/constants.js +37 -0
- package/lib/channels/slack-provisioning/credentials.js +169 -0
- package/lib/channels/slack-provisioning/curl-shim.js +132 -0
- package/lib/channels/slack-provisioning/developer-install.js +118 -0
- package/lib/channels/slack-provisioning/env-file.js +120 -0
- package/lib/channels/slack-provisioning/errors.js +47 -0
- package/lib/channels/slack-provisioning/go-ca-bundle.js +137 -0
- package/lib/channels/slack-provisioning/index.js +250 -0
- package/lib/channels/slack-provisioning/install-cli.js +323 -0
- package/lib/channels/slack-provisioning/manifest.js +89 -0
- package/lib/channels/slack-provisioning/oauth-lock.js +33 -0
- package/lib/channels/slack-provisioning/offer-cli.js +113 -0
- package/lib/channels/slack-provisioning/onboard-members.js +122 -0
- package/lib/channels/slack-provisioning/onboard.js +89 -0
- package/lib/channels/slack-provisioning/persist.js +95 -0
- package/lib/channels/slack-provisioning/remaining-urls.js +158 -0
- package/lib/channels/slack-provisioning/with-curl-on-path.js +48 -0
- package/lib/channels/teams-bot-channel-add.js +94 -0
- package/lib/channels/vendor-dataplane-url.js +249 -0
- package/lib/cli/setup-certification.help.js +2 -0
- package/lib/cli/setup-external-system-help.js +2 -0
- package/lib/cli/setup-external-system.js +1 -1
- package/lib/commands/channel-add-help.js +366 -0
- package/lib/commands/channel-add-setup.js +162 -80
- package/lib/commands/channel-configure.js +118 -0
- package/lib/commands/channel.js +10 -1
- package/lib/commands/governance-upload-external.js +6 -3
- package/lib/commands/lifecycle-command-action.js +2 -5
- package/lib/commands/lifecycle-external.js +93 -21
- package/lib/commands/repair-datasource-test-payload.js +4 -2
- package/lib/commands/repair-datasource-webhooks-sync-bridge.js +23 -2
- package/lib/commands/repair-datasource.js +3 -0
- package/lib/commands/repair-external-run.js +59 -25
- package/lib/commands/repair-openapi-rest-runtime.js +127 -0
- package/lib/commands/repair-orchestration-tag-merge.js +339 -0
- package/lib/commands/repair-rbac-catalog-defaults.js +96 -0
- package/lib/commands/repair-rbac-platform-preset.js +26 -15
- package/lib/commands/repair-rbac-reader-roles.js +8 -21
- package/lib/commands/repair-rbac.js +10 -34
- package/lib/commands/repair-system-permissions.js +24 -22
- package/lib/commands/role-assistant-test-output.js +14 -0
- package/lib/commands/role-assistant.js +59 -21
- package/lib/commands/test-e2e-external.js +148 -21
- package/lib/commands/upload-credential-push-log.js +2 -1
- package/lib/commands/upload-publish-context.js +1 -1
- package/lib/commands/upload-role-assistant-shell.js +124 -0
- package/lib/commands/upload-sidecar-followups.js +6 -3
- package/lib/commands/upload.js +18 -3
- package/lib/core/secrets-docker-env.js +4 -1
- package/lib/core/secrets-env-content.js +17 -3
- package/lib/datasource/capability/capability-manifest.js +0 -3
- package/lib/datasource/capability/copy-test-payload.js +3 -0
- package/lib/external-system/deploy-helpers.js +26 -1
- package/lib/external-system/deploy.js +10 -6
- package/lib/external-system/download-helpers.js +29 -2
- package/lib/external-system/download-role-assistant.js +83 -4
- package/lib/external-system/download.js +40 -30
- package/lib/external-system/sync-deploy-manifest.js +3 -2
- package/lib/external-system/upload-identity.js +42 -0
- package/lib/generator/index.js +19 -15
- package/lib/generator/split.js +13 -2
- package/lib/lifecycle/role-assistant-governance.js +146 -0
- package/lib/lifecycle/scenario-upload.js +181 -30
- package/lib/programmatic/builder-api-cors.js +143 -0
- package/lib/programmatic/builder-api-request.js +6 -0
- package/lib/programmatic/builder-api-route-dispatch.js +56 -42
- package/lib/programmatic/builder-api-server.js +8 -1
- package/lib/programmatic/capability-registry.js +54 -0
- package/lib/programmatic/channel-install-help-resolve.js +359 -0
- package/lib/programmatic/channel-install-session.js +218 -0
- package/lib/programmatic/help-content/channel-onboarding.md +2 -0
- package/lib/programmatic/help-content/cip-overview.md +3 -1
- package/lib/programmatic/http-raw-response.js +62 -0
- package/lib/programmatic/openapi-channel-install-bindings.js +179 -0
- package/lib/programmatic/openapi-channel-install-schemas.js +286 -0
- package/lib/programmatic/openapi-component-schemas.js +18 -1
- package/lib/programmatic/openapi-description-enricher.js +3 -1
- package/lib/programmatic/openapi-descriptions.js +74 -3
- package/lib/programmatic/openapi-envelope-examples.js +117 -0
- package/lib/programmatic/openapi-operation-bindings.js +4 -1
- package/lib/programmatic/openapi-schema-descriptions-channel.js +316 -0
- package/lib/programmatic/openapi-spec.js +12 -8
- package/lib/programmatic/platform-rbac-catalog.js +36 -18
- package/lib/programmatic/route-handler-map.js +51 -1
- package/lib/programmatic/route-handlers-context.js +2 -1
- package/lib/programmatic/run-auth-permissions.js +54 -0
- package/lib/programmatic/run-channel-catalog.js +196 -0
- package/lib/programmatic/run-channel-install-artifacts.js +137 -0
- package/lib/programmatic/run-channel-install-copilot.js +353 -0
- package/lib/programmatic/run-channel-install-options.js +232 -0
- package/lib/programmatic/run-channel-install-teams-bot.js +204 -0
- package/lib/programmatic/run-channel-install.js +346 -0
- package/lib/programmatic/run-channel-package.js +87 -7
- package/lib/programmatic/workspace-context.js +2 -0
- package/lib/role-assistant/evidence-sync.js +4 -2
- package/lib/role-assistant/evidence-validation-issues.js +51 -0
- package/lib/role-assistant/knowledge-sync.js +10 -4
- package/lib/role-assistant/settings.js +49 -2
- package/lib/role-assistant/test-cases-evaluate.js +30 -2
- package/lib/role-assistant/test-cases-evidence.js +175 -0
- package/lib/role-assistant/test-cases-search.js +229 -0
- package/lib/role-assistant/test-cases.js +37 -4
- package/lib/role-assistant/test-runner-search.js +149 -0
- package/lib/role-assistant/test-runner-workhub-helpers.js +40 -2
- package/lib/role-assistant/test-runner-workhub-soft.js +9 -3
- package/lib/role-assistant/test-runner-workhub.js +29 -11
- package/lib/role-assistant/test-runner.js +16 -1
- package/lib/schema/external-datasource.schema.json +119 -16
- package/lib/schema/external-system.schema.json +159 -2
- package/lib/schema/mcp-client-profile.schema.json +103 -0
- package/lib/utils/app-path-resolve.js +4 -0
- package/lib/utils/authentication-env-template.js +44 -6
- package/lib/utils/credential-secrets-env.js +6 -1
- package/lib/utils/env-copy.js +19 -7
- package/lib/utils/external-env-template.js +20 -2
- package/lib/utils/paths.js +18 -1
- package/lib/utils/platform-shell-package.js +69 -0
- package/lib/utils/port-resolver.js +50 -1
- package/lib/utils/rewrite-name-flag.js +20 -0
- package/lib/utils/role-assistant-paths.js +13 -10
- package/lib/utils/schema-resolver.js +11 -1
- package/lib/utils/secrets-helpers.js +12 -21
- package/lib/utils/token-manager.js +10 -5
- package/lib/validation/validate.js +40 -0
- package/package.json +1 -1
- package/templates/README.md +1 -1
- package/templates/applications/builder-api/Dockerfile +1 -1
- package/templates/applications/builder-api/env.template +7 -0
- package/templates/applications/dataplane/application.yaml +6 -5
- package/templates/applications/dataplane/env.template +2 -2
- package/templates/applications/dataplane/rbac.yaml +7 -2
- package/templates/applications/keycloak/Dockerfile +30 -4
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +896 -0
- package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +19 -0
- package/templates/applications/keycloak/env.template +3 -0
- package/templates/applications/miso-controller/application.yaml +1 -2
- package/templates/applications/miso-controller/env.template +2 -0
- package/templates/channels/README.md +5 -5
- package/templates/channels/chatgpt/TROUBLESHOOTING.md +51 -0
- package/templates/channels/chatgpt/channel.json +59 -0
- package/templates/channels/chatgpt/icon.svg +1 -0
- package/templates/channels/chatgpt/install.template.md.hbs +20 -95
- package/templates/channels/chatgpt/integration/README.md +1 -1
- package/templates/channels/chatgpt/integration/application.json +2 -2
- package/templates/channels/chatgpt/integration/chatgpt-system.json +49 -4
- package/templates/channels/chatgpt/integration/env.template +2 -4
- package/templates/channels/chatgpt/integration/rbac.json +13 -0
- package/templates/channels/claude/TROUBLESHOOTING.md +41 -0
- package/templates/channels/claude/channel.json +59 -0
- package/templates/channels/claude/icon.svg +1 -0
- package/templates/channels/claude/install.template.md.hbs +21 -0
- package/templates/channels/claude/integration/README.md +14 -0
- package/templates/channels/claude/integration/application.json +15 -0
- package/templates/channels/claude/integration/claude-system.json +59 -0
- package/templates/channels/claude/integration/env.template +2 -0
- package/templates/channels/claude/integration/rbac.json +13 -0
- package/templates/channels/claude/manifest.template.json.hbs +40 -0
- package/templates/channels/mcp-client/channel.json +72 -0
- package/templates/channels/mcp-client/icon.svg +1 -0
- package/templates/channels/mcp-client/install.template.md.hbs +24 -0
- package/templates/channels/mcp-client/integration/README.md +13 -0
- package/templates/channels/mcp-client/integration/application.json +15 -0
- package/templates/channels/mcp-client/integration/env.template +2 -0
- package/templates/channels/mcp-client/integration/mcp-client-system.json +72 -0
- package/templates/channels/mcp-client/integration/rbac.json +13 -0
- package/templates/channels/mcp-client/manifest.template.json.hbs +41 -0
- package/templates/channels/mcp-client/profiles/cursor.json +26 -0
- package/templates/channels/mcp-client/profiles/github-copilot.json +29 -0
- package/templates/channels/mcp-client/profiles/vscode.json +29 -0
- package/templates/channels/microsoft-copilot/channel.json +73 -0
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +8 -4
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +65 -5
- package/templates/channels/microsoft-copilot/icon.svg +1 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +9 -9
- package/templates/channels/microsoft-copilot/integration/README.md +1 -1
- package/templates/channels/microsoft-copilot/integration/application.json +2 -2
- package/templates/channels/microsoft-copilot/integration/env.template +3 -6
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +63 -4
- package/templates/channels/microsoft-copilot/integration/rbac.json +13 -0
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +5 -5
- package/templates/channels/slack/channel.json +223 -0
- package/templates/channels/slack/icon.svg +1 -0
- package/templates/channels/slack/install.template.md.hbs +36 -28
- package/templates/channels/slack/integration/README.md +245 -22
- package/templates/channels/slack/integration/application.json +44 -7
- package/templates/channels/slack/integration/env.template +15 -6
- package/templates/channels/slack/integration/openapi/openapi.json +126 -0
- package/templates/channels/slack/integration/rbac.json +84 -0
- package/templates/channels/slack/integration/slack-datasource-commands.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-conversations.json +28 -3
- package/templates/channels/slack/integration/slack-datasource-events.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-interactive.json +154 -0
- package/templates/channels/slack/integration/slack-datasource-messages.json +40 -2
- package/templates/channels/slack/integration/slack-system.json +155 -77
- package/templates/channels/slack/manifest.template.json.hbs +5 -2
- package/templates/channels/teams-bot/channel.json +161 -0
- package/templates/channels/teams-bot/icon.svg +1 -0
- package/templates/channels/teams-bot/install.template.md.hbs +12 -13
- package/templates/channels/teams-bot/integration/README.md +5 -4
- package/templates/channels/teams-bot/integration/application.json +6 -4
- package/templates/channels/teams-bot/integration/env.template +5 -4
- package/templates/channels/teams-bot/integration/openapi/openapi.json +68 -0
- package/templates/channels/teams-bot/integration/rbac.json +21 -0
- package/templates/channels/teams-bot/integration/teams-bot-datasource-activities.json +154 -0
- package/templates/channels/teams-bot/integration/teams-bot-system.json +128 -23
- package/templates/channels/web-embed/channel.json +60 -0
- package/templates/channels/web-embed/install.template.md.hbs +1 -1
- package/templates/channels/web-embed/integration/README.md +2 -2
- package/templates/channels/web-embed/integration/application.json +2 -2
- package/templates/channels/web-embed/integration/env.template +1 -1
- package/templates/channels/web-embed/integration/rbac.json +13 -0
- package/templates/channels/web-embed/integration/web-embed-system.json +50 -4
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Official Slack CLI installer (Mac/Linux install.sh only).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Do not apt/snap install the Slack desktop app
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const https = require('https');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { promisify } = require('util');
|
|
14
|
+
const { spawn } = require('child_process');
|
|
15
|
+
const execFileDefault = promisify(require('child_process').execFile);
|
|
16
|
+
const chalk = require('chalk');
|
|
17
|
+
const ora = require('ora');
|
|
18
|
+
const logger = require('../../utils/logger');
|
|
19
|
+
const { formatSuccessLine } = require('../../utils/cli-layout-chalk');
|
|
20
|
+
const { SLACK_CLI_INSTALL_SCRIPT_URL } = require('./constants');
|
|
21
|
+
const { slackProvisioningError } = require('./errors');
|
|
22
|
+
const { withGoTrustedCa } = require('./go-ca-bundle');
|
|
23
|
+
const { withCurlOnPath } = require('./with-curl-on-path');
|
|
24
|
+
|
|
25
|
+
const MAX_SCRIPT_BYTES = 512 * 1024;
|
|
26
|
+
const MAX_REDIRECTS = 5;
|
|
27
|
+
const SLACK_CLI_FALLBACK_HOME = 'aifabrix-slack-home';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} dir
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isRootHomeDir(dir) {
|
|
34
|
+
const resolved = path.resolve(dir);
|
|
35
|
+
return resolved === path.parse(resolved).root;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {unknown} value
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function usableHomeDir(value) {
|
|
43
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
44
|
+
if (!trimmed || isRootHomeDir(trimmed)) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
return path.resolve(trimmed);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* HOME=/ in builder-api cannot mkdir ~/.slack. Use a writable temp home instead.
|
|
52
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
function slackCliHomeDir(env = process.env) {
|
|
56
|
+
const ownHome = Boolean(env && Object.prototype.hasOwnProperty.call(env, 'HOME'));
|
|
57
|
+
const home = ownHome
|
|
58
|
+
? usableHomeDir(env.HOME)
|
|
59
|
+
: usableHomeDir(env && env.HOME) || usableHomeDir(process.env.HOME);
|
|
60
|
+
if (home) {
|
|
61
|
+
return home;
|
|
62
|
+
}
|
|
63
|
+
const fallback = path.join(os.tmpdir(), SLACK_CLI_FALLBACK_HOME);
|
|
64
|
+
fs.mkdirSync(fallback, { recursive: true });
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
function isSlackCliInstallerSupported() {
|
|
72
|
+
return process.platform === 'linux' || process.platform === 'darwin';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
function slackCliLocalBinDir(env = process.env) {
|
|
80
|
+
return path.join(slackCliHomeDir(env), '.local', 'bin');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
85
|
+
* @returns {NodeJS.ProcessEnv}
|
|
86
|
+
*/
|
|
87
|
+
function withSlackCliPath(env = process.env) {
|
|
88
|
+
const home = slackCliHomeDir(env);
|
|
89
|
+
const dir = slackCliLocalBinDir({ ...env, HOME: home });
|
|
90
|
+
const current = env.PATH || '';
|
|
91
|
+
const parts = current.split(path.delimiter).filter(Boolean);
|
|
92
|
+
const nextPath = parts.includes(dir) ? current : `${dir}${path.delimiter}${current}`;
|
|
93
|
+
return withGoTrustedCa({ ...env, HOME: home, PATH: nextPath }, home);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Object} [deps]
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
function applySlackCliPath(deps = {}) {
|
|
101
|
+
const next = withSlackCliPath(deps.env || process.env);
|
|
102
|
+
deps.env = next;
|
|
103
|
+
process.env.PATH = next.PATH;
|
|
104
|
+
if (next.SSL_CERT_FILE) {
|
|
105
|
+
process.env.SSL_CERT_FILE = next.SSL_CERT_FILE;
|
|
106
|
+
}
|
|
107
|
+
if (!usableHomeDir(process.env.HOME)) {
|
|
108
|
+
process.env.HOME = next.HOME;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {string} url
|
|
114
|
+
* @param {import('http').IncomingMessage} res
|
|
115
|
+
* @param {number} redirects
|
|
116
|
+
* @param {(value: string) => void} resolve
|
|
117
|
+
* @param {(reason: Error) => void} reject
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
function followHttpsRedirect(url, res, redirects, resolve, reject) {
|
|
121
|
+
const status = res.statusCode || 0;
|
|
122
|
+
if (!(status >= 300 && status < 400 && res.headers.location)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
res.resume();
|
|
126
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
127
|
+
reject(new Error('Too many redirects downloading Slack CLI installer.'));
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const nextUrl = new URL(res.headers.location, url).toString();
|
|
131
|
+
if (!nextUrl.startsWith('https://')) {
|
|
132
|
+
reject(new Error('Slack CLI installer redirect was not HTTPS.'));
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
fetchHttpsText(nextUrl, redirects + 1).then(resolve, reject);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* @param {import('http').IncomingMessage} res
|
|
141
|
+
* @param {(value: string) => void} resolve
|
|
142
|
+
* @param {(reason: Error) => void} reject
|
|
143
|
+
* @returns {void}
|
|
144
|
+
*/
|
|
145
|
+
function readInstallScriptBody(res, resolve, reject) {
|
|
146
|
+
const chunks = [];
|
|
147
|
+
let size = 0;
|
|
148
|
+
res.on('data', (chunk) => {
|
|
149
|
+
size += chunk.length;
|
|
150
|
+
if (size > MAX_SCRIPT_BYTES) {
|
|
151
|
+
res.destroy(new Error('Slack CLI installer script is larger than expected.'));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
chunks.push(chunk);
|
|
155
|
+
});
|
|
156
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
157
|
+
res.on('error', reject);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} url
|
|
162
|
+
* @param {number} [redirects]
|
|
163
|
+
* @returns {Promise<string>}
|
|
164
|
+
*/
|
|
165
|
+
function fetchHttpsText(url, redirects = 0) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const req = https.get(url, { timeout: 30000 }, (res) => {
|
|
168
|
+
if (followHttpsRedirect(url, res, redirects, resolve, reject)) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if ((res.statusCode || 0) !== 200) {
|
|
172
|
+
res.resume();
|
|
173
|
+
reject(new Error(`Slack CLI installer download failed (${res.statusCode || 0}).`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
readInstallScriptBody(res, resolve, reject);
|
|
177
|
+
});
|
|
178
|
+
req.on('error', reject);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Prefer child stderr over Node's "Command failed: bash /tmp/…" wrapper.
|
|
184
|
+
* @param {unknown} error
|
|
185
|
+
* @returns {string}
|
|
186
|
+
*/
|
|
187
|
+
function slackCliInstallFailureMessage(error) {
|
|
188
|
+
const stderr = error && error.stderr ? String(error.stderr).trim() : '';
|
|
189
|
+
const stdout = error && error.stdout ? String(error.stdout).trim() : '';
|
|
190
|
+
const raw = error && error.message ? String(error.message) : '';
|
|
191
|
+
const fromChild = stderr || stdout;
|
|
192
|
+
if (fromChild) {
|
|
193
|
+
const lines = fromChild.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
194
|
+
return lines[lines.length - 1] || fromChild;
|
|
195
|
+
}
|
|
196
|
+
const stripped = raw.replace(/^Command failed: bash \S+\s*/i, '').trim();
|
|
197
|
+
return stripped || 'Slack CLI install failed.';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function assertInstallScript(script) {
|
|
201
|
+
const text = String(script || '').trim();
|
|
202
|
+
if (!text.startsWith('#!')) {
|
|
203
|
+
throw slackProvisioningError('Slack CLI installer script is not a shell script.', 'SLACK_CLI');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @param {Object} [deps]
|
|
209
|
+
* @returns {Promise<void>}
|
|
210
|
+
*/
|
|
211
|
+
async function runInstallScript(deps = {}) {
|
|
212
|
+
const fetchFn = deps.fetchInstallScript || fetchHttpsText;
|
|
213
|
+
const script = await fetchFn(SLACK_CLI_INSTALL_SCRIPT_URL);
|
|
214
|
+
assertInstallScript(script);
|
|
215
|
+
const scriptPath = path.join(os.tmpdir(), `aifabrix-slack-cli-install-${process.pid}.sh`);
|
|
216
|
+
fs.writeFileSync(scriptPath, script, { mode: 0o700 });
|
|
217
|
+
const execFileImpl = deps.execFile || execFileDefault;
|
|
218
|
+
const env = await withCurlOnPath(withSlackCliPath(deps.env || process.env));
|
|
219
|
+
try {
|
|
220
|
+
await execFileImpl('bash', [scriptPath], {
|
|
221
|
+
env,
|
|
222
|
+
timeout: deps.timeout || 180000,
|
|
223
|
+
maxBuffer: 2 * 1024 * 1024
|
|
224
|
+
});
|
|
225
|
+
} catch (error) {
|
|
226
|
+
throw slackProvisioningError(slackCliInstallFailureMessage(error), 'SLACK_CLI');
|
|
227
|
+
} finally {
|
|
228
|
+
try {
|
|
229
|
+
fs.unlinkSync(scriptPath);
|
|
230
|
+
} catch {
|
|
231
|
+
// ignore
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* @param {string} text
|
|
238
|
+
* @returns {import('ora').Ora|null}
|
|
239
|
+
*/
|
|
240
|
+
function startInstallSpinner(text) {
|
|
241
|
+
if (!process.stdout || !process.stdout.isTTY) {
|
|
242
|
+
logger.log(chalk.white(text));
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
return ora({ text: chalk.white(text), spinner: 'dots' }).start();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Official Slack install.sh. Never apt/snap `slack` (desktop app).
|
|
250
|
+
* @param {Object} [deps]
|
|
251
|
+
* @returns {Promise<void>}
|
|
252
|
+
*/
|
|
253
|
+
async function installOfficialSlackCli(deps = {}) {
|
|
254
|
+
if (typeof deps.installCli === 'function') {
|
|
255
|
+
await deps.installCli();
|
|
256
|
+
applySlackCliPath(deps);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!isSlackCliInstallerSupported()) {
|
|
260
|
+
throw slackProvisioningError(
|
|
261
|
+
'Automatic Slack CLI install is available on Linux and macOS only.',
|
|
262
|
+
'SLACK_CLI_MISSING'
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const spinner = startInstallSpinner('Installing Slack CLI…');
|
|
266
|
+
try {
|
|
267
|
+
await runInstallScript(deps);
|
|
268
|
+
applySlackCliPath(deps);
|
|
269
|
+
if (spinner) {
|
|
270
|
+
spinner.stop();
|
|
271
|
+
}
|
|
272
|
+
logger.log(formatSuccessLine('Slack CLI installed'));
|
|
273
|
+
} catch (error) {
|
|
274
|
+
if (spinner) {
|
|
275
|
+
spinner.stop();
|
|
276
|
+
}
|
|
277
|
+
throw slackProvisioningError(
|
|
278
|
+
error && error.message ? error.message : 'Slack CLI install failed.',
|
|
279
|
+
'SLACK_CLI'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* @param {Object} [deps]
|
|
286
|
+
* @returns {Promise<void>}
|
|
287
|
+
*/
|
|
288
|
+
async function runSlackLogin(deps = {}) {
|
|
289
|
+
if (typeof deps.slackLogin === 'function') {
|
|
290
|
+
await deps.slackLogin();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const bin = typeof deps.slackBinary === 'string' && deps.slackBinary.trim()
|
|
294
|
+
? deps.slackBinary.trim()
|
|
295
|
+
: 'slack';
|
|
296
|
+
await new Promise((resolve, reject) => {
|
|
297
|
+
const child = spawn(bin, ['login'], {
|
|
298
|
+
stdio: 'inherit',
|
|
299
|
+
env: withSlackCliPath(deps.env || process.env)
|
|
300
|
+
});
|
|
301
|
+
child.on('error', reject);
|
|
302
|
+
child.on('exit', (code) => {
|
|
303
|
+
if (code === 0) {
|
|
304
|
+
resolve();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
reject(slackProvisioningError(`slack login exited ${code}.`, 'SLACK_CLI'));
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = {
|
|
313
|
+
isSlackCliInstallerSupported,
|
|
314
|
+
slackCliHomeDir,
|
|
315
|
+
slackCliLocalBinDir,
|
|
316
|
+
withSlackCliPath,
|
|
317
|
+
applySlackCliPath,
|
|
318
|
+
fetchHttpsText,
|
|
319
|
+
assertInstallScript,
|
|
320
|
+
slackCliInstallFailureMessage,
|
|
321
|
+
installOfficialSlackCli,
|
|
322
|
+
runSlackLogin
|
|
323
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Slack manifest checks — generated artifact is SSOT.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Do not rebuild Slack fields; validate the generated JSON
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { slackProvisioningError } = require('./errors');
|
|
10
|
+
const { SLACK_APP_LONG_DESCRIPTION_MIN_LENGTH } = require('../channel-description');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} content
|
|
14
|
+
* @returns {Object}
|
|
15
|
+
*/
|
|
16
|
+
function parseGeneratedManifest(content) {
|
|
17
|
+
if (typeof content !== 'string' || !content.trim()) {
|
|
18
|
+
throw slackProvisioningError('Generated Slack manifest is empty.', 'MANIFEST');
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(content);
|
|
22
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
23
|
+
throw new Error('not an object');
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
} catch {
|
|
27
|
+
throw slackProvisioningError('Generated Slack manifest is not valid JSON.', 'MANIFEST');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {Object} [display]
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
function hasDisplayParts(display) {
|
|
36
|
+
const name = display && display.name;
|
|
37
|
+
const longDescription = String((display && display.long_description) || '').trim();
|
|
38
|
+
return Boolean(name && longDescription.length >= SLACK_APP_LONG_DESCRIPTION_MIN_LENGTH);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Object} [features]
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function hasFeatureParts(features) {
|
|
46
|
+
const commands = features && features.slash_commands;
|
|
47
|
+
return Boolean(features && features.bot_user && Array.isArray(commands) && commands.length > 0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {Object} [settings]
|
|
52
|
+
* @returns {boolean}
|
|
53
|
+
*/
|
|
54
|
+
function hasSettingsParts(settings) {
|
|
55
|
+
return Boolean(settings && settings.event_subscriptions && settings.interactivity);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Object} manifest
|
|
60
|
+
* @returns {boolean}
|
|
61
|
+
*/
|
|
62
|
+
function hasRequiredManifestParts(manifest) {
|
|
63
|
+
return Boolean(
|
|
64
|
+
hasDisplayParts(manifest && manifest.display_information) &&
|
|
65
|
+
hasFeatureParts(manifest && manifest.features) &&
|
|
66
|
+
hasSettingsParts(manifest && manifest.settings)
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Object} manifest
|
|
72
|
+
* @returns {void}
|
|
73
|
+
*/
|
|
74
|
+
function assertLocalManifest(manifest) {
|
|
75
|
+
if (!hasRequiredManifestParts(manifest)) {
|
|
76
|
+
throw slackProvisioningError(
|
|
77
|
+
'Generated Slack manifest is missing required display, long_description (min 175 characters), bot, events, interactivity, or slash commands.',
|
|
78
|
+
'MANIFEST'
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (manifest.settings && manifest.settings.socket_mode_enabled === true) {
|
|
82
|
+
throw slackProvisioningError('Slack Socket Mode must stay disabled for this channel.', 'MANIFEST');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
parseGeneratedManifest,
|
|
88
|
+
assertLocalManifest
|
|
89
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep Slack OAuth fields Slack will not let us turn off.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Token rotation cannot be disabled after it was enabled.
|
|
5
|
+
* Never copy remote OAuth redirect URLs — Slack HMAC does not use a callback.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {Object} manifest
|
|
12
|
+
* @param {Object|null} remote
|
|
13
|
+
* @returns {Object}
|
|
14
|
+
*/
|
|
15
|
+
function preserveSlackOAuthLocks(manifest, remote) {
|
|
16
|
+
if (!manifest || typeof manifest !== 'object') {
|
|
17
|
+
return manifest;
|
|
18
|
+
}
|
|
19
|
+
const next = {
|
|
20
|
+
...manifest,
|
|
21
|
+
settings: { ...(manifest.settings || {}) },
|
|
22
|
+
oauth_config: { ...(manifest.oauth_config || {}) }
|
|
23
|
+
};
|
|
24
|
+
if (remote && remote.settings && remote.settings.token_rotation_enabled === true) {
|
|
25
|
+
next.settings.token_rotation_enabled = true;
|
|
26
|
+
}
|
|
27
|
+
next.oauth_config.redirect_urls = [];
|
|
28
|
+
return next;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = {
|
|
32
|
+
preserveSlackOAuthLocks
|
|
33
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offer to install Slack CLI and sign in during channel add.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Interactive TTY only — CI / JSON keep remaining steps
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const inquirer = require('inquirer');
|
|
10
|
+
const { isChannelAddInteractive } = require('../channel-add-wizard');
|
|
11
|
+
const { shouldSkipLiveSlackCli, resolveSlackBinary } = require('./cli');
|
|
12
|
+
const {
|
|
13
|
+
isSlackCliInstallerSupported,
|
|
14
|
+
installOfficialSlackCli,
|
|
15
|
+
runSlackLogin
|
|
16
|
+
} = require('./install-cli');
|
|
17
|
+
const { slackProvisioningError } = require('./errors');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {Object} [deps]
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function shouldSkipCliOffer(deps = {}) {
|
|
24
|
+
if (typeof deps.confirm === 'function') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (typeof deps.slackApi === 'function') {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return shouldSkipLiveSlackCli(deps);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Object} [options]
|
|
35
|
+
* @param {Object} [deps]
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
function canOfferSlackCliInstall(options = {}, deps = {}) {
|
|
39
|
+
if (typeof deps.confirm === 'function') {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (!isSlackCliInstallerSupported()) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return isChannelAddInteractive(options);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} message
|
|
50
|
+
* @param {Object} [deps]
|
|
51
|
+
* @returns {Promise<boolean>}
|
|
52
|
+
*/
|
|
53
|
+
async function confirmYesNo(message, deps = {}) {
|
|
54
|
+
if (typeof deps.confirm === 'function') {
|
|
55
|
+
return deps.confirm(message);
|
|
56
|
+
}
|
|
57
|
+
const answers = await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
name: 'ok',
|
|
61
|
+
message,
|
|
62
|
+
default: true
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
return answers.ok === true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {Object} [deps]
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function offerInstallAndLogin(deps = {}) {
|
|
73
|
+
const accepted = await confirmYesNo('Install Slack CLI now?', deps);
|
|
74
|
+
if (!accepted) {
|
|
75
|
+
throw slackProvisioningError('Slack CLI install declined.', 'SLACK_CLI_MISSING');
|
|
76
|
+
}
|
|
77
|
+
await installOfficialSlackCli(deps);
|
|
78
|
+
const signIn = await confirmYesNo('Sign in to Slack now?', deps);
|
|
79
|
+
if (signIn) {
|
|
80
|
+
await runSlackLogin(deps);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Prompt + install Slack CLI when missing, then continue channel add.
|
|
86
|
+
* @param {Object} [options]
|
|
87
|
+
* @param {Object} [deps]
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async function ensureSlackCliReady(options = {}, deps = {}) {
|
|
91
|
+
if (shouldSkipCliOffer(deps)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const existing = await resolveSlackBinary(deps);
|
|
95
|
+
if (existing) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (!canOfferSlackCliInstall(options, deps)) {
|
|
99
|
+
throw slackProvisioningError(
|
|
100
|
+
'Slack CLI (`slack`) is not installed or not on PATH.',
|
|
101
|
+
'SLACK_CLI_MISSING'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
await offerInstallAndLogin(deps);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
shouldSkipCliOffer,
|
|
109
|
+
canOfferSlackCliInstall,
|
|
110
|
+
confirmYesNo,
|
|
111
|
+
offerInstallAndLogin,
|
|
112
|
+
ensureSlackCliReady
|
|
113
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack workspace member list + email-join helpers for channel onboard.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Skip bots; never print emails or Slack user ids on TTY
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { listUsers } = require('../../api/users.api');
|
|
10
|
+
const { extractUserListItems } = require('../../identity/resolve-user-id');
|
|
11
|
+
const { runSlackApi } = require('./cli');
|
|
12
|
+
const { slackProvisioningError } = require('./errors');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} member
|
|
16
|
+
* @returns {'skip'|'unmatched'|'candidate'}
|
|
17
|
+
*/
|
|
18
|
+
function classifySlackMember(member) {
|
|
19
|
+
if (!member || member.deleted === true) {
|
|
20
|
+
return 'skip';
|
|
21
|
+
}
|
|
22
|
+
if (member.is_bot === true || member.is_app_user === true || member.id === 'USLACKBOT') {
|
|
23
|
+
return 'skip';
|
|
24
|
+
}
|
|
25
|
+
if (typeof member.id !== 'string' || !member.id.trim()) {
|
|
26
|
+
return 'skip';
|
|
27
|
+
}
|
|
28
|
+
const email = member.profile && String(member.profile.email || '').trim();
|
|
29
|
+
if (!email || !email.includes('@')) {
|
|
30
|
+
return 'unmatched';
|
|
31
|
+
}
|
|
32
|
+
return 'candidate';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} [deps]
|
|
37
|
+
* @returns {Promise<Object[]>}
|
|
38
|
+
*/
|
|
39
|
+
async function listSlackWorkspaceMembers(deps = {}) {
|
|
40
|
+
const members = [];
|
|
41
|
+
let cursor = '';
|
|
42
|
+
for (let page = 0; page < 50; page += 1) {
|
|
43
|
+
const payload = { limit: 200 };
|
|
44
|
+
if (cursor) {
|
|
45
|
+
payload.cursor = cursor;
|
|
46
|
+
}
|
|
47
|
+
const body = await runSlackApi('users.list', payload, deps);
|
|
48
|
+
if (!body || body.ok !== true) {
|
|
49
|
+
throw slackProvisioningError('Slack users.list failed.', 'SLACK_API');
|
|
50
|
+
}
|
|
51
|
+
members.push(...(Array.isArray(body.members) ? body.members : []));
|
|
52
|
+
cursor = (body.response_metadata && body.response_metadata.next_cursor) || '';
|
|
53
|
+
if (!cursor) {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return members;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @async
|
|
62
|
+
* @param {string} controllerUrl
|
|
63
|
+
* @param {Object} authConfig
|
|
64
|
+
* @param {string} email
|
|
65
|
+
* @returns {Promise<Object|null>}
|
|
66
|
+
*/
|
|
67
|
+
async function lookupUniqueUserByEmail(controllerUrl, authConfig, email) {
|
|
68
|
+
const value = String(email || '').trim();
|
|
69
|
+
if (!value) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const listed = await listUsers(controllerUrl, authConfig, {
|
|
73
|
+
filter: `email:eq:${value}`,
|
|
74
|
+
pageSize: 2,
|
|
75
|
+
page: 1
|
|
76
|
+
});
|
|
77
|
+
if (!listed || listed.success === false) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
const lower = value.toLowerCase();
|
|
81
|
+
const matches = extractUserListItems(listed).filter(
|
|
82
|
+
(user) => String(user.email || '').toLowerCase() === lower && user.id
|
|
83
|
+
);
|
|
84
|
+
return matches.length === 1 ? matches[0] : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @async
|
|
89
|
+
* @param {Object[]} members
|
|
90
|
+
* @param {{ controllerUrl: string, authConfig: Object }} ctx
|
|
91
|
+
* @param {Object} [deps]
|
|
92
|
+
* @returns {Promise<{ items: Object[], unmatched: number }>}
|
|
93
|
+
*/
|
|
94
|
+
async function matchSlackMembersToUsers(members, ctx, deps = {}) {
|
|
95
|
+
const lookup = deps.lookupUniqueUserByEmail || lookupUniqueUserByEmail;
|
|
96
|
+
const items = [];
|
|
97
|
+
let unmatched = 0;
|
|
98
|
+
for (const member of members || []) {
|
|
99
|
+
const kind = classifySlackMember(member);
|
|
100
|
+
if (kind === 'skip') {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (kind !== 'candidate') {
|
|
104
|
+
unmatched += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const user = await lookup(ctx.controllerUrl, ctx.authConfig, member.profile.email.trim());
|
|
108
|
+
if (user && user.id) {
|
|
109
|
+
items.push({ externalUserId: member.id, platformUserId: user.id });
|
|
110
|
+
} else {
|
|
111
|
+
unmatched += 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { items, unmatched };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
classifySlackMember,
|
|
119
|
+
listSlackWorkspaceMembers,
|
|
120
|
+
lookupUniqueUserByEmail,
|
|
121
|
+
matchSlackMembersToUsers
|
|
122
|
+
};
|