diff --git a/README.md b/README.md index d290edf0..5663a94d 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,15 @@ Next, initialize your project to use the cz-conventional-changelog adapter by ty commitizen init cz-conventional-changelog --save-dev --save-exact ``` +Or if you are using Yarn: + +``` +commitizen init cz-conventional-changelog --yarn --dev --exact +``` + Note that if you want to force install over the top of an old adapter, you can apply the `--force` argument. For more information on this, just run `commitizen help`. -The above command does three things for you. +The above command does three things for you. 1. Installs the cz-conventional-changelog adapter npm module 2. Saves it to package.json's dependencies or devDependencies 3. Adds the `config.commitizen` key to the root of your **package.json** as shown here: diff --git a/package.json b/package.json index 8bc69421..67f91346 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "semantic-release": "semantic-release pre && npm publish && semantic-release post", "start": "npm run test:watch", "lint": "eslint --ignore-path .gitignore .", - "test": "nyc --require babel-core/register _mocha -- test/tests/index.js", + "test": "nyc --require ./test/helpers/register _mocha -- test/tests/index.js", "test:watch": "nodemon -q --ignore test/.tmp/ --ignore test/artifacts/ --ignore coverage/ --exec \"npm run test\" --", "test:windows": "node ./test/tools/trigger-appveyor-tests.js" }, diff --git a/src/commitizen/adapter.js b/src/commitizen/adapter.js index 27959cea..10a9950a 100644 --- a/src/commitizen/adapter.js +++ b/src/commitizen/adapter.js @@ -14,7 +14,9 @@ export { getNpmInstallStringMappings, getPrompter, generateNpmInstallAdapterCommand, - resolveAdapterPath + resolveAdapterPath, + getYarnAddStringMappings, + generateYarnAddAdapterCommand, }; /** @@ -70,6 +72,24 @@ function generateNpmInstallAdapterCommand (stringMappings, adapterNpmName) { return installAdapterCommand; } +/** + * Generates an yarn add command given a map of strings and a package name + */ +function generateYarnAddAdapterCommand (stringMappings, adapterNpmName) { + + // Start with an initial yarn add command + let installAdapterCommand = `yarn add ${adapterNpmName}`; + + // Append the necessary arguments to it based on user preferences + for (let [key, value] of stringMappings.entries()) { + if (value) { + installAdapterCommand = installAdapterCommand + ' ' + value; + } + } + + return installAdapterCommand; +} + /** * Gets the nearest npm_modules directory */ @@ -106,6 +126,16 @@ function getNpmInstallStringMappings (save, saveDev, saveExact, force) { .set('force', force ? '--force' : undefined); } +/** + * Gets a map of arguments where the value is the corresponding yarn strings + */ +function getYarnAddStringMappings (dev, exact, force) { + return new Map() + .set('dev', dev ? '--dev' : undefined) + .set('exact', exact ? '--exact' : undefined) + .set('force', force ? '--force' : undefined); +} + /** * Gets the prompter from an adapter given an adapter path */ @@ -115,7 +145,7 @@ function getPrompter (adapterPath) { // Load the adapter let adapter = require(resolvedAdapterPath); - + /* istanbul ignore next */ if (adapter && adapter.prompter && isFunction(adapter.prompter)) { return adapter.prompter; diff --git a/src/commitizen/init.js b/src/commitizen/init.js index d73696ab..7333ac45 100644 --- a/src/commitizen/init.js +++ b/src/commitizen/init.js @@ -6,7 +6,9 @@ import * as adapter from './adapter'; let { addPathToAdapterConfig, generateNpmInstallAdapterCommand, - getNpmInstallStringMappings + getNpmInstallStringMappings, + generateYarnAddAdapterCommand, + getYarnAddStringMappings, } = adapter; export default init; @@ -15,12 +17,12 @@ const CLI_PATH = path.normalize(__dirname + '/../../'); /** * CZ INIT - * + * * Init is generally responsible for initializing an adapter in - * a user's project. The goal is to be able to run + * a user's project. The goal is to be able to run * `commitizen init` and be prompted for certain fields which * will help you install the proper adapter for your project. - * + * * Init does not actually create the adapter (it defers to adapter * for this). Instead, it is specifically designed to help gather * and validate the information needed to install the adapter @@ -34,65 +36,74 @@ const defaultInitOptions = { save: false, saveDev: true, saveExact: false, - force: false + force: false, + + // for --yarn use + // @see https://github.com/commitizen/cz-cli/issues/527#issuecomment-392653897 + yarn: false, + dev: true, + exact: false, // should add trailing comma, thus next developer doesn't got blamed for this line }; /** * Runs npm install for the adapter then modifies the config.commitizen as needed */ function init (sh, repoPath, adapterNpmName, { - save = false, - saveDev = true, + save = false, + saveDev = true, saveExact = false, - force = false + force = false, + yarn = false, + dev = false, + exact = false, } = defaultInitOptions) { - + // Don't let things move forward if required args are missing checkRequiredArguments(sh, repoPath, adapterNpmName); - + // Move to the correct directory so we can run commands sh.cd(repoPath); - + // Load the current adapter config let adapterConfig = loadAdapterConfig(); - + // Get the npm string mappings based on the arguments provided - let stringMappings = getNpmInstallStringMappings(save, saveDev, saveExact, force); - + let stringMappings = yarn ? getYarnAddStringMappings(dev, exact, force) : getNpmInstallStringMappings(save, saveDev, saveExact, force); + // Generate a string that represents the npm install command - let installAdapterCommand = generateNpmInstallAdapterCommand(stringMappings, adapterNpmName); + let installAdapterCommand = yarn ? generateYarnAddAdapterCommand(stringMappings, adapterNpmName) : generateNpmInstallAdapterCommand(stringMappings, adapterNpmName); // Check for previously installed adapters if (adapterConfig && adapterConfig.path && adapterConfig.path.length > 0) { - + // console.log(` - // Previous adapter detected! + // Previous adapter detected! // `); - - if (!force) { - + + if (!force) { + // console.log(` - // Previous adapter detected! + // Previous adapter detected! // `); - - throw 'A previous adapter is already configured. Use --force to override'; + + throw 'A previous adapter is already configured. Use --force to override'; } else { // Override it try { executeShellCommand(sh, repoPath, installAdapterCommand); - addPathToAdapterConfig(sh, CLI_PATH, repoPath, adapterNpmName); + addPathToAdapterConfig(sh, CLI_PATH, repoPath, adapterNpmName); } catch (e) { console.error(e); } } - + } else { - + // console.log(` - // No previous adapter was detected - // `); + // No previous adapter was detected + // `); try { - + executeShellCommand(sh, repoPath, installAdapterCommand); addPathToAdapterConfig(sh, CLI_PATH, repoPath, adapterNpmName); } catch (e) { @@ -124,7 +135,7 @@ function checkRequiredArguments (sh, path, adapterNpmName) { function loadAdapterConfig () { let config = configLoader.load(); if (config) { - return config; + return config; } else { return; } diff --git a/test/helpers/register.js b/test/helpers/register.js new file mode 100644 index 00000000..f101f787 --- /dev/null +++ b/test/helpers/register.js @@ -0,0 +1,6 @@ +require('babel-core/register')({ + ignore: [ + /node_modules/, + /yarn/ + ] +}); diff --git a/test/tests/init.js b/test/tests/init.js index fd0654a3..d49c19cc 100644 --- a/test/tests/init.js +++ b/test/tests/init.js @@ -158,6 +158,139 @@ describe('init', function () { expect(semver.inc(range, 'major')).not.to.equal(null); }); + + it('installs an adapter with --yarn', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Install an adapter + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true }); + + // TEST + + // Check resulting json + let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo); + expect(packageJson).to.have.deep.property('dependencies.cz-conventional-changelog'); + + }); + + + it('installs an adapter with --yarn --dev', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Install an adapter + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true, dev: true }); + + // TEST + + // Check resulting json + let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo); + expect(packageJson).to.have.deep.property('devDependencies.cz-conventional-changelog'); + + }); + + it('errors (with --yarn) on previously installed adapter', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Add a first adapter + sh.cd(config.paths.endUserRepo); + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true, dev: true }); + + // TEST + sh.cd(config.paths.endUserRepo); + // Adding a second adapter + expect(function () { + commitizenInit(sh, config.paths.endUserRepo, 'cz-jira-smart-commit', { yarn: true, dev: true }); + }).to.throw(/already configured/); + + // Check resulting json + let packageJson = util.getParsedPackageJsonFromPath(config.paths.endUserRepo); + expect(packageJson).not.to.have.deep.property('devDependencies', 'cz-jira-smart-commit'); + expect(packageJson).to.have.deep.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 (with --yarn) if force is true', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Add a first adapter + sh.cd(config.paths.endUserRepo); + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true, dev: true }); + + // TEST + + // Adding a second adapter + expect(function () { + commitizenInit(sh, config.paths.endUserRepo, 'cz-jira-smart-commit', { yarn: true, dev: 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.deep.property('config.commitizen.path', './node_modules/cz-jira-smart-commit'); + + }); + + it('installs (with --yarn) an adapter without --save-exact', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Add a first adapter + sh.cd(config.paths.endUserRepo); + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true, dev: 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']; + + // 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 + + }); + + it('installs an adapter with --yarn --exact', function () { + + this.timeout(config.maxTimeout); // this could take a while + + // SETUP + + // Add a first adapter + sh.cd(config.paths.endUserRepo); + commitizenInit(sh, config.paths.endUserRepo, 'cz-conventional-changelog', { yarn: true, dev: true, exact: 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']; + + // 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); + + }); + }); afterEach(function () {