Skip to content

feat(configLoader): add cosmiconfig support #987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -6,4 +6,5 @@ artifacts/
npm-debug.log
.nyc_output
test/tools/trigger-appveyor-tests.sh
logo/*.png
logo/*.png
.idea
12,954 changes: 54 additions & 12,900 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -74,6 +74,7 @@
},
"dependencies": {
"cachedir": "2.3.0",
"cosmiconfig": "7.1.0",
"cz-conventional-changelog": "3.3.0",
"dedent": "0.7.0",
"detect-indent": "6.1.0",
@@ -83,6 +84,7 @@
"glob": "7.2.3",
"inquirer": "8.2.5",
"is-utf8": "^0.2.1",
"json5": "2.2.3",
"lodash": "4.17.21",
"minimist": "1.2.7",
"strip-bom": "4.0.0",
10 changes: 8 additions & 2 deletions src/cli/git-cz.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { configLoader } from '../commitizen';
import { loadConfig } from '../configLoader/loader';
import { git as useGitStrategy, gitCz as useGitCzStrategy } from './strategies';
import { createEnvironmentConfig } from "../common/util";

export {
bootstrap
@@ -14,7 +15,12 @@ function bootstrap (environment = {}, argv = process.argv) {
// Get cli args
let rawGitArgs = argv.slice(2, argv.length);

let adapterConfig = environment.config || configLoader.load();
let adapterConfig;
if (environment.config) {
adapterConfig = createEnvironmentConfig(environment.config);
} else {
adapterConfig = loadConfig();
}

// Choose a strategy based on the existance the adapter config
if (typeof adapterConfig !== 'undefined') {
14 changes: 12 additions & 2 deletions src/cli/strategies/git-cz.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,16 @@ let { isClean } = staging;

export default gitCz;

/**
*
* @param {any} rawGitArgs
* @param {any} environment
* @param {Object} adapterConfig
* @param {Object} adapterConfig.config
* @param {string} adapterConfig.config.path
* @param {string|null} adapterConfig.filepath
* @param {boolean} [adapterConfig.isEmpty]
*/
function gitCz (rawGitArgs, environment, adapterConfig) {

// See if any override conditions exist.
@@ -38,9 +48,9 @@ function gitCz (rawGitArgs, environment, adapterConfig) {
// for husky prepare-commit-message
let hookMode = !(typeof parsedCommitizenArgs.hook === 'undefined');

let resolvedAdapterConfigPath = resolveAdapterPath(adapterConfig.path);
let resolvedAdapterConfigPath = resolveAdapterPath(adapterConfig);
let resolvedAdapterRootPath = findRoot(resolvedAdapterConfigPath);
let prompter = getPrompter(adapterConfig.path);
let prompter = getPrompter(adapterConfig);
let shouldStageAllFiles = rawGitArgs.includes('-a') || rawGitArgs.includes('--all');

isClean(process.cwd(), function (error, stagingIsClean) {
47 changes: 35 additions & 12 deletions src/commitizen/adapter.js
Original file line number Diff line number Diff line change
@@ -133,10 +133,15 @@ function getInstallStringMappings({ save, dev, saveDev, exact, saveExact, force

/**
* Gets the prompter from an adapter given an adapter path
* @param {Object} adapterConfig
* @param {Object} adapterConfig.config
* @param {string} adapterConfig.config.path
* @param {string|null} adapterConfig.filepath
* @param {boolean} [adapterConfig.isEmpty]
*/
function getPrompter (adapterPath) {
function getPrompter (adapterConfig) {
// Resolve the adapter path
let resolvedAdapterPath = resolveAdapterPath(adapterPath);
let resolvedAdapterPath = resolveAdapterPath(adapterConfig);

// Load the adapter
let adapter = require(resolvedAdapterPath);
@@ -147,23 +152,41 @@ function getPrompter (adapterPath) {
} else if (adapter && adapter.default && adapter.default.prompter && isFunction(adapter.default.prompter)) {
return adapter.default.prompter;
} else {
throw new Error(`Could not find prompter method in the provided adapter module: ${adapterPath}`);
throw new Error(`Could not find prompter method in the provided adapter module: ${adapterConfig.config.path}`);
}
}

/**
* Given a resolvable module name or path, which can be a directory or file, will
* return a located adapter path or will throw.
* * @param {Object} resolvedConfig
* @param {Object} resolvedConfig.config
* @param {string} resolvedConfig.config.path
* @param {string|null} resolvedConfig.filepath pass null when environment config was provided
* @param {boolean} [resolvedConfig.isEmpty]
*/
function resolveAdapterPath (inboundAdapterPath) {
// Check if inboundAdapterPath is a path or node module name
let parsed = path.parse(inboundAdapterPath);
let isPath = parsed.dir.length > 0 && parsed.dir.charAt(0) !== "@";

// Resolve from the root of the git repo if inboundAdapterPath is a path
let absoluteAdapterPath = isPath ?
path.resolve(getGitRootPath(), inboundAdapterPath) :
inboundAdapterPath;
function resolveAdapterPath (resolvedConfig) {
// Check if resolvedConfig is a path or node module name
const parsed = path.parse(resolvedConfig.config.path);
const isPath = parsed.dir.length > 0 && parsed.dir.charAt(0) !== "@";
let absoluteAdapterPath;

if (resolvedConfig.filepath == null) {
if (isPath) {
absoluteAdapterPath = path.resolve(getGitRootPath(), resolvedConfig.config.path)
} else {
absoluteAdapterPath = resolvedConfig.config.path;
}
} else {
if (isPath) {
absoluteAdapterPath = path.resolve(
path.basename(resolvedConfig.filepath),
resolvedConfig.config.path
)
} else {
absoluteAdapterPath = resolvedConfig.config.path
}
}

try {
// try to resolve the given path
32 changes: 27 additions & 5 deletions src/commitizen/configLoader.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import { loader } from '../configLoader';
import { loadConfig, loadConfigAtRoot } from '../configLoader/loader';

export { load };
export { load, loadAtRoot };

// Configuration sources in priority order.
var configs = ['.czrc', '.cz.json', 'package.json'];

/**
* @param {String} [config] - partial path to configuration file
* @param {String} [cwd] - directory path which will be joined with config argument
* @return {any|undefined} - parsed config or nothing
*/
function load (config, cwd) {
return loader(configs, config, cwd);
const cfg = loadConfig(config, cwd);

if (cfg) {
return cfg.config;
} else return undefined;

}

/**
* @param {String} [config] - partial path to configuration file
* @param {String} [cwd] - directory path which will be joined with config argument
* @return {any|undefined} - parsed config or nothing
*/
function loadAtRoot (config, cwd) {
const cfg = loadConfigAtRoot(config, cwd);

if (cfg) {
return cfg.config;
} else return undefined;

}
4 changes: 2 additions & 2 deletions src/commitizen/init.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import childProcess from 'child_process';
import path from 'path';
import * as configLoader from './configLoader';
import * as adapter from './adapter';
import { loadAtRoot } from "./configLoader";

let {
addPathToAdapterConfig,
@@ -116,7 +116,7 @@ function checkRequiredArguments (path, adapterNpmName) {
* Loads and returns the adapter config at key config.commitizen, if it exists
*/
function loadAdapterConfig (cwd) {
let config = configLoader.load(null, cwd);
let config = loadAtRoot(undefined, cwd);
if (config) {
return config;
} else {
10 changes: 9 additions & 1 deletion src/common/util.js
Original file line number Diff line number Diff line change
@@ -5,7 +5,8 @@ export {
getParsedJsonFromFile,
getParsedPackageJsonFromPath,
isFunction,
isInTest
isInTest,
createEnvironmentConfig
}

/**
@@ -46,3 +47,10 @@ function isFunction (functionToCheck) {
function isInTest () {
return typeof global.it === 'function';
}

function createEnvironmentConfig(config) {
return {
config,
filepath: null,
}
}
12 changes: 0 additions & 12 deletions src/configLoader.js

This file was deleted.

85 changes: 85 additions & 0 deletions src/configLoader/cosmiconfigLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { cosmiconfigSync, defaultLoaders, OptionsSync } from 'cosmiconfig';
import JSON5 from 'json5';
import stripBom from 'strip-bom';
import isUTF8 from "is-utf8";

const moduleName = 'cz';
const fullModuleName = 'commitizen';
const packageJsonConfigPath = 'config';

const searchPlaces = [
`.${moduleName}rc`, // .czrc
`.${moduleName}rc.json`,
`.${moduleName}rc.json5`,
`.${moduleName}rc.yaml`,
`.${moduleName}rc.yml`,
`.${moduleName}rc.js`,
`.${moduleName}rc.cjs`,
// `.${moduleName}rc.ts`,
`.config/${moduleName}rc`,
`.config/${moduleName}rc.json`,
`.config/${moduleName}rc.json5`,
`.config/${moduleName}rc.yaml`,
`.config/${moduleName}rc.yml`,
`.config/${moduleName}rc.js`,
// `.config/${moduleName}rc.ts`,
`.config/${moduleName}rc.cjs`,
`${moduleName}.config.js`,
// `${moduleName}.config.ts`,
`${moduleName}.config.cjs`,
`.${moduleName}.json`, // .cz.json
`.${moduleName}.json5`,
'package.json',
];

function withSafeContentLoader (loader) {
return function (filePath, content) {
if (!isUTF8(Buffer.from(content, 'utf8'))) {
throw new Error(`The config file at "${filePath}" contains invalid charset, expect utf8`);
}
return loader(filePath, stripBom(content));
}
}

function json5Loader (filePath, content) {
try {
return JSON5.parse(content);
} catch (err) {
err.message = `Error parsing json at ${filePath}:\n${err.message}`;
throw err;
}
}

const loaders = {
'.cjs': withSafeContentLoader(defaultLoaders['.js']),
'.js': withSafeContentLoader(defaultLoaders['.js']),
'.yml': withSafeContentLoader(defaultLoaders['.yaml']),
'.json': withSafeContentLoader(json5Loader),
'.json5': withSafeContentLoader(json5Loader),
'.yaml': withSafeContentLoader(defaultLoaders['.yaml']),
'.ts': withSafeContentLoader(defaultLoaders['.ts']),
noExt: withSafeContentLoader(json5Loader)
}

/**
*
* @param {OptionsSync} [optionsSync] absolute or relative path where config lookup should stop. Given cwd stop lookup at cwd dir.
*/
const defaultConfigExplorer = (optionsSync) => {
return cosmiconfigSync(moduleName, {
packageProp: [packageJsonConfigPath, fullModuleName],
searchPlaces: searchPlaces,
loaders: loaders,
cache: false,
...optionsSync
});
};

export {
searchPlaces,
moduleName,
packageJsonConfigPath,
fullModuleName,
defaultConfigExplorer,
json5Loader,
};
34 changes: 0 additions & 34 deletions src/configLoader/findup.js

This file was deleted.

82 changes: 0 additions & 82 deletions src/configLoader/getContent.js

This file was deleted.

28 changes: 0 additions & 28 deletions src/configLoader/getNormalizedConfig.js

This file was deleted.

87 changes: 50 additions & 37 deletions src/configLoader/loader.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import path from 'path';

import { findup, getContent } from '../configLoader';
import { isInTest } from '../common/util.js';
import { defaultConfigExplorer } from "./cosmiconfigLoader";

export default loader;
export { loadConfig, loadConfigAtRoot };

/**
* Command line config helpers
@@ -13,48 +13,61 @@ export default loader;

/**
* Get content of the configuration file
* @param {String} config - partial path to configuration file
* @param {String} [config] - partial path to configuration file
* @param {String} [cwd = process.cwd()] - directory path which will be joined with config argument
* @return {Object|undefined}
* @return {{config: any; filepath: string; isEmpty?: boolean}|undefined}
*/
function loader (configs, config, cwd) {
var content;
var directory = cwd || process.cwd();
function loadConfig (config, cwd = process.cwd()) {
var directory = cwd || process.cwd();

// If config option is given, attempt to load it
if (config) {
return getContent(config, directory);
if (config) {
const configPath = path.resolve(directory, config);
const content = defaultConfigExplorer().load(configPath);
if (content) {
return content
}
}

content = getContent(
findup(configs, { nocase: true, cwd: directory }, function (configPath) {
if (path.basename(configPath) === 'package.json') {
// return !!this.getContent(configPath);
}

return true;
})
);
const cfg = defaultConfigExplorer().search(directory);
if (cfg) {
return cfg;
}
/* istanbul ignore if */
if (!isInTest()) { /* istanbul ignore next */
// Try to load standard configs from home dir
const directoryArr = [process.env.USERPROFILE, process.env.HOMEPATH, process.env.HOME];
for (let i = 0, dirLen = directoryArr.length; i < dirLen; i++) {
if (!directoryArr[i]) {
continue;
}

if (content) {
const content = defaultConfigExplorer({ stopDir: directoryArr[i] }).search(directoryArr[i]);
if (content) {
return content;
}
/* istanbul ignore if */
if (!isInTest()) {
// Try to load standard configs from home dir
var directoryArr = [process.env.USERPROFILE, process.env.HOMEPATH, process.env.HOME];
for (var i = 0, dirLen = directoryArr.length; i < dirLen; i++) {
if (!directoryArr[i]) {
continue;
}

for (var j = 0, len = configs.length; j < len; j++) {
content = getContent(configs[j], directoryArr[i]);

if (content) {
return content;
}
}
}
}
}
}

/**
* Get content of the configuration file
* @param {String} [config] - partial path to configuration file
* @param {String} [cwd = process.cwd()] - directory path which will be joined with config argument
* @return {{config: any; filepath: string; isEmpty?: boolean}|undefined}
*/
function loadConfigAtRoot (config, cwd) {
const directory = cwd || process.cwd();

if (config) {
const configPath = path.resolve(directory, config);
const cfg = defaultConfigExplorer({ stopDir: directory }).load(configPath);
if (cfg) {
return cfg
}
}

const cfg = defaultConfigExplorer({ stopDir: directory }).search(directory);
if (cfg) {
return cfg;
}
}
1 change: 1 addition & 0 deletions test/fixtures/empty-json.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

4 changes: 0 additions & 4 deletions test/fixtures/valid-json-rc

This file was deleted.

5 changes: 5 additions & 0 deletions test/fixtures/valid-json5.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
some: "json",
// with comments
other: Infinity
}
147 changes: 122 additions & 25 deletions test/tests/adapter.js
Original file line number Diff line number Diff line change
@@ -37,8 +37,8 @@ describe('adapter', function () {
path: config.paths.endUserRepo,
files: {
dummyfile: {
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
},
gitignore: {
contents: `node_modules/`,
@@ -57,14 +57,53 @@ describe('adapter', function () {
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog');

// TEST
expect(function () { adapter.resolveAdapterPath('IAMANIMPOSSIBLEPATH'); }).to.throw(Error);
expect(function () { adapter.resolveAdapterPath(adapterConfig.path); }).not.to.throw(Error);
expect(function () { adapter.resolveAdapterPath(path.join(adapterConfig.path, 'index.js')); }).not.to.throw(Error);

// This line is only here to make sure that cz-conventional-changelog
// was installed for the purposes of running tests, it is not needed
// for testing any other adapters.
expect(function () { adapter.resolveAdapterPath('cz-conventional-changelog'); }).not.to.throw(Error);
expect(function () {
adapter.resolveAdapterPath({
config: adapterConfig,
filepath: 'IAMANIMPOSSIBLEPATH',
});
}).to.throw;

expect(function () {
adapter.resolveAdapterPath({
config: adapterConfig,
filepath: null,
});
}).not.to.throw;
expect(function () {
adapter.resolveAdapterPath({
config: {
path: './node_modules/cz-conventional-changelog',
npmName: 'cz-conventional-changelog'
},
filepath: repoConfig.path,
});
}).not.to.throw;

expect(function () {
adapter.resolveAdapterPath({
config: adapterConfig,
filepath: repoConfig.path,
});
}).not.to.throw;

expect(function () {
adapter.resolveAdapterPath({
config: {
path: 'cz-conventional-changelog',
},
filepath: repoConfig.path,
});
}).not.to.throw;

expect(function () {
adapter.resolveAdapterPath({
config: {
path: 'cz-conventional-changelog'
},
filepath: null,
});
}).not.to.throw(Error);
});

it('resolves scoped adapter paths', function () {
@@ -78,8 +117,8 @@ describe('adapter', function () {
path: config.paths.endUserRepo,
files: {
dummyfile: {
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
},
gitignore: {
contents: `node_modules/`,
@@ -98,9 +137,35 @@ describe('adapter', function () {
commitizenInit(config.paths.endUserRepo, '@commitizen/cz-conventional-changelog');

// TEST
expect(function () { adapter.resolveAdapterPath('IAMANIMPOSSIBLEPATH'); }).to.throw(Error);
expect(function () { adapter.resolveAdapterPath(adapterConfig.path); }).not.to.throw(Error);
expect(function () { adapter.resolveAdapterPath(path.join(adapterConfig.path, 'index.js')); }).not.to.throw(Error);
expect(function () {
adapter.resolveAdapterPath({
config: adapterConfig,
filepath: 'IAMANIMPOSSIBLEPATH',
});
}).to.throw;
expect(function () {
adapter.resolveAdapterPath({
config: adapterConfig,
filepath: null,
});
}).not.to.throw;
expect(function () {
adapter.resolveAdapterPath({
config: {
path: './node_modules/@commitizen/cz-conventional-changelog',
},
filepath: repoConfig.path,
});
}).not.to.throw;

expect(function () {
adapter.resolveAdapterPath({
config: {
path: '@commitizen/cz-conventional-changelog',
},
filepath: repoConfig.path,
});
}).not.to.throw;
});

it.skip('gets adapter prompter functions', function () {
@@ -114,8 +179,8 @@ describe('adapter', function () {
path: config.paths.endUserRepo,
files: {
dummyfile: {
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
},
gitignore: {
contents: `node_modules/`,
@@ -131,11 +196,15 @@ describe('adapter', function () {
};

// Install an adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', {includeCommitizen: true});
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { includeCommitizen: true });

// TEST
expect(function () { adapter.getPrompter('IAMANIMPOSSIBLEPATH'); }).to.throw(Error);
expect(function () { adapter.getPrompter(adapterConfig.path); }).not.to.throw(Error);
expect(function () {
adapter.getPrompter('IAMANIMPOSSIBLEPATH');
}).to.throw(Error);
expect(function () {
adapter.getPrompter(adapterConfig.path);
}).not.to.throw(Error);
expect(isFunction(adapter.getPrompter(adapterConfig.path))).to.be.true;
});

@@ -150,8 +219,8 @@ describe('adapter', function () {
path: config.paths.endUserRepo,
files: {
dummyfile: {
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
contents: `duck-duck-goose`,
filename: `mydummyfile.txt`,
},
gitignore: {
contents: `node_modules/`,
@@ -170,9 +239,37 @@ describe('adapter', function () {
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog-default-export');

// TEST
expect(function () { adapter.getPrompter('IAMANIMPOSSIBLEPATH'); }).to.throw(Error);
expect(function () { adapter.getPrompter(adapterConfig.path); }).not.to.throw(Error);
expect(isFunction(adapter.getPrompter(adapterConfig.path))).to.be.true;
expect(function () {
adapter.getPrompter({
config: adapterConfig,
filepath: 'IAMANIMPOSSIBLEPATH',
});
}).to.throw;
expect(function () {
adapter.getPrompter({
config: adapterConfig,
filepath: null,
});
}).not.to.throw;
expect(function () {
adapter.getPrompter({
config: adapterConfig,
filepath: repoConfig.path,
});
}).not.to.throw;
expect(function () {
adapter.getPrompter({
config: {
path: 'cz-conventional-changelog-default-export'
},
filepath: repoConfig.path,
});
}).not.to.throw;

expect(isFunction(adapter.getPrompter({
config: adapterConfig,
filepath: null,
}))).to.be.true;
});

});
27 changes: 20 additions & 7 deletions test/tests/cli.js
Original file line number Diff line number Diff line change
@@ -2,25 +2,39 @@ import { expect } from 'chai';
import proxyquire from 'proxyquire';
import sinon from 'sinon';

// Bootstrap our tester
import { bootstrap } from '../tester';
import { createEnvironmentConfig } from "../../src/common/util";
// Destructure some things based on the bootstrap process
let { config, repo, clean, util } = bootstrap();

describe('git-cz', () => {
let bootstrap;
let fakeStrategies, fakeCommitizen;
let fakeStrategies, fakeCommitizen, fakeConfigLoader;

beforeEach(() => {
beforeEach(function () {
this.timeout(config.maxTimeout);
fakeStrategies = {
git: sinon.spy(),
gitCz: sinon.spy()
}

fakeCommitizen = {
configLoader: {
load: sinon.stub()
load: sinon.stub(),
loadAtRoot: sinon.stub()
}
}

fakeConfigLoader = {
loadConfig: sinon.stub(),
loadConfigAtRoot: sinon.stub()
}

bootstrap = proxyquire('../../src/cli/git-cz', {
'./strategies': fakeStrategies,
'../commitizen': fakeCommitizen
'../commitizen': fakeCommitizen,
'../configLoader/loader': fakeConfigLoader
}).bootstrap;
});

@@ -31,7 +45,7 @@ describe('git-cz', () => {

bootstrap({ config });

expect(fakeStrategies.gitCz.args[0][2]).to.equal(config);
expect(fakeStrategies.gitCz.args[0][2]).to.deep.equal(createEnvironmentConfig(config));
});
});

@@ -40,10 +54,9 @@ describe('git-cz', () => {
describe('and the config is returned from configLoader.load', () => {
it('uses config from configLoader.load()', () => {
const config = sinon.stub();
fakeCommitizen.configLoader.load.returns(config);
fakeConfigLoader.loadConfig.returns(config);

bootstrap({});

expect(fakeStrategies.gitCz.args[0][2]).to.equal(config);
});
});
224 changes: 224 additions & 0 deletions test/tests/commitizen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
import { expect } from 'chai';
import fs from "fs";
import path from "path";

import { configLoader } from '../../src/commitizen';
// Bootstrap our tester
import { bootstrap } from '../tester';
import { writeFilesToPath } from "../tools/files";

let { config, repo, clean, files } = bootstrap();

const { load, loadAtRoot } = configLoader;

before(function () {
// Creates the temp path
clean.before(config.paths.tmp);
});

beforeEach(function () {
this.timeout(config.maxTimeout); // this could take a while
/* istanbul ignore next */
repo.createEndUser(config.paths.endUserRepo);
});

afterEach(function () {
this.timeout(config.maxTimeout); // this could take a while
// All this should do is archive the tmp path to the artifacts
clean.afterEach(config.paths.tmp, config.preserve);
});

after(function () {
this.timeout(config.maxTimeout); // this could take a while
// Once everything is done, the artifacts should be cleaned up based on
// the preserve setting in the config
clean.after(config.paths.tmp, config.preserve);
});

describe('commitizen configLoader public api', function () {
describe('load', function () {
it('should find config at root', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrc: {
contents: `{
"path": "right config"
}`,
filename: `.czrc`,
},
},
};

writeFilesToPath(repoConfig.files, repoConfig.path);
expect(load(undefined, repoConfig.path)).to.deep.equal({ path: 'right config' });
});

it('should find config at upper folders', function () {
const repoConfig = {
path: config.paths.endUserRepo,
};

const file = {
czrc: {
contents: `{
"path": "right config"
}`,
filename: `.czrc`,
},
};

fs.writeFileSync(path.resolve(repoConfig.path, '../', file.czrc.filename), file.czrc.contents);

expect(load(undefined, repoConfig.path)).to.deep.equal({ path: 'right config' });
});

it('should prefer config at root', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrc: {
contents: `{
"path": "right config"
}`,
filename: `.czrc`,
},
},
};

const wrongFile = {
czrc: {
contents: `{
"path": "wrong config"
}`,
filename: `.czrc`,
},
};

fs.writeFileSync(path.resolve(repoConfig.path, '..', wrongFile.czrc.filename), wrongFile.czrc.contents);

writeFilesToPath(repoConfig.files, repoConfig.path);
expect(load(undefined, repoConfig.path)).to.deep.equal({ path: 'right config' });
});

it('should load config at given path with given name if provided', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrc: {
contents: `{
"path": "wrong config"
}`,
filename: `.czrc`,
},
czrcjson: {
contents: `{
"path": "right config"
}`,
filename: `.czrc.json`,
},
},
};

writeFilesToPath(repoConfig.files, repoConfig.path);
expect(load(repoConfig.files.czrcjson.filename, repoConfig.path)).to.deep.equal({ path: 'right config' });
});

it('should throw when no file exist', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrcjson: {
contents: `{
"path": "right config"
}`,
filename: `.czrc.json`,
},
},
};

expect(() => load(repoConfig.files.czrcjson.filename, repoConfig.path)).to.throw;
});

it('should return undefined when no file exists in all upper paths and root', function () {
expect(load(undefined, path.resolve('/'))).to.be.undefined;
});
});

describe('loadAtRoot', function () {
it('should find config at root', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrc: {
contents: `{
"path": "right config"
}`,
filename: `.czrc`,
},
},
};

writeFilesToPath(repoConfig.files, repoConfig.path);
expect(loadAtRoot(undefined, repoConfig.path)).to.deep.equal({ path: 'right config' });
});

it('should ignore upper level configs config at upper folders', function () {
const repoConfig = {
path: config.paths.endUserRepo,
};

const file = {
czrc: {
contents: `{
"path": "right config"
}`,
filename: `.czrc`,
},
};

fs.writeFileSync(path.resolve(repoConfig.path, '../', file.czrc.filename), file.czrc.contents);

expect(loadAtRoot(undefined, repoConfig.path)).to.be.undefined;
});

it('should throw when no file exist', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrcjson: {
contents: `{
"path": "right config"
}`,
filename: `.czrc.json`,
},
},
};

expect(() => loadAtRoot(repoConfig.files.czrcjson.filename, repoConfig.path)).to.throw;
});

it('should load config at given path with given name if provided', function () {
const repoConfig = {
path: config.paths.endUserRepo,
files: {
czrc: {
contents: `{
"path": "wrong config"
}`,
filename: `.czrc`,
},
czrcjson: {
contents: `{
"path": "right config"
}`,
filename: `.czrc.json`,
},
},
};

writeFilesToPath(repoConfig.files, repoConfig.path);
expect(loadAtRoot(repoConfig.files.czrcjson.filename, repoConfig.path)).to.deep.equal({ path: 'right config' });
});
});
});
1,132 changes: 1,100 additions & 32 deletions test/tests/configLoader.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions test/tests/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import './adapter';
import './cli';
import './commit';
import './commitizen';
import './configLoader';
import './init';
import './parsers';
197 changes: 99 additions & 98 deletions test/tests/init.js
Original file line number Diff line number Diff line change
@@ -23,155 +23,156 @@ beforeEach(function () {
});

describe('init', function () {
describe('init with npm', () => {
it('installs an adapter with --save-dev', function () {

it('installs an adapter with --save-dev', function () {
this.timeout(config.maxTimeout); // this could take a while

this.timeout(config.maxTimeout); // this could take a while
// SETUP

// SETUP
// Install an adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog');

// Install an adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog');
// TEST

// TEST
// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).to.have.nested.property('devDependencies.cz-conventional-changelog');

// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).to.have.nested.property('devDependencies.cz-conventional-changelog');

});
});

it('installs an adapter with --save', function () {
it('installs an adapter with --save', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Install an adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { save: true, saveDev: false });
// Install an adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { save: true, saveDev: false });

// TEST
// TEST

// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).to.have.nested.property('dependencies.cz-conventional-changelog');
// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).to.have.nested.property('dependencies.cz-conventional-changelog');

});
});

it('errors on previously installed adapter', function () {
it('errors on previously installed adapter', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveDev: true });
// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveDev: true });

// TEST
// Adding a second adapter
expect(function () {
commitizenInit(config.paths.endUserRepo, 'cz-jira-smart-commit', { saveDev: true });
}).to.throw(/already configured/);
// TEST
// Adding a second adapter
expect(function () {
commitizenInit(config.paths.endUserRepo, 'cz-jira-smart-commit', { saveDev: true });
}).to.throw(/already configured/);

// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).not.to.have.nested.property('devDependencies', 'cz-jira-smart-commit');
expect(packageJson).to.have.nested.property('config.commitizen.path', './node_modules/cz-conventional-changelog');
// TODO: Eventually may need to offer both path and package keys. package = npm package name
// Path for local development
});
// Check resulting json
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson).not.to.have.nested.property('devDependencies', 'cz-jira-smart-commit');
expect(packageJson).to.have.nested.property('config.commitizen.path', './node_modules/cz-conventional-changelog');
// TODO: Eventually may need to offer both path and package keys. package = npm package name
// Path for local development
});

it('succeeds if force is true', function () {
it('succeeds if force is true', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveDev: true });
// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveDev: true });

// TEST
// TEST

// Adding a second adapter
expect(function () {
commitizenInit(config.paths.endUserRepo, 'cz-jira-smart-commit', { saveDev: true, force: true });
}).to.not.throw();
// Adding a second adapter
expect(function () {
commitizenInit(config.paths.endUserRepo, 'cz-jira-smart-commit', { saveDev: true, force: true });
}).to.not.throw();

let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson.devDependencies).to.have.property('cz-jira-smart-commit');
expect(packageJson).to.have.nested.property('config.commitizen.path', './node_modules/cz-jira-smart-commit');
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
expect(packageJson.devDependencies).to.have.property('cz-jira-smart-commit');
expect(packageJson).to.have.nested.property('config.commitizen.path', './node_modules/cz-jira-smart-commit');

});
});

it('installs an adapter without --save-exact', function () {
it('installs an adapter without --save-exact', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog');
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog');
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);

// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
let range = packageJson.devDependencies['cz-conventional-changelog'];
// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
let range = packageJson.devDependencies['cz-conventional-changelog'];

// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);
// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);

// // But you CAN NOT increment a range
// expect(semver.inc(range, 'major')).to.equal(null);
// TODO: We need to figure out how to check if the repo has save exact set
// in the config before we can re-enable this. The --save-exact setting
// in our package.json breaks this test
// // But you CAN NOT increment a range
// expect(semver.inc(range, 'major')).to.equal(null);
// TODO: We need to figure out how to check if the repo has save exact set
// in the config before we can re-enable this. The --save-exact setting
// in our package.json breaks this test

});
});

it('installs an adapter with --save-exact', function () {
it('installs an adapter with --save-exact', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveExact: true });
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { saveExact: true });
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);

// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
let range = packageJson.devDependencies['cz-conventional-changelog'];
// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
let range = packageJson.devDependencies['cz-conventional-changelog'];

// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);
// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);

// But you CAN increment a single version
expect(semver.inc(range, 'major')).not.to.equal(null);
// But you CAN increment a single version
expect(semver.inc(range, 'major')).not.to.equal(null);

});
});

it('installs an commitizen with includeCommitizen', function () {
it('installs an commitizen with includeCommitizen', function () {

this.timeout(config.maxTimeout); // this could take a while
this.timeout(config.maxTimeout); // this could take a while

// SETUP
// SETUP

// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { includeCommitizen: true });
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);
// Add a first adapter
commitizenInit(config.paths.endUserRepo, 'cz-conventional-changelog', { includeCommitizen: true });
let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo);

// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
expect(packageJson.devDependencies).to.have.property('commitizen');
let range = packageJson.devDependencies['cz-conventional-changelog'];
// TEST
expect(packageJson.devDependencies).to.have.property('cz-conventional-changelog');
expect(packageJson.devDependencies).to.have.property('commitizen');
let range = packageJson.devDependencies['cz-conventional-changelog'];

// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);
// It should satisfy the requirements of a range
expect(semver.validRange(range)).to.not.equal(null);

// But you CAN increment a single version
expect(semver.inc(range, 'major')).not.to.equal(null);
// But you CAN increment a single version
expect(semver.inc(range, 'major')).not.to.equal(null);

});
});
})

const supportedPackageManagers = ['yarn', 'pnpm'];