Skip to content

Commit

Permalink
feat: upgrade to semantic-release-based pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
Xunnamius committed Jan 29, 2021
1 parent 408e116 commit 3074894
Show file tree
Hide file tree
Showing 52 changed files with 22,035 additions and 20 deletions.
212 changes: 212 additions & 0 deletions .changelogrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// ? See https://github.com/conventional-changelog/conventional-changelog

const debug = require('debug')(
`${require('./package.json').name}:conventional-changelog-config`
);

const escapeRegExpStr = require('escape-string-regexp');
const semver = require('semver');
const sjx = require('shelljs');

// ? Commit types that trigger releases by default (using angular configuration)
// ? See https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js
const DEFAULT_RELEASED_TYPES = ['feat', 'fix', 'perf'];

// ? Same options as commit-analyzer's releaseRules (see
// ? https://github.com/semantic-release/commit-analyzer#releaserules) with the
// ? addition of the `title` property to set the resulting section title
const ADDITIONAL_RELEASE_RULES = [
{ type: 'build', release: 'patch', title: 'Build System' }
];

const changelogTitle =
`# Changelog\n\n` +
`All notable changes to this project will be documented in this file.\n\n` +
`The format is based on [Conventional Commits](https://conventionalcommits.org),\n` +
`and this project adheres to [Semantic Versioning](https://semver.org).`;

// ? Strings in commit messages that, when found, are skipped
// ! These also have to be updated in build-test-deploy.yml and cleanup.yml
const SKIP_COMMANDS = '[skip ci], [ci skip], [skip cd], [cd skip]'.split(', ');

debug('SKIP_COMMANDS:', SKIP_COMMANDS);

sjx.config.silent = !process.env.DEBUG;

// ! XXX: dark magic to synchronously deal with this async package
const wait = sjx.exec(
`node -e 'require("conventional-changelog-angular").then(o => console.log(o.writerOpts.transform.toString()));'`
);

if (wait.code != 0) throw new Error('failed to acquire angular transformation');

const transform = Function(`"use strict";return (${wait.stdout})`)();
const sentenceCase = (s) => s.toString().charAt(0).toUpperCase() + s.toString().slice(1);

const extraReleaseTriggerCommitTypes = ADDITIONAL_RELEASE_RULES.map((r) => r.type);
const allReleaseTriggerCommitTypes = [
DEFAULT_RELEASED_TYPES,
extraReleaseTriggerCommitTypes
].flat();

debug('extra types that trigger releases: %O', extraReleaseTriggerCommitTypes);
debug('all types that trigger releases: %O', allReleaseTriggerCommitTypes);

// ? Releases made before this repo adopted semantic-release. They will be
// ? collected together under a single header
const legacyReleases = [];
let shouldGenerate = true;

module.exports = {
changelogTitle,
additionalReleaseRules: ADDITIONAL_RELEASE_RULES.map(({ title, ...r }) => r),
parserOpts: {
mergePattern: /^Merge pull request #(\d+) from (.*)$/,
mergeCorrespondence: ['id', 'source'],
noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES', 'BREAKING'],
// eslint-disable-next-line no-console
warn: console.warn.bind(console)
},
writerOpts: {
generateOn: (commit) => {
const decision =
shouldGenerate === 'always' ||
(shouldGenerate &&
!!semver.valid(commit.version) &&
!semver.prerelease(commit.version));
debug(`::generateOn shouldGenerate: ${shouldGenerate} [decision: ${decision}]`);
shouldGenerate = true;
return decision;
},
transform: (commit, context) => {
const version = commit.version || null;
const firstRelease = version === context.gitSemverTags?.slice(-1)[0]?.slice(1);

debug('::transform encountered commit: %O', commit);
debug(`::transform commit version: ${version}`);
debug(`::transform commit firstRelease: ${firstRelease}`);

if (commit.revert) {
debug('::transform coercing to type "revert"');
commit.type = 'revert';
} else if (commit.type == 'revert') {
debug('::transform ignoring malformed revert commit');
return null;
}

commit.originalType = commit.type;

if (!firstRelease || commit.type) {
// ? This commit does not have a type, but has a version. It must be a
// ? legacy release!
if (version && !commit.type) {
debug('::transform determined commit is legacy release');
legacyReleases.push(commit);
commit = null;
shouldGenerate = false;
} else {
let fakeFix = false;

if (extraReleaseTriggerCommitTypes.includes(commit.type)) {
debug(`::transform encountered custom commit type: ${commit.type}`);
commit.type = 'fix';
fakeFix = true;
}

commit = transform(commit, context);

debug('::transform angular transformed commit: %O', commit);

if (commit) {
if (fakeFix) {
commit.type = ADDITIONAL_RELEASE_RULES.find(
(r) => r.type == commit.originalType
)?.title;
debug('::transform debug: %O', ADDITIONAL_RELEASE_RULES);
debug(`::transform commit type set to custom title: ${commit.type}`);
} else commit.type = sentenceCase(commit.type);

// ? Ignore any commits with skip commands in them
if (SKIP_COMMANDS.some((cmd) => commit.subject?.includes(cmd))) {
debug(`::transform saw skip command in commit message; commit skipped`);
return null;
}

if (commit.subject) {
// ? Make scope-less commit subjects sentence case in the
// ? changelog per my tastes
if (!commit.scope) commit.subject = sentenceCase(commit.subject);

// ? Italicize reverts per my tastes
if (commit.originalType == 'revert') commit.subject = `*${commit.subject}*`;
}

// ? For breaking changes, make all scopes and subjects bold.
// ? Scope-less subjects are made sentence case. All per my
// ? tastes
commit.notes.forEach((note) => {
if (note.text) {
debug('::transform saw BC notes for this commit');
const [firstLine, ...remainder] = note.text.trim().split('\n');
note.text =
`**${!commit.scope ? sentenceCase(firstLine) : firstLine}**` +
remainder.reduce((result, line) => `${result}\n${line}`, '');
}
});
}
}
}

// ? If this is the commit representing the earliest release (and there
// ? are legacy releases), use this commit to report collected legacy
// ? releases
else {
debug('::transform generating summary legacy release commit');
shouldGenerate = 'always';

const getShortHash = (h) => h.substring(0, 7);
const shortHash = getShortHash(commit.hash);
const url = context.repository
? `${context.host}/${context.owner}/${context.repository}`
: context.repoUrl;

const subject = legacyReleases
.reverse()
.map(({ hash, version }) => ({
url: `[${getShortHash(hash)}](${url}/commit/${hash})`,
version
}))
.reduce(
(subject, { url, version }) => `Version ${version} (${url})\n\n- ${subject}`,
`Version ${commit.version}`
);

commit = {
type: null,
scope: null,
subject,
id: null,
source: null,
merge: null,
header: null,
body: null,
footer: null,
notes: [],
references: [],
mentions: [],
revert: null,
hash: commit.hash,
shortHash,
gitTags: null,
committerDate: 'pre-CI/CD',
version: 'Archived Releases'
};
}

debug('::transform final commit: %O', commit);
return commit;
}
}
};

debug('exports: %O', module.exports);
2 changes: 2 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
coverage:
range: '70...100'
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
103 changes: 103 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
const debug = require('debug')(`${require('./package.json').name}:eslint-config`);

module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['jest', '@typescript-eslint', 'import'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript'
],
parserOptions: {
ecmaVersion: 8,
sourceType: 'module',
ecmaFeatures: {
impliedStrict: true,
experimentalObjectRestSpread: true
},
extraFileExtensions: ['.mjs', '.cjs'],
project: 'tsconfig.eslint.json'
},
env: {
es6: true,
node: true,
jest: true,
'jest/globals': true,
browser: false,
webextensions: false
},
rules: {
'no-console': 'warn',
'no-return-await': 'warn',
'no-await-in-loop': 'warn',
'import/no-unresolved': ['error', { commonjs: true }],
'no-extra-boolean-cast': 'off',
'no-empty': 'off',
'@typescript-eslint/camelcase': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/prefer-ts-expect-error': 'warn',
'@typescript-eslint/no-floating-promises': [
'error',
{ ignoreVoid: true, ignoreIIFE: true }
],
'@typescript-eslint/ban-ts-comment': [
'warn',
{
'ts-expect-error': 'allow-with-description',
minimumDescriptionLength: 6
}
],
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_+',
varsIgnorePattern: '^_+',
caughtErrorsIgnorePattern: '^ignored?\\d*$',
caughtErrors: 'all'
}
],
// ? Ever since v4, we will rely on TypeScript to catch these
'no-undef': 'off',
'@typescript-eslint/no-var-requires': 'off',
'no-unused-vars': 'off'
},
overrides: [
{
files: ['*.test.*'],
extends: ['plugin:jest/all', 'plugin:jest/style'],
rules: {
'jest/lowercase': 'off',
'jest/consistent-test-it': 'off',
'jest/require-top-level-describe': 'off',
'jest/valid-describe': 'off',
'jest/no-hooks': 'off',
'jest/require-to-throw-message': 'off',
'jest/prefer-called-with': 'off',
'jest/prefer-spy-on': 'off',
'jest/no-if': 'off',
'jest/no-disabled-tests': 'warn',
'jest/no-commented-out-tests': 'warn',
'jest/no-alias-methods': 'off'
}
}
],
settings: {
'import/extensions': ['.ts', '.js'],
// ? Switch parsers depending on which type of file we're looking at
'import/parsers': {
'@typescript-eslint/parser': ['.ts'],
'babel-eslint': ['.js']
},
'import/ignore': [
// ? Don't go complaining about anything that we don't own
'.*/node_modules/.*'
]
},
ignorePatterns: ['coverage', 'dist', 'bin']
};

debug('exports: %O', module.exports);
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# See https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners

* @Xunnamius
Loading

0 comments on commit 3074894

Please sign in to comment.