From 9dc87e0f499b793850491d719c554aac218814e9 Mon Sep 17 00:00:00 2001 From: Pierre Cavin Date: Mon, 6 Nov 2023 02:47:27 +0100 Subject: [PATCH] feat: add support for ESM presets (#537) Co-authored-by: Matt Travi --- index.js | 4 ++-- lib/load-parser-config.js | 11 +++++----- lib/load-release-rules.js | 8 ++++---- package-lock.json | 11 +++++++++- package.json | 2 +- test/load-parser-config.test.js | 11 ++++++++++ test/load-release-rules.test.js | 36 ++++++++++++++++----------------- 7 files changed, 52 insertions(+), 31 deletions(-) diff --git a/index.js b/index.js index 4ff2f2ec..98e36b22 100644 --- a/index.js +++ b/index.js @@ -23,11 +23,11 @@ const debug = debugFactory("semantic-release:commit-analyzer"); * @param {Array} 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} 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; diff --git a/lib/load-parser-config.js b/lib/load-parser-config.js index 44163652..fecbec0f 100644 --- a/lib/load-parser-config.js +++ b/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"; /** @@ -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} a `Promise` that resolve to the `conventional-changelog-parser` options. */ export default async ({ preset, config, parserOpts, presetConfig }, { cwd }) => { @@ -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(); } diff --git a/lib/load-release-rules.js b/lib/load-release-rules.js index 66d5dfbe..dcd3e601 100644 --- a/lib/load-release-rules.js +++ b/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"; /** @@ -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} 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)) { diff --git a/package-lock.json b/package-lock.json index 7a491685..6299196c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,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" }, @@ -2268,6 +2268,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, "engines": { "node": ">=12.2" }, @@ -2275,6 +2276,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-from-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.0.3.tgz", + "integrity": "sha512-bncoEU7L4Vi5OoeygZuBUP9IC1AxavLR4UMCwZ9FtujOhDG1PDEo7IpCdfeOxKfrMCGwoK6UXpF9q/cXTA/ejg==", + "engines": { + "node": ">=16.20" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", diff --git a/package.json b/package.json index 1b7b7d83..619a9ba8 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/test/load-parser-config.test.js b/test/load-parser-config.test.js index 6c3de649..46da4248 100644 --- a/test/load-parser-config.test.js +++ b/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(); @@ -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(); +}); diff --git a/test/load-release-rules.test.js b/test/load-release-rules.test.js index 65ba3d09..dc15df84 100644 --- a/test/load-release-rules.test.js +++ b/test/load-release-rules.test.js @@ -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 }, @@ -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/, }); });