-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(ci): Introduce posting changelog into the Slack channel
refs #1781
- Loading branch information
Showing
8 changed files
with
769 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
SLACK_CHANGELOG_WEBHOOK_URL= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,3 +48,6 @@ nx-cloud.env | |
!.yarn/releases | ||
!.yarn/sdks | ||
!.yarn/versions | ||
|
||
# Dotenv | ||
.env* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# `post-changelog` | ||
|
||
> Post Changelog to Slack | ||
Parses the latest changelog entry when new package version is release and posts it to our Slack notifications channel. | ||
|
||
Dry run: | ||
|
||
```shell | ||
yarn zx scripts/post-changelog.mjs --dry | ||
``` | ||
|
||
Production run: | ||
|
||
```shell | ||
yarn zx scripts/post-changelog.mjs | ||
``` | ||
|
||
## Development & Testing | ||
|
||
Set up a local `.env` file with the content based on the `.env.example` file. | ||
|
||
See the [Slack Block Kit documentation][slack-block-kit-docs] and [chat.postMessage API method documentation][slack-post-message-docs] for more information. | ||
|
||
[slack-block-kit-docs]: https://api.slack.com/reference/block-kit/ | ||
[slack-post-message-docs]: https://api.slack.com/methods/chat.postMessage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
{ | ||
"name": "@lmc-eu/spirit-post-changelog", | ||
"version": "1.0.0", | ||
"description": "Post changelog to Slack of the Spirit Design System", | ||
"type": "module", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/lmc-eu/spirit-design-system.git", | ||
"directory": "scripts/post-changelog" | ||
}, | ||
"license": "MIT", | ||
"keywords": [ | ||
"post", | ||
"changelog", | ||
"slack" | ||
], | ||
"scripts": { | ||
"post-changelog": "yarn zx ./post-changelog.mjs" | ||
}, | ||
"dependencies": { | ||
"dotenv": "^16.4.7", | ||
"dotenv-safe": "^9.1.0", | ||
"gitdiff-parser": "^0.3.1", | ||
"simple-git": "^3.27.0", | ||
"slackify-markdown": "^4.4.0", | ||
"zx": "^8.3.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// This script is based on @see { @link https://github.com/kiwicom/orbit/blob/master/scripts/post-changelog.mjs } | ||
/* eslint-disable import/no-unresolved */ | ||
/* eslint-disable import/no-extraneous-dependencies */ | ||
/* eslint-disable no-console */ | ||
import { fileURLToPath } from 'url'; | ||
import dotenv from 'dotenv-safe'; | ||
import gitDiffParser from 'gitdiff-parser'; | ||
import { simpleGit } from 'simple-git'; | ||
import slackifyMarkdown from 'slackify-markdown'; | ||
import { $, fetch, argv, path } from 'zx'; | ||
|
||
const COLOR_CORE = '#00A58E'; | ||
const PACKAGES = ['web', 'web-react', 'web-twig', 'design-tokens', 'icons', 'codemods', 'analytics']; | ||
let SLACK_CHANGELOG_WEBHOOK_URL = process.env.SLACK_CHANGELOG_WEBHOOK_URL ?? ''; | ||
|
||
/** | ||
* Generates a title for the given package. | ||
* | ||
* @returns {string} The generated title. | ||
*/ | ||
function getTitle() { | ||
return `🚀 New release published`; | ||
} | ||
|
||
/** | ||
* Sends the content to the specified webhook URL. | ||
* | ||
* @param {object} params - The parameters. | ||
* @param {object} params.content - The content to send. | ||
* @param {string} params.webhookUrl - The webhook URL to send the content to. | ||
*/ | ||
async function sendToWebhook({ content, webhookUrl }) { | ||
await fetch(webhookUrl, { | ||
method: 'POST', | ||
body: JSON.stringify(content), | ||
}) | ||
.then((res) => { | ||
if (res.status !== 200) { | ||
throw new Error(`${res.status} ${res.statusText}`); | ||
} | ||
}) | ||
.catch((err) => { | ||
console.log('Error posting to Slack'); | ||
console.error(err); | ||
process.exit(1); | ||
}); | ||
} | ||
|
||
/** | ||
* Formats the changelog string with the given package name and prefix. | ||
* | ||
* @param {string} str - The changelog string to format. | ||
* @param {string} packageName - The name of the package. | ||
* @param {string} prefix - The prefix to use for the package. | ||
* | ||
* @returns {string} The formatted changelog string. | ||
*/ | ||
function format(str, packageName, prefix = '@lmc-eu') { | ||
const output = str | ||
.replace( | ||
/^(#+ )(.+)/, | ||
`# 📦 ${packageName | ||
.split('-') | ||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1)) | ||
.join(' ')} _$2_ \`${prefix}/spirit-${packageName}\``, | ||
) | ||
.replace(/\(\d{4}-\d{2}-\d{2}\)/, '') // Remove release date | ||
.replace('Bug Fixes', '🐛 Bug Fixes') | ||
.replace('Features', '⚡ Features') | ||
.replace('BREAKING CHANGES', '🚨 BREAKING CHANGES') | ||
.replace('Dependencies', '📦 Dependencies') | ||
.replace('Documentation', '📜 Documentation') | ||
.replace('Tests', '🧪 Tests') | ||
.replace('Code Refactoring', '🛠️ Code Refactoring') | ||
.replace('Chores', '🔨 Chores') | ||
.replace('Styles', '💅 Styles') | ||
.replaceAll('https://github.com/lmc-eu/spirit-design-system/issues/', 'https://jira.almacareer.tech/browse/'); | ||
|
||
return output; | ||
} | ||
|
||
/** | ||
* Extracts the changelog content from the given diff files. | ||
* | ||
* @param {Array} files - The diff files to extract the changelog from. | ||
* @returns {string} The extracted changelog content. | ||
*/ | ||
function getChangelogFromDiff(files) { | ||
// Only one file as we're only looking at the changelog | ||
const versionPattern = /<a name=".*"><\/a>/; | ||
const [changelogFile] = files; | ||
const changelog = changelogFile.hunks | ||
.flatMap((hunk) => hunk.changes.filter(({ isInsert }) => isInsert).map(({ content }) => content)) | ||
.filter((line) => !versionPattern.test(line)) | ||
.join('\n') | ||
.trim(); | ||
|
||
return changelog; | ||
} | ||
|
||
/** | ||
* Gets the diff for the given tag and path. | ||
* | ||
* @param {string} tag - The tag to get the diff for. | ||
* @param {string} path - The path to get the diff for. | ||
* @returns {Promise<string>} The diff output. | ||
*/ | ||
function getDiff(tag, path) { | ||
return simpleGit().show([tag, path]); | ||
} | ||
|
||
/** | ||
* Returns the changelog path for the given package name. | ||
* | ||
* @param {string} packageName - The name of the package. | ||
* @returns {string} The changelog path. | ||
*/ | ||
function changelogPath(packageName) { | ||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
|
||
return path.resolve(__dirname, `../../packages/${packageName}/CHANGELOG.md`); | ||
} | ||
|
||
/** | ||
* Posts a Slack notification with the given changelog. | ||
* | ||
* @param {string} changelog - The changelog content to post. | ||
* @param {string} packageName - The name of the package. | ||
* | ||
* @returns {Promise<void>} | ||
*/ | ||
async function postSlackNotification(changelog, packageName) { | ||
try { | ||
$.verbose = false; | ||
const res = await sendToWebhook({ | ||
webhookUrl: SLACK_CHANGELOG_WEBHOOK_URL, | ||
content: { | ||
attachments: [ | ||
{ | ||
color: COLOR_CORE, | ||
blocks: [ | ||
{ | ||
type: 'header', | ||
text: { | ||
type: 'plain_text', | ||
text: getTitle(packageName), | ||
emoji: true, | ||
}, | ||
}, | ||
], | ||
}, | ||
{ | ||
// The `text` field is used as fallback, but it has higher character limit (4000) then section block | ||
// @see { @link https://api.slack.com/methods/chat.postMessage#text_usage} | ||
// @see { @link https://api.slack.com/reference/block-kit/blocks#section_fields } | ||
text: changelog, | ||
color: COLOR_CORE, | ||
}, | ||
], | ||
}, | ||
}); | ||
|
||
return res; | ||
} catch (err) { | ||
console.log('Error posting to Slack'); | ||
console.error(err); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/** | ||
* Configures the webhook URL from the environment variables. | ||
*/ | ||
async function configureWebhookURL() { | ||
try { | ||
dotenv.config({ | ||
allowEmptyValues: true, | ||
example: '.env.example', | ||
}); | ||
SLACK_CHANGELOG_WEBHOOK_URL = process.env.SLACK_CHANGELOG_WEBHOOK_URL; | ||
} catch (err) { | ||
if (/SLACK_CHANGELOG_WEBHOOK_URL/g.test(err.message)) { | ||
throw new Error('SLACK_CHANGELOG_WEBHOOK_URL is not set'); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Publish the changelog for the given npm package. | ||
* | ||
* @param {string} npmPackage - The name of the npm package. | ||
*/ | ||
async function publishChangelog(npmPackage) { | ||
try { | ||
await simpleGit().fetch(['origin', 'main', '--tags']); | ||
const tags = await simpleGit().tags({ '--sort': '-taggerdate' }); | ||
const diff = await getDiff( | ||
argv.dry ? '@lmc-eu/[email protected]' : (tags.latest ?? ''), | ||
changelogPath(npmPackage), | ||
); | ||
const files = gitDiffParser.parse(diff); | ||
if (files.length === 0) { | ||
console.log(`No changes in ${npmPackage}`); | ||
|
||
return; | ||
} | ||
const changelog = getChangelogFromDiff(files); | ||
const formattedChangelog = format(changelog, npmPackage); | ||
const slackifiedChangelog = slackifyMarkdown(formattedChangelog); | ||
|
||
if (argv.dry) { | ||
console.info(slackifiedChangelog); | ||
} else { | ||
await configureWebhookURL(); | ||
await postSlackNotification(slackifiedChangelog, npmPackage); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
process.exit(1); | ||
} | ||
} | ||
|
||
(async () => { | ||
await Promise.all( | ||
PACKAGES.map(async (npmPackage) => { | ||
await publishChangelog(npmPackage); | ||
}), | ||
); | ||
process.exit(0); | ||
})(); |
Oops, something went wrong.