From ea8afb7154478ee8f1264a95fad4dd449b9aeb72 Mon Sep 17 00:00:00 2001 From: "David J. Hamilton" Date: Wed, 8 Nov 2017 14:10:36 -0800 Subject: [PATCH] Implicitly add an npm dev dependency on bower For scenarios that 1. do not already have a dependency or dev dependency on bower and 2. have bower dependencies Also assert that 1. npm is ran before bower 2. if bower is run, a local bower is found --- lib/dependency-manager-adapters/bower.js | 11 -- lib/utils/config.js | 64 ++++++- .../bower-adapter-test.js | 52 +----- test/utils/config-test.js | 161 ++++++++++++++---- ...dependency-manager-adapter-factory-test.js | 2 +- test/utils/scenario-manager-test.js | 2 +- 6 files changed, 199 insertions(+), 93 deletions(-) diff --git a/lib/dependency-manager-adapters/bower.js b/lib/dependency-manager-adapters/bower.js index 1c4622d0..31e909da 100644 --- a/lib/dependency-manager-adapters/bower.js +++ b/lib/dependency-manager-adapters/bower.js @@ -121,17 +121,6 @@ module.exports = CoreObject.extend({ return adapter._findBowerPath(adapter.cwd).then(function(bowerPath) { debug('Run bower install using bower at %s', bowerPath); return adapter.run('node', [].concat([bowerPath], commandParts, options), { cwd: adapter.cwd }); - }).catch(function() { - debug('Local bower not found'); - return adapter._checkForGlobalBower().then(function() { - debug('Using global bower'); - return adapter.run('bower', [].concat(commandParts, options), { cwd: adapter.cwd }); - }); - }); - }, - _checkForGlobalBower: function() { - return this.run('bower', ['-v'], { stdio: 'ignore' }).catch(function() { - throw new Error('Bower must be installed either locally or globally to have bower scenarios'); }); }, _bowerJSONForDependencySet: function(bowerJSON, depSet) { diff --git a/lib/utils/config.js b/lib/utils/config.js index 2baf4727..3910fe89 100644 --- a/lib/utils/config.js +++ b/lib/utils/config.js @@ -7,7 +7,9 @@ var RSVP = require('rsvp'); var findByName = require('./find-by-name'); var debug = require('debug')('ember-try:utils:config'); -function config(options) { +var IMPLICIT_BOWER_VERSION = '^1.8.2'; + +function getBaseConfig(options) { var relativePath = options.configPath || path.join('config', 'ember-try.js'); var configFile = path.join(options.project.root, relativePath); var configData; @@ -39,8 +41,39 @@ function config(options) { } } +function config(options) { + return getBaseConfig(options).then(function(configData) { + return addImplicitBowerToScenarios(configData); + }); +} + module.exports = config; +function addImplicitBowerToScenarios(configData) { + configData.scenarios.forEach(function(scenario) { + if (!('bower' in scenario)) { + // Don't do anything for scenarios that don't include bower + return; + } + + if ('npm' in scenario) { + var npm = scenario.npm; + if ((npm.dependencies && npm.dependencies.bower) || + (npm.devDependencies && npm.devDependencies.bower)) { + // Dont' do anything for scenarios that already include bower in npm, + // either as a dependency or a dev dependency + return; + } + } + + // add an implicit bower dev dependency to npm for this scenario + scenario.npm = scenario.npm || {}; + scenario.npm.devDependencies = scenario.npm.devDependencies || {}; + scenario.npm.devDependencies.bower = IMPLICIT_BOWER_VERSION; + }); + return configData; +} + function mergeAutoConfigAndConfigFileData(autoConfig, configData) { configData = configData || {}; configData.scenarios = configData.scenarios || []; @@ -77,7 +110,12 @@ function defaultConfig() { dependencies: { } /* No dependencies needed as the default is already specified in the consuming app's bower.json */ - } + }, + npm: { + devDependencies: { + bower: IMPLICIT_BOWER_VERSION, + } + }, }, { name: 'ember-release', @@ -85,7 +123,12 @@ function defaultConfig() { dependencies: { ember: 'release' } - } + }, + npm: { + devDependencies: { + bower: IMPLICIT_BOWER_VERSION, + } + }, }, { name: 'ember-beta', @@ -93,7 +136,12 @@ function defaultConfig() { dependencies: { ember: 'beta' } - } + }, + npm: { + devDependencies: { + bower: IMPLICIT_BOWER_VERSION, + } + }, }, { name: 'ember-canary', @@ -101,7 +149,12 @@ function defaultConfig() { dependencies: { ember: 'canary' } - } + }, + npm: { + devDependencies: { + bower: IMPLICIT_BOWER_VERSION, + } + }, } ] }; @@ -109,3 +162,4 @@ function defaultConfig() { // Used for internal testing purposes. module.exports._defaultConfig = defaultConfig; +module.exports._addImplicitBowerToScenarios = addImplicitBowerToScenarios; diff --git a/test/dependency-manager-adapters/bower-adapter-test.js b/test/dependency-manager-adapters/bower-adapter-test.js index cea877cb..bf6641c9 100644 --- a/test/dependency-manager-adapters/bower-adapter-test.js +++ b/test/dependency-manager-adapters/bower-adapter-test.js @@ -121,33 +121,23 @@ describe('bowerAdapter', function() { return new BowerAdapter({ cwd: tmpdir, run: stubbedRun })._install(); }); - it('runs bower install with global bower if local bower is not found', function() { - var actualCommand, actualArgs, actualOpts; - + it('rejects if local bower is not found', function() { var doNotFindLocalBower = function() { - return RSVP.reject(); - }; - - var findGlobalBower = function() { - return RSVP.resolve(); + return RSVP.reject('no local bower found'); }; - var stubbedRun = function(command, args, opts) { - actualCommand = command; - actualArgs = args; - actualOpts = opts; - return RSVP.resolve(); + var stubbedRun = function() { + return RSVP.reject(); }; return new BowerAdapter({ cwd: tmpdir, _findBowerPath: doNotFindLocalBower, - _checkForGlobalBower: findGlobalBower, run: stubbedRun })._install().then(function() { - expect(actualCommand).to.equal('bower'); - expect(actualArgs).to.eql(['install', '--config.interactive=false']); - expect(actualOpts).to.have.property('cwd', tmpdir); + expect.fail(true, false, 'unreachable: _install promise rejects'); + }, function(error) { + expect(error).to.equal('no local bower found'); }); }); @@ -283,34 +273,6 @@ describe('bowerAdapter', function() { }); }); }); - - describe('#_checkForGlobalBower()', function() { - it('runs bower -v to see if bower exists', function() { - let actualCommand, actualArgs, actualOpts; - var stubbedRun = function(command, args, opts) { - actualCommand = command; - actualArgs = args; - actualOpts = opts; - return RSVP.resolve(); - }; - - return new BowerAdapter({ cwd: tmpdir, run: stubbedRun })._checkForGlobalBower().then(function() { - expect(actualCommand).to.equal('bower'); - expect(actualArgs).to.eql(['-v']); - expect(actualOpts.stdio).to.equal('ignore'); - }); - }); - - it('throws if running bower -v fails', function() { - var stubbedRun = function() { - return RSVP.reject(); - }; - - return new BowerAdapter({ cwd: tmpdir, run: stubbedRun })._checkForGlobalBower().catch(function(err) { - expect(err).to.match(/Bower must be installed either locally or globally to have bower scenarios/); - }); - }); - }); }); function assertFileContainsJSON(filename, expectedObj) { diff --git a/test/utils/config-test.js b/test/utils/config-test.js index e3cf5b2b..c5ab4eec 100644 --- a/test/utils/config-test.js +++ b/test/utils/config-test.js @@ -9,6 +9,7 @@ var fixturePackage = require('../fixtures/package.json'); var writeJSONFile = require('../helpers/write-json-file'); var getConfig = require('../../lib/utils/config'); var defaultConfig = getConfig._defaultConfig; +var addImplicitBowerToScenarios = getConfig._addImplicitBowerToScenarios; var remove = RSVP.denodeify(fs.remove); var root = process.cwd(); @@ -36,6 +37,85 @@ describe('utils/config', function() { fs.writeFileSync('config/' + filename, contents, { encoding: 'utf8' }); } + describe('addImplicitBowerToScenarios', function() { + it('adds an npm with a bower dev depencency for scenarios that have only bower', function() { + expect(addImplicitBowerToScenarios({ + scenarios: [{ + name: 'bower-only', + bower: { dependencies: {} }, + }] + })).to.deep.equal({ + scenarios: [{ + name: 'bower-only', + bower: { dependencies: {} }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }] + }); + }); + + it('adds a bower dev dependency for scnearios that have bower and npm', function() { + expect(addImplicitBowerToScenarios({ + scenarios: [{ + name: 'bower-and-npm', + bower: { dependencies: {} }, + npm: { devDependencies: { foo: 'latest' } }, + }] + })).to.deep.equal({ + scenarios: [{ + name: 'bower-and-npm', + bower: { dependencies: {} }, + npm: { devDependencies: { foo: 'latest', bower: '^1.8.2' } }, + }] + }); + }); + + it('does not add a bower dev dependency if a bower dev dependency is already present', function() { + expect(addImplicitBowerToScenarios({ + scenarios: [{ + name: 'bower-dev-dependency', + bower: { dependencies: {} }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }] + })).to.deep.equal({ + scenarios: [{ + name: 'bower-dev-dependency', + bower: { dependencies: {} }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }] + }); + }); + + it('does not add a bower dev dependency if a bower dependency is already present', function() { + expect(addImplicitBowerToScenarios({ + scenarios: [{ + name: 'bower-dependency', + bower: { dependencies: {} }, + npm: { dependencies: { bower: '^1.8.2' } }, + }] + })).to.deep.equal({ + scenarios: [{ + name: 'bower-dependency', + bower: { dependencies: {} }, + npm: { dependencies: { bower: '^1.8.2' } }, + }] + }); + }); + + it('does not add a bower dev dependency if bower is not present', function() { + expect(addImplicitBowerToScenarios({ + scenarios: [{ + name: 'no-bower', + npm: { devDependencies: { foo: 'latest' } }, + }] + })).to.deep.equal({ + scenarios: [{ + name: 'no-bower', + npm: { devDependencies: { foo: 'latest' } }, + }] + }); + }); + }); + it('uses specified options.configFile if present', function() { generateConfigFile('module.exports = { scenarios: [ { qux: "baz" }] };', 'non-default.js'); @@ -54,6 +134,29 @@ describe('utils/config', function() { }); }); + it('implicitly adds a bower dev dependency for npm for scenarios that include bower', function() { + generateConfigFile('module.exports = { scenarios: [ { bower: { dependencies: { foo: "latest" } } }] };'); + + return getConfig({ project: project }).then(function(config) { + expect(config.scenarios).to.have.lengthOf(1); + expect(config.scenarios[0]).to.deep.equal({ + bower: { dependencies: { foo: 'latest' } }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }); + }); + }); + + it('does not add a bower dependency for scenarios that do not include bower', function() { + generateConfigFile('module.exports = { scenarios: [ { npm: { dependencies: { foo: "latest" } } }] };'); + + return getConfig({ project: project }).then(function(config) { + expect(config.scenarios).to.have.lengthOf(1); + expect(config.scenarios[0]).to.deep.equal({ + npm: { dependencies: { foo: 'latest' } }, + }); + }); + }); + it('config file can export a function', function() { generateConfigFile('module.exports = function() { return { scenarios: [ { foo: "bar" }] } };'); @@ -98,23 +201,23 @@ describe('utils/config', function() { return getConfig({ project: project }).then(function(config) { expect(config).to.eql({ scenarios: [ - { name: 'default', bower: { dependencies: {} } }, + { name: 'default', bower: { dependencies: {} }, npm: { devDependencies: { bower: '^1.8.2' } } }, { name: 'ember-beta', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#beta' }, resolutions: { ember: 'beta' } }, - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-canary', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#canary' }, resolutions: { ember: 'canary' } }, - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-2.2.0', bower: { dependencies: { ember: '2.2.0' } }, - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, ], }); @@ -126,24 +229,23 @@ describe('utils/config', function() { return getConfig({ project: project, versionCompatibility: { ember: '1.13.0' } }).then(function(config) { expect(config).to.eql({ scenarios: [ - { name: 'default', bower: { dependencies: {} } }, + { name: 'default', bower: { dependencies: {} }, npm: { devDependencies: { bower: '^1.8.2' } } }, { name: 'ember-beta', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#beta' }, resolutions: { ember: 'beta' } }, - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-canary', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#canary' }, resolutions: { ember: 'canary' } }, - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-1.13.0', bower: { dependencies: { ember: '1.13.0' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { foo: 'bar' }, ], @@ -155,26 +257,23 @@ describe('utils/config', function() { return getConfig({ project: project, versionCompatibility: { ember: '1.13.0' } }).then(function(config) { expect(config).to.eql({ scenarios: [ - { name: 'default', bower: { dependencies: {} } }, + { name: 'default', bower: { dependencies: {} }, npm: { devDependencies: { bower: '^1.8.2' } } }, { name: 'ember-beta', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#beta' }, resolutions: { ember: 'beta' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-canary', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#canary' }, resolutions: { ember: 'canary' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-1.13.0', bower: { dependencies: { ember: '1.13.0' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, ], }); @@ -196,26 +295,27 @@ describe('utils/config', function() { expect(config).to.eql({ bowerOptions: ['--allow-root=true'], scenarios: [ - { name: 'default', bower: { dependencies: {} } }, + { + name: 'default', + bower: { dependencies: {} }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }, { name: 'ember-beta', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#beta' }, resolutions: { ember: 'beta' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-canary', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#canary' }, resolutions: { ember: 'canary' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-2.2.0', bower: { dependencies: { ember: '2.2.0' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, ], }); @@ -230,26 +330,27 @@ describe('utils/config', function() { expect(config.useVersionCompatibility).to.equal(true); expect(config.bowerOptions).to.eql(['--allow-root=true']); expect(config.scenarios).to.eql([ - { name: 'default', bower: { dependencies: {} } }, + { + name: 'default', + bower: { dependencies: {} }, + npm: { devDependencies: { bower: '^1.8.2' } }, + }, { name: 'ember-beta', allowedToFail: false, bower: { dependencies: { ember: 'components/ember#beta' }, resolutions: { ember: 'beta' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-canary', allowedToFail: true, bower: { dependencies: { ember: 'components/ember#canary' }, resolutions: { ember: 'canary' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'ember-2.2.0', bower: { dependencies: { ember: '2.2.0' } }, - - npm: { devDependencies: { 'ember-source': null } }, + npm: { devDependencies: { 'ember-source': null, bower: '^1.8.2' } }, }, { name: 'bar' }, ]); diff --git a/test/utils/dependency-manager-adapter-factory-test.js b/test/utils/dependency-manager-adapter-factory-test.js index 245e9ba9..320d7824 100644 --- a/test/utils/dependency-manager-adapter-factory-test.js +++ b/test/utils/dependency-manager-adapter-factory-test.js @@ -17,7 +17,7 @@ describe('DependencyManagerAdapterFactory', function() { expect(adapters.length).to.equal(1); }); - it('creates both adapters when it has both keys', function() { + it('creates both adapters, with npm first, when it has both keys', function() { var adapters = DependencyManagerAdapterFactory.generateFromConfig({ scenarios: [{ bower: {}, npm: {} }] }, 'here'); expect(adapters[0].configKey).to.equal('npm'); expect(adapters[1].configKey).to.equal('bower'); diff --git a/test/utils/scenario-manager-test.js b/test/utils/scenario-manager-test.js index 21fa8dfb..f8b44f10 100644 --- a/test/utils/scenario-manager-test.js +++ b/test/utils/scenario-manager-test.js @@ -32,7 +32,7 @@ describe('scenarioManager', function() { }); describe('#changeTo', function() { - it('changes dependency sets on each of the managers, and concats results', function() { + it('changes dependency sets on each of the managers, in order, and concats results', function() { var fakeAdapters = [ new CoreObject({ changeToDependencySet: function() {