diff --git a/.vscode/settings.json b/.vscode/settings.json index 29dd869..afac938 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ { "editor.formatOnSave": true, - "editor.codeActionsOnSaveTimeout": 2000, "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, diff --git a/README.md b/README.md index dfdb784..e222d28 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,14 @@ The following inputs are supported. - `channel` - (required) The channel to be notified of results. +- `text` - (optional) Overrides main body text of the message. + +- `username` - (optional) Overrides bot's user name. + +- `icon_emoji` - (optional) Emoji to use as the icon for this message. Overrides `icon_url`. + +- `icon_url` - (optional) URL to an image to use as the icon for this message. + For more information, see https://api.slack.com/methods/chat.postMessage. ## Output diff --git a/action.yaml b/action.yaml index d4f4da1..7f6130c 100644 --- a/action.yaml +++ b/action.yaml @@ -19,19 +19,19 @@ inputs: description: The channel to be notified of results. required: true text: - description: todo + description: Override main body text of the message. required: false default: '' username: - description: todo + description: Override bot's user name. required: false default: '' icon_emoji: - description: todo + description: Emoji to use as the icon for this message. Overrides `icon_url`. required: false default: '' icon_url: - description: todo + description: URL to an image to use as the icon for this message. required: false default: '' diff --git a/dist/index.js b/dist/index.js index c502972..394a08f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4643,80 +4643,6 @@ class PQueue { module.exports = PQueue; -/***/ }), - -/***/ 184: -/***/ (function(__unusedmodule, exports, __webpack_require__) { - -"use strict"; - -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const github_1 = __webpack_require__(469); -const status_1 = __webpack_require__(895); -const default_1 = __webpack_require__(594); -const quote = (value) => '```' + value + '```'; -exports.messageFactory = ({ status: rawStatus, githubToken, channel }) => __awaiter(void 0, void 0, void 0, function* () { - const octokit = new github_1.GitHub(githubToken); - const { sha, workflow } = github_1.context; - const { owner, repo } = github_1.context.repo; - const { data } = yield octokit.repos.getCommit({ owner, repo, ref: sha }); - const commitMessage = data.commit.message; - const githubRepo = `${owner}/${repo}`; - const githubRepoURL = `https://github.com/${owner}/${repo}`; - const status = status_1.getStatus(rawStatus); - /* eslint-disable @typescript-eslint/camelcase */ - const attachment = { - color: default_1.defaultColors[status.type], - fields: [ - { - title: 'Commit Message', - value: quote(commitMessage), - short: false - }, - { - title: 'Repository', - value: `<${githubRepoURL}/tree/${sha}|${githubRepo}>`, - short: true - }, - { - title: 'Workflow', - value: ``, - short: true - }, - { - title: 'Status', - value: status.value, - short: true - }, - { - title: 'Commit', - value: `<${githubRepoURL}/commit/${sha}|${sha.substring(0, 7)}>`, - short: true - } - ], - footer_icon: default_1.defaultFooterIcon, - footer: default_1.defaultFooter, - ts: `${Math.floor(Date.now() / 1000)}`, - mrkdwn_in: ['fields', 'text'] - }; - return { - channel: channel, - text: default_1.defaultText[status.type], - attachments: [attachment] - }; - /* eslint-enable @typescript-eslint/camelcase */ -}); - - /***/ }), /***/ 190: @@ -4881,17 +4807,24 @@ var __importStar = (this && this.__importStar) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const core = __importStar(__webpack_require__(470)); const web_api_1 = __webpack_require__(114); -const factory_1 = __webpack_require__(184); +const builder_1 = __webpack_require__(532); function run() { return __awaiter(this, void 0, void 0, function* () { try { - const token = core.getInput('oauth_token'); + const token = core.getInput('oauth_token', { required: true }); const client = new web_api_1.WebClient(token); core.info('[Info] Slack client has been initialized.'); - const status = core.getInput('status'); - const githubToken = core.getInput('github_token'); - const channel = core.getInput('channel'); - const message = yield factory_1.messageFactory({ status, githubToken, channel }); + const optional = (value) => (value === '' ? undefined : value); + const builder = new builder_1.MessageBuilder({ + status: core.getInput('status', { required: true }), + githubToken: core.getInput('github_token', { required: true }), + channel: core.getInput('channel', { required: true }), + text: optional(core.getInput('text')), + username: optional(core.getInput('username')), + iconEmoji: optional(core.getInput('icon_emoji')), + iconURL: optional(core.getInput('icon_url')) + }); + const message = yield builder.build(); const { ok, error } = yield client.chat.postMessage(message); core.info(`[Info] Request result is ${ok}`); if (error) { @@ -23578,6 +23511,108 @@ utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { module.exports = defaults; +/***/ }), + +/***/ 532: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const github_1 = __webpack_require__(469); +const status_1 = __webpack_require__(895); +const default_1 = __webpack_require__(594); +class MessageBuilder { + constructor(option, mock) { + var _a; + this.option = option; + this.context = (_a = mock === null || mock === void 0 ? void 0 : mock.context) !== null && _a !== void 0 ? _a : github_1.context; // Use mock + this.mockCommitMessage = mock === null || mock === void 0 ? void 0 : mock.commitMessage; + } + quote(value) { + return '```' + value + '```'; + } + fetchCommitMessage() { + return __awaiter(this, void 0, void 0, function* () { + const { option, context, mockCommitMessage } = this; + if (mockCommitMessage) + return mockCommitMessage; // Use mock + const { sha } = context; + const { owner, repo } = context.repo; + const octokit = new github_1.GitHub(option.githubToken); + const { data } = yield octokit.repos.getCommit({ owner, repo, ref: sha }); + return data.commit.message; + }); + } + build() { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const { option } = this; + const { sha, workflow } = github_1.context; + const { owner, repo } = github_1.context.repo; + const githubRepo = `${owner}/${repo}`; + const githubRepoURL = `https://github.com/${owner}/${repo}`; + const status = status_1.getStatus(option.status); + // Build attachment fields + const fields = []; + const commitMessage = yield this.fetchCommitMessage(); + fields.push({ + title: 'Commit Message', + value: this.quote(commitMessage), + short: false + }); + fields.push({ + title: 'Repository', + value: `<${githubRepoURL}/tree/${sha}|${githubRepo}>`, + short: true + }); + fields.push({ + title: 'Workflow', + value: `<${githubRepoURL}/commit/${sha}/checks|${workflow}>`, + short: true + }); + fields.push({ + title: 'Status', + value: status.value, + short: true + }); + fields.push({ + title: 'Commit', + value: `<${githubRepoURL}/commit/${sha}|${sha.substring(0, 7)}>`, + short: true + }); + /* eslint-disable @typescript-eslint/camelcase */ + const attachment = { + color: default_1.defaultColors[status.type], + fields: fields, + footer_icon: default_1.defaultFooterIcon, + footer: default_1.defaultFooter, + ts: `${Math.floor(Date.now() / 1000)}`, + mrkdwn_in: ['fields', 'text'] + }; + return { + channel: option.channel, + text: (_a = option.text) !== null && _a !== void 0 ? _a : default_1.defaultText[status.type], + attachments: [attachment], + username: option.username, + icon_emoji: option.iconEmoji, + icon_url: option.iconURL + }; + }); + } +} +exports.MessageBuilder = MessageBuilder; + + /***/ }), /***/ 536: diff --git a/src/builder.ts b/src/builder.ts new file mode 100644 index 0000000..73b76a8 --- /dev/null +++ b/src/builder.ts @@ -0,0 +1,108 @@ +import { ChatPostMessageArguments, MessageAttachment } from '@slack/web-api' +import { GitHub, context } from '@actions/github' +import { Context } from '@actions/github/lib/context' +import { getStatus } from './status' +import { defaultColors, defaultText, defaultFooter, defaultFooterIcon } from './default' + +type Builder = { + build: () => Promise +} + +export type MessageBuilderOption = { + status: string + channel: string + text?: string + username?: string + iconEmoji?: string + iconURL?: string + githubToken: string +} + +export type MessageBuilderMockOption = { + context: Context + commitMessage: string +} + +export class MessageBuilder implements Builder { + private context: Context + private mockCommitMessage?: string + constructor(private option: MessageBuilderOption, mock?: MessageBuilderMockOption) { + this.context = mock?.context ?? context // Use mock + this.mockCommitMessage = mock?.commitMessage + } + + private quote(value: string) { + return '```' + value + '```' + } + + private async fetchCommitMessage(): Promise { + const { option, context, mockCommitMessage } = this + if (mockCommitMessage) return mockCommitMessage // Use mock + + const { sha } = context + const { owner, repo } = context.repo + const octokit = new GitHub(option.githubToken) + const { data } = await octokit.repos.getCommit({ owner, repo, ref: sha }) + return data.commit.message + } + + async build(): Promise { + const { option } = this + const { sha, workflow } = context + const { owner, repo } = context.repo + + const githubRepo = `${owner}/${repo}` + const githubRepoURL = `https://github.com/${owner}/${repo}` + + const status = getStatus(option.status) + + // Build attachment fields + const fields: NonNullable = [] + const commitMessage = await this.fetchCommitMessage() + fields.push({ + title: 'Commit Message', + value: this.quote(commitMessage), + short: false + }) + fields.push({ + title: 'Repository', + value: `<${githubRepoURL}/tree/${sha}|${githubRepo}>`, + short: true + }) + fields.push({ + title: 'Workflow', + value: `<${githubRepoURL}/commit/${sha}/checks|${workflow}>`, + short: true + }) + fields.push({ + title: 'Status', + value: status.value, + short: true + }) + fields.push({ + title: 'Commit', + value: `<${githubRepoURL}/commit/${sha}|${sha.substring(0, 7)}>`, + short: true + }) + + /* eslint-disable @typescript-eslint/camelcase */ + const attachment: MessageAttachment = { + color: defaultColors[status.type], + fields: fields, + footer_icon: defaultFooterIcon, + footer: defaultFooter, + ts: `${Math.floor(Date.now() / 1000)}`, + mrkdwn_in: ['fields', 'text'] + } + + return { + channel: option.channel, + text: option.text ?? defaultText[status.type], + attachments: [attachment], + username: option.username, + icon_emoji: option.iconEmoji, + icon_url: option.iconURL + } + /* eslint-enable @typescript-eslint/camelcase */ + } +} diff --git a/src/factory.ts b/src/factory.ts deleted file mode 100644 index 3999f76..0000000 --- a/src/factory.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { ChatPostMessageArguments, MessageAttachment } from '@slack/web-api' -import { GitHub, context } from '@actions/github' -import { getStatus } from './status' -import { defaultColors, defaultText, defaultFooter, defaultFooterIcon } from './default' - -const quote = (value: string) => '```' + value + '```' - -export type MessageFactoryArguments = { - status: string - githubToken: string - channel: string -} - -type MessageFactory = (args: MessageFactoryArguments) => Promise - -export const messageFactory: MessageFactory = async ({ status: rawStatus, githubToken, channel }) => { - const octokit = new GitHub(githubToken) - const { sha, workflow } = context - const { owner, repo } = context.repo - const { data } = await octokit.repos.getCommit({ owner, repo, ref: sha }) - const commitMessage = data.commit.message - - const githubRepo = `${owner}/${repo}` - const githubRepoURL = `https://github.com/${owner}/${repo}` - - const status = getStatus(rawStatus) - - /* eslint-disable @typescript-eslint/camelcase */ - const attachment: MessageAttachment = { - color: defaultColors[status.type], - fields: [ - { - title: 'Commit Message', - value: quote(commitMessage), - short: false - }, - { - title: 'Repository', - value: `<${githubRepoURL}/tree/${sha}|${githubRepo}>`, - short: true - }, - { - title: 'Workflow', - value: ``, - short: true - }, - { - title: 'Status', - value: status.value, - short: true - }, - { - title: 'Commit', - value: `<${githubRepoURL}/commit/${sha}|${sha.substring(0, 7)}>`, - short: true - } - ], - footer_icon: defaultFooterIcon, - footer: defaultFooter, - ts: `${Math.floor(Date.now() / 1000)}`, - mrkdwn_in: ['fields', 'text'] - } - - return { - channel: channel, - text: defaultText[status.type], - attachments: [attachment] - } - /* eslint-enable @typescript-eslint/camelcase */ -} diff --git a/src/main.ts b/src/main.ts index 0bd3933..f67d178 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,18 +1,26 @@ import * as core from '@actions/core' import { WebClient } from '@slack/web-api' -import { messageFactory } from './factory' +import { MessageBuilder } from './builder' async function run(): Promise { try { - const token = core.getInput('oauth_token') + const token = core.getInput('oauth_token', { required: true }) const client = new WebClient(token) core.info('[Info] Slack client has been initialized.') - const status = core.getInput('status') - const githubToken = core.getInput('github_token') - const channel = core.getInput('channel') + const optional = (value: T) => (value === '' ? undefined : value) + + const builder = new MessageBuilder({ + status: core.getInput('status', { required: true }), + githubToken: core.getInput('github_token', { required: true }), + channel: core.getInput('channel', { required: true }), + text: optional(core.getInput('text')), + username: optional(core.getInput('username')), + iconEmoji: optional(core.getInput('icon_emoji')), + iconURL: optional(core.getInput('icon_url')) + }) + const message = await builder.build() - const message = await messageFactory({ status, githubToken, channel }) const { ok, error } = await client.chat.postMessage(message) core.info(`[Info] Request result is ${ok}`) if (error) {