diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c3ecf9654f4..bd247530539 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3592,6 +3592,9 @@ importers: '@octokit/types': specifier: ^9.2.0 version: 9.2.0 + '@slack/web-api': + specifier: ^6.9.0 + version: 6.9.0 '@types/cli-table': specifier: ^0.3.1 version: 0.3.1 @@ -12433,8 +12436,8 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.21.3 - '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.17 + '@jest/types': 29.6.1 + '@jridgewell/trace-mapping': 0.3.19 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -12442,7 +12445,7 @@ packages: graceful-fs: 4.2.9 jest-haste-map: 29.5.0 jest-regex-util: 29.4.3 - jest-util: 29.5.0 + jest-util: 29.6.2 micromatch: 4.0.5 pirates: 4.0.5 slash: 3.0.0 @@ -13917,6 +13920,11 @@ packages: engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} dev: false + /@slack/types@2.8.0: + resolution: {integrity: sha512-ghdfZSF0b4NC9ckBA8QnQgC9DJw2ZceDq0BIjjRSv6XAZBXJdWgxIsYz0TYnWSiqsKZGH2ZXbj9jYABZdH3OSQ==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + dev: false + /@slack/web-api@5.15.0: resolution: {integrity: sha512-tjQ8Zqv/Fmj9SOL9yIEd7IpTiKfKHi9DKAkfRVeotoX0clMr3SqQtBqO+KZMX27gm7dmgJsQaDKlILyzdCO+IA==} engines: {node: '>= 8.9.0', npm: '>= 5.5.1'} @@ -13954,6 +13962,25 @@ packages: - debug dev: false + /@slack/web-api@6.9.0: + resolution: {integrity: sha512-RME5/F+jvQmZHkoP+ogrDbixq1Ms1mBmylzuWq4sf3f7GCpMPWoiZ+WqWk+sism3vrlveKWIgO9R4Qg9fiRyoQ==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + dependencies: + '@slack/logger': 3.0.0 + '@slack/types': 2.8.0 + '@types/is-stream': 1.1.0 + '@types/node': 16.18.21 + axios: 0.27.2 + eventemitter3: 3.1.2 + form-data: 2.5.1 + is-electron: 2.2.2 + is-stream: 1.1.0 + p-queue: 6.6.2 + p-retry: 4.6.1 + transitivePeerDependencies: + - debug + dev: false + /@storybook/addon-a11y@6.5.17-alpha.0(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-ZjYxGpuN7/euPscmgQys6QJ+ASVxrFHh28HPd8SqH+j1UmTiGnwPFYmUTm8sY8fMwormdy/H/phQ8FX6xNZ31Q==} peerDependencies: @@ -21510,6 +21537,15 @@ packages: - debug dev: true + /axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2(debug@4.3.3) + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@2.2.0: resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} @@ -30405,6 +30441,10 @@ packages: resolution: {integrity: sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q==} dev: false + /is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + dev: false + /is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -37963,7 +38003,7 @@ packages: postcss: 8.4.21 schema-utils: 3.1.1 semver: 7.5.0 - webpack: 5.70.0(webpack-cli@4.9.2) + webpack: 5.70.0(webpack-cli@3.3.12) /postcss-loader@4.3.0(postcss@8.4.21)(webpack@5.76.3): resolution: {integrity: sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q==} @@ -41117,7 +41157,7 @@ packages: sass: 1.60.0 schema-utils: 3.1.1 semver: 7.5.0 - webpack: 5.76.3(webpack-cli@3.3.12) + webpack: 5.76.3(webpack-cli@4.9.2) dev: true /sass-loader@12.6.0(sass@1.60.0)(webpack@5.76.3): diff --git a/tools/monorepo-utils/package.json b/tools/monorepo-utils/package.json index bbb3060d0ef..62f14fc3065 100644 --- a/tools/monorepo-utils/package.json +++ b/tools/monorepo-utils/package.json @@ -29,7 +29,8 @@ "promptly": "^3.2.0", "semver": "^7.3.2", "simple-git": "^3.10.0", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "@slack/web-api": "^6.9.0" }, "devDependencies": { "@types/jest": "^27.4.1", diff --git a/tools/monorepo-utils/src/slack/commands/slack/index.ts b/tools/monorepo-utils/src/slack/commands/slack/index.ts index 934a0e7fdac..c55344cf837 100644 --- a/tools/monorepo-utils/src/slack/commands/slack/index.ts +++ b/tools/monorepo-utils/src/slack/commands/slack/index.ts @@ -7,6 +7,7 @@ import { Command } from '@commander-js/extra-typings'; * Internal dependencies */ import { slackMessageCommand } from './slack-message'; +import { slackFileCommand } from './slack-file'; /** * Internal dependencies @@ -14,6 +15,7 @@ import { slackMessageCommand } from './slack-message'; const program = new Command( 'slack' ) .description( 'Slack message sending utilities' ) - .addCommand( slackMessageCommand, { isDefault: true } ); + .addCommand( slackMessageCommand, { isDefault: true } ) + .addCommand( slackFileCommand ); export default program; diff --git a/tools/monorepo-utils/src/slack/commands/slack/slack-file.ts b/tools/monorepo-utils/src/slack/commands/slack/slack-file.ts new file mode 100644 index 00000000000..6e791886c49 --- /dev/null +++ b/tools/monorepo-utils/src/slack/commands/slack/slack-file.ts @@ -0,0 +1,83 @@ +/** + * External dependencies + */ +import { Command } from '@commander-js/extra-typings'; +import { ErrorCode, WebClient } from '@slack/web-api'; +import { basename } from 'path'; + +/** + * External dependencies + */ +import { existsSync } from 'fs'; + +/** + * Internal dependencies + */ +import { Logger } from '../../../core/logger'; + +export const slackFileCommand = new Command( 'file' ) + .description( 'Send a file upload message to a slack channel' ) + .argument( + '', + 'Slack authentication token bearing required scopes.' + ) + .argument( '', 'Text based message to send to the slack channel.' ) + .argument( '', 'File path to upload to the slack channel.' ) + .argument( + '', + 'Slack channel IDs to send the message to. Pass as many as you like.' + ) + .option( + '--dont-fail', + 'Do not fail the command if a message fails to send to any channel.' + ) + .action( async ( token, text, filePath, channels, { dontFail } ) => { + Logger.startTask( + `Attempting to send message to Slack for channels: ${ channels.join( + ',' + ) }` + ); + + const shouldFail = ! dontFail; + + if ( filePath && ! existsSync( filePath ) ) { + Logger.error( + `Unable to open file with path: ${ filePath }`, + shouldFail + ); + } + + const client = new WebClient( token ); + + for ( const channel of channels ) { + try { + await client.files.uploadV2( { + file: filePath, + filename: basename( filePath ), + channel_id: channel, + initial_comment: text.replace( /\\n/g, '\n' ), + request_file_info: false, + } ); + + Logger.notice( + `Successfully uploaded ${ filePath } to channel: ${ channel }` + ); + } catch ( e ) { + if ( + 'code' in e && + e.code === ErrorCode.PlatformError && + 'message' in e && + e.message.includes( 'missing_scope' ) + ) { + Logger.error( + `The provided token does not have the required scopes, please add files:write and chat:write to the token.`, + shouldFail + ); + } else { + Logger.error( e, shouldFail ); + } + } + } + + Logger.endTask(); + } );