Skip to content

Commit b9d2bf0

Browse files
committed
feat(.changelogrc.js): transfer repository over to semantic-release CI/CD
1 parent 1fcfd40 commit b9d2bf0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+17008
-6649
lines changed

.changelogrc.js

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// ? See https://github.com/conventional-changelog/conventional-changelog
2+
3+
const debug = require('debug')(
4+
`${require('./package.json').name}:conventional-changelog-config`
5+
);
6+
7+
const escapeRegExpStr = require('escape-string-regexp');
8+
const semver = require('semver');
9+
const sjx = require('shelljs');
10+
11+
// ? Commit types that trigger releases by default (using angular configuration)
12+
// ? See https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js
13+
const DEFAULT_RELEASED_TYPES = ['feat', 'fix', 'perf'];
14+
15+
// ? Same options as commit-analyzer's releaseRules (see
16+
// ? https://github.com/semantic-release/commit-analyzer#releaserules) with the
17+
// ? addition of the `title` property to set the resulting section title
18+
const ADDITIONAL_RELEASE_RULES = [
19+
{ type: 'build', release: 'patch', title: 'Build System' }
20+
];
21+
22+
const changelogTitle =
23+
`# Changelog\n\n` +
24+
`All notable changes to this project will be documented in this file.\n\n` +
25+
`The format is based on [Conventional Commits](https://conventionalcommits.org),\n` +
26+
`and this project adheres to [Semantic Versioning](https://semver.org).`;
27+
28+
// ? Strings in commit messages that, when found, are skipped
29+
// ! These also have to be updated in build-test-deploy.yml and cleanup.yml
30+
const SKIP_COMMANDS = '[skip ci], [ci skip], [skip cd], [cd skip]'.split(', ');
31+
32+
debug('SKIP_COMMANDS=', SKIP_COMMANDS);
33+
34+
sjx.config.silent = true;
35+
36+
// ! XXX: dark magic to synchronously deal with this async package
37+
const wait = sjx.exec(
38+
`node -e 'require("conventional-changelog-angular").then(o => console.log(o.writerOpts.transform.toString()));'`
39+
);
40+
41+
if (wait.code != 0) throw new Error('failed to acquire angular transformation');
42+
43+
const transform = Function(`"use strict";return (${wait.stdout})`)();
44+
const sentenceCase = (s) => s.toString().charAt(0).toUpperCase() + s.toString().slice(1);
45+
46+
const extraReleaseTriggerCommitTypes = ADDITIONAL_RELEASE_RULES.map((r) => r.type);
47+
const allReleaseTriggerCommitTypes = [
48+
DEFAULT_RELEASED_TYPES,
49+
extraReleaseTriggerCommitTypes
50+
].flat();
51+
52+
debug('extra types that trigger releases = %O', extraReleaseTriggerCommitTypes);
53+
debug('all types that trigger releases = %O', allReleaseTriggerCommitTypes);
54+
55+
// ? Releases made before this repo adopted semantic-release. They will be
56+
// ? collected together under a single header
57+
const legacyReleases = [];
58+
let shouldGenerate = true;
59+
60+
module.exports = {
61+
changelogTitle,
62+
additionalReleaseRules: ADDITIONAL_RELEASE_RULES.map(({ title, ...r }) => r),
63+
parserOpts: {
64+
mergePattern: /^Merge pull request #(\d+) from (.*)$/,
65+
mergeCorrespondence: ['id', 'source'],
66+
noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'],
67+
// eslint-disable-next-line no-console
68+
warn: console.warn.bind(console)
69+
},
70+
writerOpts: {
71+
generateOn: (commit) => {
72+
const decision =
73+
shouldGenerate === 'always' ||
74+
(shouldGenerate &&
75+
!!semver.valid(commit.version) &&
76+
!semver.prerelease(commit.version));
77+
debug(`::generateOn shouldGenerate=${shouldGenerate} decision=${decision}`);
78+
shouldGenerate = true;
79+
return decision;
80+
},
81+
transform: (commit, context) => {
82+
const version = commit.version || null;
83+
const firstRelease = version === context.gitSemverTags?.slice(-1)[0].slice(1);
84+
85+
debug('::transform encountered commit = %O', commit);
86+
debug(`::transform commit version = ${version}`);
87+
debug(`::transform commit firstRelease = ${firstRelease}`);
88+
89+
if (commit.revert) {
90+
debug('::transform coercing to type "revert"');
91+
commit.type = 'revert';
92+
} else if (commit.type == 'revert') {
93+
debug('::transform ignoring malformed revert commit');
94+
return null;
95+
}
96+
97+
commit.originalType = commit.type;
98+
99+
if (!firstRelease || commit.type) {
100+
// ? This commit does not have a type, but has a version. It must be a
101+
// ? legacy release!
102+
if (version && !commit.type) {
103+
debug('::transform determined commit is legacy release');
104+
legacyReleases.push(commit);
105+
commit = null;
106+
shouldGenerate = false;
107+
} else {
108+
let fakeFix = false;
109+
110+
if (extraReleaseTriggerCommitTypes.includes(commit.type)) {
111+
debug(`::transform encountered custom commit type: ${commit.type}`);
112+
commit.type = 'fix';
113+
fakeFix = true;
114+
}
115+
116+
commit = transform(commit, context);
117+
118+
debug('::transform angular transformed commit = %O', commit);
119+
120+
if (commit) {
121+
if (fakeFix) {
122+
commit.type = ADDITIONAL_RELEASE_RULES.find(
123+
(r) => r.type == commit.originalType
124+
)?.title;
125+
debug('::transform debug: %O', ADDITIONAL_RELEASE_RULES);
126+
debug(`::transform commit type set to custom title: ${commit.type}`);
127+
} else commit.type = sentenceCase(commit.type);
128+
129+
// ? Ignore any commits with skip commands in them
130+
if (SKIP_COMMANDS.some((cmd) => commit.subject?.includes(cmd))) {
131+
debug(`::transform saw skip command in commit message; commit skipped`);
132+
return null;
133+
}
134+
135+
if (commit.subject) {
136+
// ? Make scope-less commit subjects sentence case in the
137+
// ? changelog per my tastes
138+
if (!commit.scope) commit.subject = sentenceCase(commit.subject);
139+
140+
// ? Italicize reverts per my tastes
141+
if (commit.originalType == 'revert') commit.subject = `*${commit.subject}*`;
142+
}
143+
144+
// ? For breaking changes, make all scopes and subjects bold.
145+
// ? Scope-less subjects are made sentence case. All per my
146+
// ? tastes
147+
commit.notes.forEach((note) => {
148+
if (note.text) {
149+
debug('::transform saw BC notes for this commit');
150+
const [firstLine, ...remainder] = note.text.trim().split('\n');
151+
note.text =
152+
`**${!commit.scope ? sentenceCase(firstLine) : firstLine}**` +
153+
remainder.reduce((result, line) => `${result}\n${line}`, '');
154+
}
155+
});
156+
}
157+
}
158+
}
159+
160+
// ? If this is the commit representing the earliest release (and there
161+
// ? are legacy releases), use this commit to report collected legacy
162+
// ? releases
163+
else {
164+
debug('::transform generating summary legacy release commit');
165+
shouldGenerate = 'always';
166+
167+
const getShortHash = (h) => h.substring(0, 7);
168+
const shortHash = getShortHash(commit.hash);
169+
const url = context.repository
170+
? `${context.host}/${context.owner}/${context.repository}`
171+
: context.repoUrl;
172+
173+
const subject = legacyReleases
174+
.reverse()
175+
.map(({ hash, version }) => ({
176+
url: `[${getShortHash(hash)}](${url}/commit/${hash})`,
177+
version
178+
}))
179+
.reduce(
180+
(subject, { url, version }) => `Version ${version} (${url})\n\n- ${subject}`,
181+
`Version ${commit.version}`
182+
);
183+
184+
commit = {
185+
type: null,
186+
scope: null,
187+
subject,
188+
id: null,
189+
source: null,
190+
merge: null,
191+
header: null,
192+
body: null,
193+
footer: null,
194+
notes: [],
195+
references: [],
196+
mentions: [],
197+
revert: null,
198+
hash: commit.hash,
199+
shortHash,
200+
gitTags: null,
201+
committerDate: 'pre-CI/CD',
202+
version: 'Archived Releases'
203+
};
204+
}
205+
206+
debug('::transform final commit = %O', commit);
207+
return commit;
208+
}
209+
}
210+
};
211+
212+
debug('exports = %O', module.exports);

.codecov.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
coverage:
2+
range: '70...100'

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
insert_final_newline = true
6+
trim_trailing_whitespace = true
7+
charset = utf-8
8+
indent_style = space
9+
indent_size = 2

.env.example

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# When running in the CI pipeline, all required environment variables must be
2+
# defined as repository secrets!
3+
4+
# MongoDB connect URI
5+
#
6+
# Affects: externals/is-next-compat
7+
# Optional: yes
8+
# Default: empty
9+
#
10+
# Specify auth credentials if necessary
11+
# MUST SPECIFY A DATABASE AT THE END! e.g. mongodb://.../your-database-here
12+
MONGODB_URI=mongodb://127.0.0.1:27017/is-next-compat
13+
14+
# GitHub Personal Access Token (PAT)
15+
#
16+
# Affects: externals/is-next-compat
17+
# Optional: yes
18+
# Default: empty
19+
#
20+
# The token used to interact with the API with higher limits. If empty, GitHub
21+
# will rate limit you pretty quick. See https://github.com/settings/tokens
22+
GITHUB_PAT=0123456789abcdef

0 commit comments

Comments
 (0)