Skip to content

Commit

Permalink
feat: add support for ESM presets (#537)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Travi <programmer@travi.org>
  • Loading branch information
sheerlox and travi committed Nov 6, 2023
1 parent 96b93b7 commit 9dc87e0
Show file tree
Hide file tree
Showing 7 changed files with 52 additions and 31 deletions.
4 changes: 2 additions & 2 deletions index.js
Expand Up @@ -23,11 +23,11 @@ const debug = debugFactory("semantic-release:commit-analyzer");
* @param {Array<Object>} context.commits The commits to analyze.
* @param {String} context.cwd The current working directory.
*
* @returns {String|null} the type of release to create based on the list of commits or `null` if no release has to be done.
* @returns {Promise<String|null>} the type of release to create based on the list of commits or `null` if no release has to be done.
*/
export async function analyzeCommits(pluginConfig, context) {
const { commits, logger } = context;
const releaseRules = loadReleaseRules(pluginConfig, context);
const releaseRules = await loadReleaseRules(pluginConfig, context);
const config = await loadParserConfig(pluginConfig, context);
let releaseType = null;

Expand Down
11 changes: 6 additions & 5 deletions lib/load-parser-config.js
@@ -1,8 +1,6 @@
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import { isPlainObject } from "lodash-es";
import importFrom from "import-from";
import importFrom from "import-from-esm";
import conventionalChangelogAngular from "conventional-changelog-angular";

/**
Expand All @@ -14,6 +12,7 @@ import conventionalChangelogAngular from "conventional-changelog-angular";
* @param {Object} pluginConfig.parserOpts Additional `conventional-changelog-parser` options that will overwrite ones loaded by `preset` or `config`.
* @param {Object} context The semantic-release context.
* @param {String} context.cwd The current working directory.
*
* @return {Promise<Object>} a `Promise` that resolve to the `conventional-changelog-parser` options.
*/
export default async ({ preset, config, parserOpts, presetConfig }, { cwd }) => {
Expand All @@ -22,9 +21,11 @@ export default async ({ preset, config, parserOpts, presetConfig }, { cwd }) =>

if (preset) {
const presetPackage = `conventional-changelog-${preset.toLowerCase()}`;
loadedConfig = await (importFrom.silent(__dirname, presetPackage) || importFrom(cwd, presetPackage))(presetConfig);
loadedConfig = await (
(await importFrom.silent(__dirname, presetPackage)) || (await importFrom(cwd, presetPackage))
)(presetConfig);
} else if (config) {
loadedConfig = await (importFrom.silent(__dirname, config) || importFrom(cwd, config))();
loadedConfig = await ((await importFrom.silent(__dirname, config)) || (await importFrom(cwd, config)))();
} else {
loadedConfig = await conventionalChangelogAngular();
}
Expand Down
8 changes: 4 additions & 4 deletions lib/load-release-rules.js
@@ -1,7 +1,7 @@
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { isUndefined } from "lodash-es";
import importFrom from "import-from";
import importFrom from "import-from-esm";
import RELEASE_TYPES from "./default-release-types.js";

/**
Expand All @@ -15,16 +15,16 @@ import RELEASE_TYPES from "./default-release-types.js";
* @param {Object} context The semantic-release context.
* @param {String} context.cwd The current working directory.
*
* @return {Array} the loaded and validated `releaseRules`.
* @return {Promise<Array>} the loaded and validated `releaseRules`.
*/
export default ({ releaseRules }, { cwd }) => {
export default async ({ releaseRules }, { cwd }) => {
let loadedReleaseRules;
const __dirname = dirname(fileURLToPath(import.meta.url));

if (releaseRules) {
loadedReleaseRules =
typeof releaseRules === "string"
? importFrom.silent(__dirname, releaseRules) || importFrom(cwd, releaseRules)
? (await importFrom.silent(__dirname, releaseRules)) || (await importFrom(cwd, releaseRules))
: releaseRules;

if (!Array.isArray(loadedReleaseRules)) {
Expand Down
11 changes: 10 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -21,7 +21,7 @@
"conventional-commits-filter": "^4.0.0",
"conventional-commits-parser": "^5.0.0",
"debug": "^4.0.0",
"import-from": "^4.0.0",
"import-from-esm": "^1.0.3",
"lodash-es": "^4.17.21",
"micromatch": "^4.0.2"
},
Expand Down
11 changes: 11 additions & 0 deletions test/load-parser-config.test.js
@@ -1,4 +1,6 @@
import test from "ava";
import importFrom from "import-from-esm";
import sinon from "sinon";
import loadParserConfig from "../lib/load-parser-config.js";

const cwd = process.cwd();
Expand Down Expand Up @@ -100,3 +102,12 @@ test('Throw error if "config" doesn`t exist', async (t) => {
test('Throw error if "preset" doesn`t exist', async (t) => {
await t.throwsAsync(loadParserConfig({ preset: "unknown-preset" }, { cwd }), { code: "MODULE_NOT_FOUND" });
});

test.serial("Load preset and config correctly when importFrom.silent fails", async (t) => {
sinon.stub(importFrom, "silent").returns(undefined);

await loadPreset(t, "angular");
await loadConfig(t, "angular");

sinon.restore();
});
36 changes: 18 additions & 18 deletions test/load-release-rules.test.js
Expand Up @@ -4,26 +4,26 @@ import testReleaseRules from "./fixtures/release-rules.cjs";

const cwd = process.cwd();

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

t.deepEqual(releaseRules, testReleaseRules);
});

test('Accept a "releaseRules" option that reference a requireable module', (t) => {
const releaseRules = loadReleaseRules({ releaseRules: "./test/fixtures/release-rules.cjs" }, { cwd });
test('Accept a "releaseRules" option that reference a requireable module', async (t) => {
const releaseRules = await loadReleaseRules({ releaseRules: "./test/fixtures/release-rules.cjs" }, { cwd });

t.deepEqual(releaseRules, testReleaseRules);
});

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

t.is(releaseRules, undefined);
});

test('Preserve release rules set to "false" or "null"', (t) => {
const releaseRules = loadReleaseRules(
test('Preserve release rules set to "false" or "null"', async (t) => {
const releaseRules = await loadReleaseRules(
{
releaseRules: [
{ type: "feat", release: false },
Expand All @@ -39,32 +39,32 @@ test('Preserve release rules set to "false" or "null"', (t) => {
]);
});

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

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

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

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

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

0 comments on commit 9dc87e0

Please sign in to comment.