Skip to content

Commit

Permalink
feat(get-config): add support for defining plugins as an object
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
XC- committed Sep 27, 2021
1 parent 685d2b5 commit a1474b0
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 3 deletions.
7 changes: 4 additions & 3 deletions lib/get-config.js
Expand Up @@ -7,17 +7,18 @@ 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) => {
const {cwd, env} = context;
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;
Expand Down
23 changes: 23 additions & 0 deletions 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.
};
88 changes: 88 additions & 0 deletions 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});
});

0 comments on commit a1474b0

Please sign in to comment.