Skip to content

Commit a773c8b

Browse files
jpedrohgr2m
andauthored
feat: esm (#247)
BREAKING CHANGE: `@semantic-release/commit-analyzer` is now a native ES Module BREAKING CHANGE: When setting the `releaseRules` option to a path, the path must include the `.js` extension, and the file must be an ES Module itself, exporting an array as default Before: ```json { "plugins": [ ["@semantic-release/commit-analyzer", { "preset": "angular", "releaseRules": "./config/release-rules" }], "@semantic-release/release-notes-generator" ] } ``` ```js // File: config/release-rules.js module.exports = [ {type: 'docs', scope: 'README', release: 'patch'}, {type: 'refactor', scope: 'core-*', release: 'minor'}, {type: 'refactor', release: 'patch'}, ]; ``` After: ```json { "plugins": [ ["@semantic-release/commit-analyzer", { "preset": "angular", "releaseRules": "./config/release-rules.js" }], "@semantic-release/release-notes-generator" ] } ``` ```js // File: config/release-rules.js export default [ {type: 'docs', scope: 'README', release: 'patch'}, {type: 'refactor', scope: 'core-*', release: 'minor'}, {type: 'refactor', release: 'patch'}, ]; ``` Co-authored-by: Gregor Martynus <[email protected]>
1 parent e7e56f6 commit a773c8b

17 files changed

+106
-78
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ With this configuration:
162162

163163
##### External package / file
164164

165-
`releaseRules` can also reference a module, either by it's `npm` name or path:
165+
`releaseRules` can also reference a module, either by it's `npm` name or path. Note that the path must include the `.js` extension.
166+
166167
```json
167168
{
168169
"plugins": [
@@ -174,9 +175,12 @@ With this configuration:
174175
]
175176
}
176177
```
178+
179+
The file must be an ES Module exporting an array as default
180+
177181
```js
178182
// File: config/release-rules.js
179-
module.exports = [
183+
export default [
180184
{type: 'docs', scope: 'README', release: 'patch'},
181185
{type: 'refactor', scope: 'core-*', release: 'minor'},
182186
{type: 'refactor', release: 'patch'},

index.js

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
const {isUndefined} = require('lodash');
2-
const parser = require('conventional-commits-parser').sync;
3-
const filter = require('conventional-commits-filter');
4-
const debug = require('debug')('semantic-release:commit-analyzer');
5-
const loadParserConfig = require('./lib/load-parser-config');
6-
const loadReleaseRules = require('./lib/load-release-rules');
7-
const analyzeCommit = require('./lib/analyze-commit');
8-
const compareReleaseTypes = require('./lib/compare-release-types');
9-
const RELEASE_TYPES = require('./lib/default-release-types');
10-
const DEFAULT_RELEASE_RULES = require('./lib/default-release-rules');
1+
import lodash from 'lodash';
2+
const {isUndefined} = lodash;
3+
import {sync as parser} from 'conventional-commits-parser';
4+
import filter from 'conventional-commits-filter';
5+
import debug from 'debug';
6+
import loadParserConfig from './lib/load-parser-config.js';
7+
import loadReleaseRules from './lib/load-release-rules.js';
8+
import analyzeCommit from './lib/analyze-commit.js';
9+
import compareReleaseTypes from './lib/compare-release-types.js';
10+
import RELEASE_TYPES from './lib/default-release-types.js';
11+
import DEFAULT_RELEASE_RULES from './lib/default-release-rules.js';
12+
13+
debug('semantic-release:commit-analyzer');
1114

1215
/**
1316
* Determine the type of release to create based on a list of commits.
@@ -25,7 +28,7 @@ const DEFAULT_RELEASE_RULES = require('./lib/default-release-rules');
2528
*/
2629
async function analyzeCommits(pluginConfig, context) {
2730
const {commits, logger} = context;
28-
const releaseRules = loadReleaseRules(pluginConfig, context);
31+
const releaseRules = await loadReleaseRules(pluginConfig, context);
2932
const config = await loadParserConfig(pluginConfig, context);
3033
let releaseType = null;
3134

@@ -79,4 +82,4 @@ async function analyzeCommits(pluginConfig, context) {
7982
return releaseType;
8083
}
8184

82-
module.exports = {analyzeCommits};
85+
export {analyzeCommits};

lib/analyze-commit.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
const {isMatchWith, isString} = require('lodash');
2-
const micromatch = require('micromatch');
3-
const debug = require('debug')('semantic-release:commit-analyzer');
4-
const RELEASE_TYPES = require('./default-release-types');
5-
const compareReleaseTypes = require('./compare-release-types');
1+
import lodash from 'lodash';
2+
import micromatch from 'micromatch';
3+
import debug from 'debug';
4+
import RELEASE_TYPES from './default-release-types.js';
5+
import compareReleaseTypes from './compare-release-types.js';
6+
const {isMatchWith, isString} = lodash;
7+
const {isMatch} = micromatch;
8+
debug('semantic-release:commit-analyzer');
69

710
/**
811
* Find all the rules matching and return the highest release type of the matching rules.
@@ -11,7 +14,7 @@ const compareReleaseTypes = require('./compare-release-types');
1114
* @param {Commit} commit a parsed commit.
1215
* @return {string} the highest release type of the matching rules or `undefined` if no rule match the commit.
1316
*/
14-
module.exports = (releaseRules, commit) => {
17+
export default (releaseRules, commit) => {
1518
let releaseType;
1619

1720
releaseRules
@@ -23,7 +26,7 @@ module.exports = (releaseRules, commit) => {
2326
(!revert || commit.revert) &&
2427
// Otherwise match the regular rules
2528
isMatchWith(commit, rule, (object, src) =>
26-
isString(src) && isString(object) ? micromatch.isMatch(object, src) : undefined
29+
isString(src) && isString(object) ? isMatch(object, src) : undefined
2730
)
2831
)
2932
.every(match => {

lib/compare-release-types.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const RELEASE_TYPES = require('./default-release-types');
1+
import RELEASE_TYPES from './default-release-types.js';
22

33
/**
44
* Test if a realease type is of higher level than a given one.
@@ -7,5 +7,5 @@ const RELEASE_TYPES = require('./default-release-types');
77
* @param {string} releaseType the release type to compare with.
88
* @return {Boolean} true if `releaseType` is higher than `currentReleaseType`.
99
*/
10-
module.exports = (currentReleaseType, releaseType) =>
10+
export default (currentReleaseType, releaseType) =>
1111
!currentReleaseType || RELEASE_TYPES.indexOf(releaseType) < RELEASE_TYPES.indexOf(currentReleaseType);

lib/default-release-rules.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* @type {Array}
55
*/
6-
module.exports = [
6+
export default [
77
{breaking: true, release: 'major'},
88
{revert: true, release: 'patch'},
99
// Angular

lib/default-release-types.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
*
44
* @type {Array}
55
*/
6-
module.exports = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];
6+
export default ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'];

lib/esm-import.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const esmImport = async name => {
2+
try {
3+
return (await import(name)).default;
4+
} catch (error) {
5+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
6+
error.code = 'MODULE_NOT_FOUND';
7+
}
8+
9+
throw error;
10+
}
11+
};

lib/load-parser-config.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const {promisify} = require('util');
2-
const {isPlainObject} = require('lodash');
3-
const importFrom = require('import-from');
4-
const conventionalChangelogAngular = require('conventional-changelog-angular');
1+
import {promisify} from 'util';
2+
import lodash from 'lodash';
3+
const {isPlainObject} = lodash;
4+
import {esmImport} from './esm-import.js';
55

66
/**
77
* Load `conventional-changelog-parser` options. Handle presets that return either a `Promise<Array>` or a `Promise<Function>`.
@@ -14,16 +14,16 @@ const conventionalChangelogAngular = require('conventional-changelog-angular');
1414
* @param {String} context.cwd The current working directory.
1515
* @return {Promise<Object>} a `Promise` that resolve to the `conventional-changelog-parser` options.
1616
*/
17-
module.exports = async ({preset, config, parserOpts, presetConfig}, {cwd}) => {
17+
export default async ({preset, config, parserOpts, presetConfig}, {_}) => {
1818
let loadedConfig;
1919

2020
if (preset) {
2121
const presetPackage = `conventional-changelog-${preset.toLowerCase()}`;
22-
loadedConfig = importFrom.silent(__dirname, presetPackage) || importFrom(cwd, presetPackage);
22+
loadedConfig = await esmImport(presetPackage);
2323
} else if (config) {
24-
loadedConfig = importFrom.silent(__dirname, config) || importFrom(cwd, config);
24+
loadedConfig = await esmImport(config);
2525
} else {
26-
loadedConfig = conventionalChangelogAngular;
26+
loadedConfig = await esmImport('conventional-changelog-angular');
2727
}
2828

2929
loadedConfig = await (typeof loadedConfig === 'function'

lib/load-release-rules.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
const {isUndefined} = require('lodash');
2-
const importFrom = require('import-from');
3-
const RELEASE_TYPES = require('./default-release-types');
1+
import lodash from 'lodash';
2+
const {isUndefined} = lodash;
3+
import {esmImport} from './esm-import.js';
4+
import RELEASE_TYPES from './default-release-types.js';
5+
import {resolve} from 'path';
6+
import {pathToFileURL} from 'url';
47

58
/**
69
* Load and validate the `releaseRules` rules.
@@ -15,14 +18,12 @@ const RELEASE_TYPES = require('./default-release-types');
1518
*
1619
* @return {Array} the loaded and validated `releaseRules`.
1720
*/
18-
module.exports = ({releaseRules}, {cwd}) => {
21+
export default async ({releaseRules}, {cwd}) => {
1922
let loadedReleaseRules;
2023

2124
if (releaseRules) {
2225
loadedReleaseRules =
23-
typeof releaseRules === 'string'
24-
? importFrom.silent(__dirname, releaseRules) || importFrom(cwd, releaseRules)
25-
: releaseRules;
26+
typeof releaseRules === 'string' ? await esmImport(pathToFileURL(resolve(cwd, releaseRules)).href) : releaseRules;
2627

2728
if (!Array.isArray(loadedReleaseRules)) {
2829
throw new TypeError('Error in commit-analyzer configuration: "releaseRules" must be an array of rules');

package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"name": "@semantic-release/commit-analyzer",
3+
"type": "module",
34
"description": "semantic-release plugin to analyze commits with conventional-changelog",
45
"version": "0.0.0-development",
56
"author": "Pierre Vanduynslager (https://twitter.com/@pvdlg_)",
@@ -56,7 +57,7 @@
5657
"semantic-release"
5758
],
5859
"license": "MIT",
59-
"main": "index.js",
60+
"exports": "./index.js",
6061
"nyc": {
6162
"include": [
6263
"lib/**/*.js",
@@ -94,7 +95,11 @@
9495
"prettier": true,
9596
"space": true,
9697
"rules": {
97-
"unicorn/string-content": "off"
98+
"unicorn/string-content": "off",
99+
"unicorn/import-index": "off",
100+
"import/extensions": "off",
101+
"import/no-useless-path-segments": "off",
102+
"node/no-unsupported-features/es-syntax": "off"
98103
}
99104
},
100105
"renovate": {

test/analyze-commit.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
const test = require('ava');
2-
const analyzeCommit = require('../lib/analyze-commit');
1+
import test from 'ava';
2+
import analyzeCommit from '../lib/analyze-commit.js';
33

44
test('Match breaking change', t => {
55
const commit = {

test/compare-release-types.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
const test = require('ava');
2-
const compareReleaseTypes = require('../lib/compare-release-types');
1+
import test from 'ava';
2+
import compareReleaseTypes from '../lib/compare-release-types.js';
33

44
test('Compares release types', t => {
55
t.true(compareReleaseTypes('patch', 'minor'));
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
module.exports = 42;
1+
export default 42;

test/fixtures/release-rules.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
module.exports = [
1+
export default [
22
{breaking: true, release: 'major'},
33
{type: 'feat', release: 'minor'},
44
{type: 'fix', release: 'patch'},

test/integration.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
const test = require('ava');
2-
const {stub} = require('sinon');
3-
const {analyzeCommits} = require('..');
1+
import test from 'ava';
2+
import sinon from 'sinon';
3+
import {analyzeCommits} from '../index.js';
44

55
const cwd = process.cwd();
66

77
test.beforeEach(t => {
8-
const log = stub();
8+
const log = sinon.stub();
99
t.context.log = log;
1010
t.context.logger = {log};
1111
});
@@ -117,7 +117,7 @@ test('Accept a "releaseRules" option that reference a requierable module', async
117117
{hash: '456', message: 'feat(scope2): Second feature'},
118118
];
119119
const releaseType = await analyzeCommits(
120-
{releaseRules: './test/fixtures/release-rules'},
120+
{releaseRules: './test/fixtures/release-rules.js'},
121121
{cwd, commits, logger: t.context.logger}
122122
);
123123

@@ -357,7 +357,7 @@ test('Throw error if "releaseRules" is not an Array or a String', async t => {
357357
});
358358

359359
test('Throw error if "releaseRules" option reference a requierable module that is not an Array or a String', async t => {
360-
await t.throwsAsync(analyzeCommits({releaseRules: './test/fixtures/release-rules-invalid'}, {cwd}), {
360+
await t.throwsAsync(analyzeCommits({releaseRules: './test/fixtures/release-rules-invalid.js'}, {cwd}), {
361361
message: /Error in commit-analyzer configuration: "releaseRules" must be an array of rules/,
362362
});
363363
});

test/load-parser-config.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
const test = require('ava');
2-
const loadParserConfig = require('../lib/load-parser-config');
1+
import test from 'ava';
2+
import loadParserConfig from '../lib/load-parser-config.js';
3+
import conventionalChangelogAngular from 'conventional-changelog-angular';
34

45
const cwd = process.cwd();
56

@@ -34,7 +35,7 @@ async function loadConfig(t, config, pluginOptions) {
3435
loadConfig.title = (providedTitle, config) => `${providedTitle} Load "${config}" config`.trim();
3536

3637
test('Load "conventional-changelog-angular" by default', async t => {
37-
t.deepEqual(await loadParserConfig({}, {cwd}), (await require('conventional-changelog-angular')).parserOpts);
38+
t.deepEqual(await loadParserConfig({}, {cwd}), (await conventionalChangelogAngular).parserOpts);
3839
});
3940

4041
test('Accept a "parserOpts" object as option', async t => {

test/load-release-rules.test.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
1-
const test = require('ava');
2-
const loadReleaseRules = require('../lib/load-release-rules');
3-
const testReleaseRules = require('./fixtures/release-rules');
1+
import test from 'ava';
2+
import loadReleaseRules from '../lib/load-release-rules.js';
3+
import testReleaseRules from './fixtures/release-rules.js';
44

55
const cwd = process.cwd();
66

7-
test('Accept a "releaseRules" option', t => {
8-
const releaseRules = loadReleaseRules({releaseRules: testReleaseRules}, {cwd});
7+
test('Accept a "releaseRules" option', async t => {
8+
const releaseRules = await loadReleaseRules({releaseRules: testReleaseRules}, {cwd});
99

1010
t.deepEqual(releaseRules, testReleaseRules);
1111
});
1212

13-
test('Accept a "releaseRules" option that reference a requierable module', t => {
14-
const releaseRules = loadReleaseRules({releaseRules: './test/fixtures/release-rules'}, {cwd});
13+
test('Accept a "releaseRules" option that reference a requierable module', async t => {
14+
const releaseRules = await loadReleaseRules({releaseRules: './test/fixtures/release-rules.js'}, {cwd});
1515

1616
t.deepEqual(releaseRules, testReleaseRules);
1717
});
1818

19-
test('Return undefined if "releaseRules" not set', t => {
20-
const releaseRules = loadReleaseRules({}, {cwd});
19+
test('Return undefined if "releaseRules" not set', async t => {
20+
const releaseRules = await loadReleaseRules({}, {cwd});
2121

2222
t.is(releaseRules, undefined);
2323
});
2424

25-
test('Preserve release rules set to "false" or "null"', t => {
26-
const releaseRules = loadReleaseRules(
25+
test('Preserve release rules set to "false" or "null"', async t => {
26+
const releaseRules = await loadReleaseRules(
2727
{
2828
releaseRules: [
2929
{type: 'feat', release: false},
@@ -39,32 +39,32 @@ test('Preserve release rules set to "false" or "null"', t => {
3939
]);
4040
});
4141

42-
test('Throw error if "releaseRules" reference invalid commit type', t => {
43-
t.throws(() => loadReleaseRules({releaseRules: [{tag: 'Update', release: 'invalid'}]}, {cwd}), {
42+
test('Throw error if "releaseRules" reference invalid commit type', async t => {
43+
await t.throwsAsync(() => loadReleaseRules({releaseRules: [{tag: 'Update', release: 'invalid'}]}, {cwd}), {
4444
message: /Error in commit-analyzer configuration: "invalid" is not a valid release type\. Valid values are:\[?.*]/,
4545
});
4646
});
4747

48-
test('Throw error if a rule in "releaseRules" does not have a release type', t => {
49-
t.throws(() => loadReleaseRules({releaseRules: [{tag: 'Update'}]}, {cwd}), {
48+
test('Throw error if a rule in "releaseRules" does not have a release type', async t => {
49+
await t.throwsAsync(() => loadReleaseRules({releaseRules: [{tag: 'Update'}]}, {cwd}), {
5050
message: /Error in commit-analyzer configuration: rules must be an object with a "release" property/,
5151
});
5252
});
5353

54-
test('Throw error if "releaseRules" is not an Array or a String', t => {
55-
t.throws(() => loadReleaseRules({releaseRules: {}}, {cwd}), {
54+
test('Throw error if "releaseRules" is not an Array or a String', async t => {
55+
await t.throwsAsync(() => loadReleaseRules({releaseRules: {}}, {cwd}), {
5656
message: /Error in commit-analyzer configuration: "releaseRules" must be an array of rules/,
5757
});
5858
});
5959

60-
test('Throw error if "releaseRules" option reference a requierable module that is not an Array or a String', t => {
61-
t.throws(() => loadReleaseRules({releaseRules: './test/fixtures/release-rules-invalid'}, {cwd}), {
60+
test('Throw error if "releaseRules" option reference a requierable module that is not an Array or a String', async t => {
61+
await t.throwsAsync(() => loadReleaseRules({releaseRules: './test/fixtures/release-rules-invalid.js'}, {cwd}), {
6262
message: /Error in commit-analyzer configuration: "releaseRules" must be an array of rules/,
6363
});
6464
});
6565

66-
test('Throw error if "releaseRules" contains an undefined rule', t => {
67-
t.throws(() => loadReleaseRules({releaseRules: [{type: 'feat', release: 'minor'}, undefined]}, {cwd}), {
66+
test('Throw error if "releaseRules" contains an undefined rule', async t => {
67+
await t.throwsAsync(() => loadReleaseRules({releaseRules: [{type: 'feat', release: 'minor'}, undefined]}, {cwd}), {
6868
message: /Error in commit-analyzer configuration: rules must be an object with a "release" property/,
6969
});
7070
});

0 commit comments

Comments
 (0)