Skip to content

Commit

Permalink
feat: add support for ESM presets
Browse files Browse the repository at this point in the history
  • Loading branch information
sheerlox committed Oct 3, 2023
1 parent 7055003 commit 94c9fb1
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 40 deletions.
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
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
12 changes: 4 additions & 8 deletions lib/load-parser-config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
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 conventionalChangelogAngular from "conventional-changelog-angular";
import { importModule } from "./module-loader.js";

/**
* Load `conventional-changelog-parser` options. Handle presets that return either a `Promise<Array>` or a `Promise<Function>`.
Expand All @@ -14,17 +10,17 @@ 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 }) => {
let loadedConfig;
const __dirname = dirname(fileURLToPath(import.meta.url));

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

/**
* Load and validate the `releaseRules` rules.
Expand All @@ -15,17 +13,13 @@ 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)
: releaseRules;
loadedReleaseRules = typeof releaseRules === "string" ? await importModule(cwd, releaseRules) : releaseRules;

if (!Array.isArray(loadedReleaseRules)) {
throw new TypeError('Error in commit-analyzer configuration: "releaseRules" must be an array of rules');
Expand Down
24 changes: 24 additions & 0 deletions lib/module-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { join } from "node:path";

/**
* Import a module from node_modules or current working directory.
*
* @param {string} cwd the current working directory.
* @param {string} moduleName npm package name or path relative to cwd.
*
* @return {Promise<any>} the loaded module's default export.
*/
export const importModule = async (cwd, moduleName) => {
const localModulePath = join(cwd, moduleName);
try {
return (await import(moduleName)).default;
} catch (e) {
try {
return (await import(localModulePath)).default;
} catch (e) {
const error = new Error(`Cannot find module "${moduleName}" or "${localModulePath}".`);
error.code = "MODULE_NOT_FOUND";
throw error;
}
}
};
2 changes: 1 addition & 1 deletion package-lock.json

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

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"conventional-commits-filter": "^4.0.0",
"conventional-commits-parser": "^5.0.0",
"debug": "^4.0.0",
"import-from": "^4.0.0",
"lodash-es": "^4.17.21",
"micromatch": "^4.0.2"
},
Expand Down
36 changes: 18 additions & 18 deletions test/load-release-rules.test.js
Original file line number Diff line number Diff line change
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 94c9fb1

Please sign in to comment.