From a1474b06673def7bbc0b743cd10517f9a0885cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aki=20M=C3=A4kinen?= Date: Mon, 27 Sep 2021 23:17:42 +0300 Subject: [PATCH] feat(get-config): add support for defining plugins as an object This implementation allows the plugins to be defined as an object instead of an array. The benefit is readability and usability, especially when using yaml format, where empty objects can be left out. In Javascript, the benefit is more limited since it is necessary to put in the empty objects, but even then the configuration should be more intuitive. This is implemented in backwards compatible way, where the original format is still used internally, so nothing should break. --- lib/get-config.js | 7 +-- lib/normalize-config.js | 23 +++++++++ test/normalize-config.test.js | 88 +++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 lib/normalize-config.js create mode 100644 test/normalize-config.test.js diff --git a/lib/get-config.js b/lib/get-config.js index 1f16962ac9..e94d2c470b 100644 --- a/lib/get-config.js +++ b/lib/get-config.js @@ -7,7 +7,7 @@ const {repoUrl} = require('./git'); const PLUGINS_DEFINITIONS = require('./definitions/plugins'); const plugins = require('./plugins'); const {validatePlugin, parseConfig} = require('./plugins/utils'); - +const normalizeConfig = require('./normalize-config'); const CONFIG_NAME = 'release'; module.exports = async (context, cliOptions) => { @@ -15,9 +15,10 @@ module.exports = async (context, cliOptions) => { const {config, filepath} = (await cosmiconfig(CONFIG_NAME).search(cwd)) || {}; debug('load config from: %s', filepath); - + const normalizedConfig = normalizeConfig(config); // Merge config file options and CLI/API options - let options = {...config, ...cliOptions}; + + let options = {...normalizedConfig, ...cliOptions}; const pluginsPath = {}; let extendPaths; diff --git a/lib/normalize-config.js b/lib/normalize-config.js new file mode 100644 index 0000000000..fd488889e2 --- /dev/null +++ b/lib/normalize-config.js @@ -0,0 +1,23 @@ +// The idea is to still use internally the old format, but to allow the plugins to be object in the configuration. +// This buys some time to figure out what is the format to be used internally, if the old is optimal, or if some +// refactoring would improve the semantic-release somehow (readability, maintainability etc.) +module.exports = (config) => { + if (!config.plugins) return config; // No idea if this is possible, but if it is, then handle the situation the old way + + if (Array.isArray(config.plugins)) { + return config; // The configuration is already in the internally used format + } + + if (config.plugins.constructor === Object) { + const normalizedPlugins = Object.keys(config.plugins).map((key) => { + if (Object.keys(config.plugins[key]).length > 0) { + return [key, config.plugins[key]]; + } + + return key; + }); + return {...config, plugins: normalizedPlugins}; + } + + return config; // Again, if the config object is something unpredictable, just return it and handle it the old way. +}; diff --git a/test/normalize-config.test.js b/test/normalize-config.test.js new file mode 100644 index 0000000000..85fb4a79be --- /dev/null +++ b/test/normalize-config.test.js @@ -0,0 +1,88 @@ +const test = require('ava'); +const {gitRepo} = require('./helpers/git-utils'); +const {outputJson, writeFile} = require('fs-extra'); +const path = require('path'); +const yaml = require('js-yaml'); +const {stub} = require('sinon'); +const proxyquire = require('proxyquire'); + +test.beforeEach((t) => { + t.context.plugins = stub().returns({}); + t.context.getConfig = proxyquire('../lib/get-config', {'./plugins': t.context.plugins}); +}); + +test('Read options from package.json when plugins are an object', async (t) => { + // Create a git repository, set the current working directory at the root of the repo + const {cwd} = await gitRepo(); + const options = { + analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'}, + generateNotes: 'generateNotes', + branches: ['test_branch'], + repositoryUrl: 'https://host.null/owner/module.git', + tagFormat: `v\${version}`, + plugins: { + 'plugin-1': {}, + 'plugin-2': {plugin2Opt: 'value'}, + }, + }; + // Create package.json in repository root + await outputJson(path.resolve(cwd, 'package.json'), {release: options}); + + const {options: result} = await t.context.getConfig({cwd}); + + const expected = {...options, branches: ['test_branch'], plugins: ['plugin-1', ['plugin-2', {plugin2Opt: 'value'}]]}; + // Verify the options contains the plugin config from package.json + t.deepEqual(result, expected); + // Verify the plugins module is called with the plugin options from package.json + t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd}); +}); + +test('Read options from .releaserc.yml when plugins are an object', async (t) => { + // Create a git repository, set the current working directory at the root of the repo + const {cwd} = await gitRepo(); + const options = { + analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'}, + branches: ['test_branch'], + repositoryUrl: 'https://host.null/owner/module.git', + tagFormat: `v\${version}`, + plugins: { + 'plugin-1': {}, + 'plugin-2': {plugin2Opt: 'value'}, + }, + }; + // Create package.json in repository root + await writeFile(path.resolve(cwd, '.releaserc.yml'), yaml.dump(options)); + + const {options: result} = await t.context.getConfig({cwd}); + + const expected = {...options, branches: ['test_branch'], plugins: ['plugin-1', ['plugin-2', {plugin2Opt: 'value'}]]}; + // Verify the options contains the plugin config from package.json + t.deepEqual(result, expected); + // Verify the plugins module is called with the plugin options from package.json + t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd}); +}); + +test('Read options from .releaserc.json when plugins are an object', async (t) => { + // Create a git repository, set the current working directory at the root of the repo + const {cwd} = await gitRepo(); + const options = { + analyzeCommits: {path: 'analyzeCommits', param: 'analyzeCommits_param'}, + branches: ['test_branch'], + repositoryUrl: 'https://host.null/owner/module.git', + tagFormat: `v\${version}`, + plugins: { + 'plugin-1': {}, + 'plugin-2': {plugin2Opt: 'value'}, + }, + }; + // Create package.json in repository root + await outputJson(path.resolve(cwd, '.releaserc.json'), options); + + const {options: result} = await t.context.getConfig({cwd}); + + const expected = {...options, branches: ['test_branch'], plugins: ['plugin-1', ['plugin-2', {plugin2Opt: 'value'}]]}; + // Verify the options contains the plugin config from package.json + t.deepEqual(result, expected); + // Verify the plugins module is called with the plugin options from package.json + t.deepEqual(t.context.plugins.args[0][0], {options: expected, cwd}); +});