diff --git a/src/cli/domain/get-components-deps.js b/src/cli/domain/get-components-deps.js deleted file mode 100644 index d6256b7ad..000000000 --- a/src/cli/domain/get-components-deps.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict'; - -const coreModules = require('builtin-modules'); -const format = require('stringformat'); -const fs = require('fs-extra'); -const path = require('path'); -const _ = require('lodash'); - -const settings = require('../../resources'); - -module.exports = function(components) { - const deps = { modules: {}, withVersions: {}, templates: {} }; - - const legacyTemplates = { - jade: true, - handlebars: true - }; - - components.forEach(componentPath => { - const pkg = fs.readJsonSync(path.join(componentPath, 'package.json')); - const type = pkg.oc.files.template.type; - const dependencies = pkg.dependencies || {}; - const devDependencies = pkg.devDependencies || {}; - const templateCompiler = type + '-compiler'; - - if (!deps.templates[type] && !legacyTemplates[type]) { - if (!devDependencies[templateCompiler]) { - throw new Error( - format(settings.errors.cli.TEMPLATE_DEP_MISSING, type, componentPath) - ); - } - deps.templates[type] = true; - dependencies[templateCompiler] = devDependencies[templateCompiler]; - } - - _.keys(dependencies).forEach(name => { - const version = dependencies[name]; - const depToInstall = version.length > 0 ? `${name}@${version}` : name; - - if (!deps.withVersions[depToInstall]) { - deps.withVersions[depToInstall] = true; - } - - if (!deps.modules[name]) { - deps.modules[name] = true; - } - }); - }); - - return { - modules: _.union(coreModules, _.keys(deps.modules)), - withVersions: _.keys(deps.withVersions), - templates: _.keys(deps.templates) - }; -}; diff --git a/src/cli/domain/get-local-npm-modules.js b/src/cli/domain/get-local-npm-modules.js deleted file mode 100644 index e55f8008e..000000000 --- a/src/cli/domain/get-local-npm-modules.js +++ /dev/null @@ -1,22 +0,0 @@ -'use strict'; - -const fs = require('fs-extra'); -const path = require('path'); - -module.exports = function() { - return function(componentsDir) { - const nodeFolder = path.join(componentsDir, 'node_modules'); - - if (!fs.existsSync(nodeFolder)) { - return []; - } - - return fs.readdirSync(nodeFolder).filter(file => { - const filePath = path.resolve(nodeFolder, file), - isBin = file === '.bin', - isDir = fs.lstatSync(filePath).isDirectory(); - - return isDir && !isBin; - }); - }; -}; diff --git a/src/cli/domain/get-missing-deps.js b/src/cli/domain/get-missing-deps.js deleted file mode 100644 index 9239cf9d5..000000000 --- a/src/cli/domain/get-missing-deps.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -const path = require('path'); -const _ = require('lodash'); - -module.exports = dependencies => { - const missing = []; - - _.forEach(dependencies, npmModule => { - const index = npmModule.indexOf('@'); - let moduleName = npmModule; - - if (index > 0) { - moduleName = npmModule.substr(0, index); - } - - const pathToModule = path.resolve('node_modules/', moduleName); - - try { - if (require.cache[pathToModule]) { - delete require.cache[pathToModule]; - } - - require.resolve(pathToModule); - } catch (e) { - missing.push(npmModule); - } - }); - - return missing; -}; diff --git a/src/cli/domain/handle-dependencies/ensure-compiler-is-declared-as-devDependency.js b/src/cli/domain/handle-dependencies/ensure-compiler-is-declared-as-devDependency.js new file mode 100644 index 000000000..c609bf967 --- /dev/null +++ b/src/cli/domain/handle-dependencies/ensure-compiler-is-declared-as-devDependency.js @@ -0,0 +1,17 @@ +'use strict'; + +const format = require('stringformat'); + +const strings = require('../../../resources'); + +module.exports = (options, cb) => { + const { componentPath, pkg, template } = options; + const compilerDep = `${template}-compiler`; + const isOk = pkg.devDependencies[compilerDep]; + + const err = isOk + ? null + : format(strings.errors.cli.TEMPLATE_DEP_MISSING, template, componentPath); + + cb(err, compilerDep); +}; diff --git a/src/cli/domain/handle-dependencies/get-compiler.js b/src/cli/domain/handle-dependencies/get-compiler.js new file mode 100644 index 000000000..1d9066db1 --- /dev/null +++ b/src/cli/domain/handle-dependencies/get-compiler.js @@ -0,0 +1,31 @@ +'use strict'; + +const path = require('path'); + +const cleanRequire = require('../../../utils/clean-require'); +const installCompiler = require('./install-compiler'); + +module.exports = (options, cb) => { + const { compilerDep, componentPath, logger, pkg } = options; + const compilerPath = path.join(componentPath, 'node_modules', compilerDep); + const compiler = cleanRequire(compilerPath, { justTry: true }); + + if (compiler) { + return cb(null, compiler); + } + + let dependency = compilerDep; + if (pkg.devDependencies[compilerDep]) { + dependency += `@${pkg.devDependencies[compilerDep]}`; + } + + const installOptions = { + compilerPath, + componentName: pkg.name, + componentPath, + dependency, + logger + }; + + installCompiler(installOptions, cb); +}; diff --git a/src/cli/domain/handle-dependencies/get-missing-dependencies.js b/src/cli/domain/handle-dependencies/get-missing-dependencies.js new file mode 100644 index 000000000..769f71578 --- /dev/null +++ b/src/cli/domain/handle-dependencies/get-missing-dependencies.js @@ -0,0 +1,17 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const cleanRequire = require('../../../utils/clean-require'); + +module.exports = dependencies => { + const missing = []; + _.each(dependencies, (version, dependency) => { + const pathToModule = path.resolve('node_modules/', dependency); + if (!cleanRequire(pathToModule, { justTry: true })) { + missing.push(`${dependency}@${version || 'latest'}`); + } + }); + return missing; +}; diff --git a/src/cli/domain/handle-dependencies/index.js b/src/cli/domain/handle-dependencies/index.js new file mode 100644 index 000000000..0a41618ae --- /dev/null +++ b/src/cli/domain/handle-dependencies/index.js @@ -0,0 +1,80 @@ +'use strict'; + +const async = require('async'); +const coreModules = require('builtin-modules'); +const fs = require('fs-extra'); +const path = require('path'); +const _ = require('lodash'); + +const ensureCompilerIsDeclaredAsDevDependency = require('./ensure-compiler-is-declared-as-devDependency'); +const getCompiler = require('./get-compiler'); +const installMissingDependencies = require('./install-missing-dependencies'); +const isTemplateLegacy = require('./is-template-legacy'); +const strings = require('../../../resources'); + +const getComponentPackageJson = (componentPath, cb) => + fs.readJson(path.join(componentPath, 'package.json'), cb); + +module.exports = (options, callback) => { + const { components, logger } = options; + + const dependencies = {}; + const addDependencies = componentDependencies => + _.each(componentDependencies || {}, (version, dependency) => { + dependencies[dependency] = version; + }); + + const templates = {}; + const addTemplate = (templateName, template) => { + templates[templateName] = template; + }; + + const setupComponentDependencies = (componentPath, done) => + async.waterfall( + [ + cb => getComponentPackageJson(componentPath, cb), + (pkg, cb) => { + addDependencies(pkg.dependencies); + + const template = pkg.oc.files.template.type; + if (isTemplateLegacy(template)) { + return done(); + } + + cb(null, { componentPath, logger, pkg, template }); + }, + + (options, cb) => + ensureCompilerIsDeclaredAsDevDependency(options, (err, compilerDep) => + cb(err, _.extend(options, { compilerDep })) + ), + + (options, cb) => + getCompiler(options, (err, compiler) => + cb(err, _.extend(options, { compiler })) + ), + + (options, cb) => { + const { compiler, template } = options; + addTemplate(template, compiler); + cb(); + } + ], + done + ); + + logger.warn(strings.messages.cli.CHECKING_DEPENDENCIES); + async.eachSeries(components, setupComponentDependencies, err => { + if (err) { + return callback(err); + } + + const result = { + modules: _.union(coreModules, _.keys(dependencies)).sort(), + templates: _.values(templates) + }; + + const installOptions = { dependencies, logger }; + installMissingDependencies(installOptions, err => callback(err, result)); + }); +}; diff --git a/src/cli/domain/handle-dependencies/install-compiler.js b/src/cli/domain/handle-dependencies/install-compiler.js new file mode 100644 index 000000000..d2e31eca2 --- /dev/null +++ b/src/cli/domain/handle-dependencies/install-compiler.js @@ -0,0 +1,35 @@ +'use strict'; + +const format = require('stringformat'); + +const cleanRequire = require('../../../utils/clean-require'); +const isTemplateValid = require('../../../utils/is-template-valid'); +const npm = require('../../../utils/npm-utils'); +const strings = require('../../../resources/index'); + +module.exports = (options, cb) => { + const { + compilerPath, + componentName, + componentPath, + dependency, + logger + } = options; + + logger.warn(format(strings.messages.cli.INSTALLING_DEPS, dependency), true); + + const npmOptions = { + dependency, + installPath: componentPath, + save: false, + silent: true + }; + + npm.installDependency(npmOptions, err => { + err ? logger.err('FAIL') : logger.ok('OK'); + const compiler = cleanRequire(compilerPath, { justTry: true }); + const isOk = isTemplateValid(compiler); + const errorMsg = 'There was a problem while installing the compiler'; + cb(!err && isOk ? null : errorMsg, compiler); + }); +}; diff --git a/src/cli/domain/handle-dependencies/install-missing-dependencies.js b/src/cli/domain/handle-dependencies/install-missing-dependencies.js new file mode 100644 index 000000000..bd0a9cbd0 --- /dev/null +++ b/src/cli/domain/handle-dependencies/install-missing-dependencies.js @@ -0,0 +1,40 @@ +'use strict'; + +const format = require('stringformat'); +const path = require('path'); +const _ = require('lodash'); + +const getMissingDependencies = require('./get-missing-dependencies'); +const npm = require('../../../utils/npm-utils'); +const strings = require('../../../resources/index'); + +module.exports = (options, callback) => { + const { dependencies, logger } = options; + + const missing = getMissingDependencies(dependencies); + + if (_.isEmpty(missing)) { + return callback(null); + } + + logger.warn( + format(strings.messages.cli.INSTALLING_DEPS, missing.join(', ')), + true + ); + + const npmOptions = { + dependencies: missing, + installPath: path.resolve('.'), + save: false, + silent: true + }; + + npm.installDependencies(npmOptions, err => { + if (err || !_.isEmpty(getMissingDependencies(dependencies))) { + logger.fail('FAIL'); + return callback(strings.errors.cli.DEPENDENCIES_INSTALL_FAIL); + } + logger.ok('OK'); + callback(null); + }); +}; diff --git a/src/cli/domain/handle-dependencies/is-template-legacy.js b/src/cli/domain/handle-dependencies/is-template-legacy.js new file mode 100644 index 000000000..f6a611e18 --- /dev/null +++ b/src/cli/domain/handle-dependencies/is-template-legacy.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = template => !!{ jade: true, handlebars: true }[template]; diff --git a/src/cli/domain/handle-dependencies/require-template.js b/src/cli/domain/handle-dependencies/require-template.js new file mode 100644 index 000000000..9b25a103d --- /dev/null +++ b/src/cli/domain/handle-dependencies/require-template.js @@ -0,0 +1,51 @@ +'use strict'; + +const format = require('stringformat'); +const path = require('path'); + +const cleanRequire = require('../../../utils/clean-require'); +const isTemplateLegacy = require('./is-template-legacy'); +const isTemplateValid = require('../../../utils/is-template-valid'); +const strings = require('../../../resources'); + +module.exports = function(template, options) { + const requireOptions = options || {}; + let ocTemplate; + + if (isTemplateLegacy(template)) { + template = `oc-template-${template}`; + } + + if (requireOptions.compiler) { + template = `${template}-compiler`; + } + + const localTemplate = path.join(__dirname, '../../node_modules', template); + const relativeTemplate = path.resolve('.', 'node_modules', template); + const componentRelativePath = path.join( + requireOptions.componentPath, + 'node_modules', + template + ); + + [ + componentRelativePath, + template, + localTemplate, + relativeTemplate + ].forEach(pathToTry => { + ocTemplate = ocTemplate || cleanRequire(pathToTry, { justTry: true }); + }); + + if (!ocTemplate) { + throw new Error(format(strings.errors.cli.TEMPLATE_NOT_FOUND, template)); + } + + if (!isTemplateValid(ocTemplate, requireOptions)) { + throw new Error( + format(strings.errors.cli.TEMPLATE_TYPE_NOT_VALID, template) + ); + } + + return ocTemplate; +}; diff --git a/src/cli/domain/init-template/create-component-dir.js b/src/cli/domain/init-template/create-component-dir.js deleted file mode 100644 index 95facd497..000000000 --- a/src/cli/domain/init-template/create-component-dir.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const fs = require('fs-extra'); - -module.exports = function({ componentPath }) { - fs.ensureDirSync(componentPath); -}; diff --git a/src/cli/domain/init-template/index.js b/src/cli/domain/init-template/index.js index ec632e5e2..0abcf8dfc 100644 --- a/src/cli/domain/init-template/index.js +++ b/src/cli/domain/init-template/index.js @@ -1,61 +1,26 @@ 'use strict'; +const async = require('async'); const fs = require('fs-extra'); const path = require('path'); const _ = require('lodash'); -const createComponentDir = require('./create-component-dir'); -const initPackage = require('./init-package'); const installTemplate = require('./install-template'); +const npm = require('../../../utils/npm-utils'); const scaffold = require('./scaffold'); module.exports = function(options, callback) { - const { - compiler, - componentName, - componentPath, - logger, - templateType - } = options; + const { compiler, componentPath } = options; + const compilerPath = path.join(componentPath, 'node_modules', compiler); + const npmOptions = { initPath: componentPath, silent: true }; - createComponentDir({ componentPath }); - - const compilerInstalledOnDevRegistryPath = path.join( - process.cwd(), - 'node_modules', - compiler - ); - const compilerInstalledOnComponentPath = path.join( - componentPath, - 'node_modules', - compiler + async.series( + [ + cb => fs.ensureDir(componentPath, cb), + cb => npm.init(npmOptions, cb), + cb => installTemplate(options, cb), + cb => scaffold(_.extend(options, { compilerPath }), cb) + ], + callback ); - fs.stat(compilerInstalledOnDevRegistryPath, (err, stats) => { - if (err) { - initPackage({ componentPath }); - return installTemplate(options, (err, done) => { - if (err) { - return callback(err); - } - return scaffold( - _.extend(options, { compilerPath: compilerInstalledOnComponentPath }), - (err, done) => { - if (err) { - return callback(err); - } - return callback(null, done); - } - ); - }); - } - return scaffold( - _.extend(options, { compilerPath: compilerInstalledOnDevRegistryPath }), - (err, done) => { - if (err) { - return callback(err); - } - return callback(null, done); - } - ); - }); }; diff --git a/src/cli/domain/init-template/init-package.js b/src/cli/domain/init-template/init-package.js deleted file mode 100644 index 5ac63474b..000000000 --- a/src/cli/domain/init-template/init-package.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const spawn = require('cross-spawn'); - -module.exports = function({ componentPath }) { - spawn.sync('npm', ['init', '--yes'], { cwd: componentPath }); -}; diff --git a/src/cli/domain/init-template/install-template.js b/src/cli/domain/init-template/install-template.js index 85887da33..9ee1a73e3 100644 --- a/src/cli/domain/init-template/install-template.js +++ b/src/cli/domain/init-template/install-template.js @@ -2,18 +2,12 @@ const tryRequire = require('try-require'); -const isValidTemplate = require('../../../utils/isValidTemplate'); +const isTemplateValid = require('../../../utils/is-template-valid'); const npm = require('../../../utils/npm-utils'); const strings = require('../../../resources'); module.exports = function installTemplate(options, callback) { - const { - compiler, - componentName, - componentPath, - logger, - templateType - } = options; + const { compiler, componentPath, logger, templateType } = options; const npmOptions = { dependency: compiler, @@ -32,7 +26,7 @@ module.exports = function installTemplate(options, callback) { const installedCompiler = tryRequire(result.dest); - if (!isValidTemplate(installedCompiler, { compiler: true })) { + if (!isTemplateValid(installedCompiler, { compiler: true })) { return callback(errorMessage); } const version = installedCompiler.getInfo().version; diff --git a/src/cli/domain/init-template/scaffold.js b/src/cli/domain/init-template/scaffold.js index 406e957cd..6c7cdc0ca 100644 --- a/src/cli/domain/init-template/scaffold.js +++ b/src/cli/domain/init-template/scaffold.js @@ -11,7 +11,6 @@ module.exports = function scaffold(options, callback) { compilerPath, componentName, componentPath, - logger, templateType } = options; diff --git a/src/cli/domain/load-dependencies.js b/src/cli/domain/load-dependencies.js deleted file mode 100644 index fbf9aef4f..000000000 --- a/src/cli/domain/load-dependencies.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -const format = require('stringformat'); -const path = require('path'); -const _ = require('lodash'); - -const getComponentsDependencies = require('./get-components-deps'); -const getMissingDeps = require('./get-missing-deps'); -const npm = require('../../utils/npm-utils'); -const strings = require('../../resources/index'); - -module.exports = function loadDependencies({ components, logger }, cb) { - logger.warn(strings.messages.cli.CHECKING_DEPENDENCIES, true); - let dependencies; - try { - dependencies = getComponentsDependencies(components); - } catch (err) { - return logger.err(err); - } - - const missing = getMissingDeps(dependencies.withVersions, components); - - if (_.isEmpty(missing)) { - logger.ok('OK'); - return cb(dependencies); - } - - logger.err('FAIL'); - installMissingDeps({ missing, logger }, () => { - loadDependencies({ components, logger }, cb); - }); -}; - -function installMissingDeps({ missing, logger }, cb) { - if (_.isEmpty(missing)) { - return cb(); - } - - logger.warn(format(strings.messages.cli.INSTALLING_DEPS, missing.join(', '))); - - const options = { - dependencies: missing, - installPath: path.resolve('.'), - save: false - }; - - npm.installDependencies(options, err => { - if (err) { - logger.err(err.toString()); - throw err; - } - cb(); - }); -} diff --git a/src/cli/domain/local.js b/src/cli/domain/local.js index 0aebcb214..b40996604 100644 --- a/src/cli/domain/local.js +++ b/src/cli/domain/local.js @@ -6,7 +6,6 @@ const targz = require('targz'); const _ = require('lodash'); const getComponentsByDir = require('./get-components-by-dir'); -const getLocalNpmModules = require('./get-local-npm-modules'); const packageComponents = require('./package-components'); const mock = require('./mock'); const validator = require('../../registry/domain/validators'); @@ -35,7 +34,6 @@ module.exports = function() { ); }, getComponentsByDir: getComponentsByDir(), - getLocalNpmModules: getLocalNpmModules(), init: function(options, callback) { let { componentName, templateType, logger } = options; if (!validator.validateComponentName(componentName)) { diff --git a/src/cli/domain/package-components.js b/src/cli/domain/package-components.js index adf94a988..d3e94857a 100644 --- a/src/cli/domain/package-components.js +++ b/src/cli/domain/package-components.js @@ -3,7 +3,8 @@ const fs = require('fs-extra'); const path = require('path'); const _ = require('lodash'); -const requireTemplate = require('../../utils/require-template'); + +const requireTemplate = require('./handle-dependencies/require-template'); const validator = require('../../registry/domain/validators'); module.exports = function() { @@ -13,7 +14,6 @@ module.exports = function() { const minify = options.minify === true; const verbose = options.verbose === true; const publishPath = path.join(componentPath, '_package'); - const componentPackagePath = path.join(componentPath, 'package.json'); const ocPackagePath = path.join(__dirname, '../../../package.json'); @@ -26,7 +26,6 @@ module.exports = function() { fs.emptyDirSync(publishPath); const componentPackage = fs.readJsonSync(componentPackagePath); - const ocPackage = fs.readJsonSync(ocPackagePath); if (!validator.validateComponentName(componentPackage.name)) { @@ -47,13 +46,11 @@ module.exports = function() { }; try { - const ocTemplate = requireTemplate(type, { compiler: true }); - ocTemplate.compile(compileOptions, (err, info) => { - if (err) { - return callback(err); - } - return callback(null, info); + const ocTemplate = requireTemplate(type, { + compiler: true, + componentPath }); + ocTemplate.compile(compileOptions, callback); } catch (err) { return callback(err); } diff --git a/src/cli/facade/dev.js b/src/cli/facade/dev.js index e5fbe56f9..2d07ce592 100644 --- a/src/cli/facade/dev.js +++ b/src/cli/facade/dev.js @@ -8,10 +8,9 @@ const path = require('path'); const _ = require('lodash'); const getMockedPlugins = require('../domain/get-mocked-plugins'); -const loadDependencies = require('../domain/load-dependencies'); +const handleDependencies = require('../domain/handle-dependencies'); const oc = require('../../index'); const strings = require('../../resources/index'); -const requireTemplate = require('../../utils/require-template'); const watch = require('../domain/watch'); const wrapCliCallback = require('../wrap-cli-callback'); @@ -44,9 +43,7 @@ module.exports = function(dependencies) { if (!hotReloading) { logger.warn(strings.messages.cli.HOT_RELOADING_DISABLED); } else { - cb([componentDir], done => { - liveReloadServer.refresh('/'); - }); + cb([componentDir], done => liveReloadServer.refresh('/')); } } }); @@ -136,22 +133,26 @@ module.exports = function(dependencies) { logger.log(colors.green('├── ') + component); }); - loadDependencies({ components, logger }, dependencies => { + handleDependencies({ components, logger }, (err, dependencies) => { + if (err) { + logger.err(err); + return callback(err); + } packageComponents(components, () => { const liveReloadServer = livereload.createServer({ port: port + 1 }); const registry = new oc.Registry({ - local: true, - hotReloading: hotReloading, - fallbackRegistryUrl: fallbackRegistryUrl, + baseUrl, + dependencies: dependencies.modules, discovery: true, - verbosity: 1, - path: path.resolve(componentsDir), - port: port, - baseUrl: baseUrl, env: { name: 'local' }, - dependencies: dependencies.modules, - templates: dependencies.templates.map(requireTemplate) + fallbackRegistryUrl, + hotReloading, + local: true, + path: path.resolve(componentsDir), + port, + templates: dependencies.templates, + verbosity: 1 }); registerPlugins(registry); diff --git a/src/cli/facade/init.js b/src/cli/facade/init.js index 16625b22c..014d3aa6d 100644 --- a/src/cli/facade/init.js +++ b/src/cli/facade/init.js @@ -35,7 +35,7 @@ module.exports = function(dependencies) { } if (err === 'template type not valid') { - err = errors.TEMPLATE_TYPE_NOT_VALID; + err = format(errors.TEMPLATE_TYPE_NOT_VALID, templateType); } logger.err(format(errors.INIT_FAIL, err)); } else { diff --git a/src/cli/facade/publish.js b/src/cli/facade/publish.js index 93b723d59..abae12beb 100644 --- a/src/cli/facade/publish.js +++ b/src/cli/facade/publish.js @@ -7,7 +7,7 @@ const path = require('path'); const read = require('read'); const _ = require('lodash'); -const loadDependencies = require('../domain/load-dependencies'); +const handleDependencies = require('../domain/handle-dependencies'); const strings = require('../../resources/index'); const wrapCliCallback = require('../wrap-cli-callback'); @@ -135,9 +135,13 @@ module.exports = function(dependencies) { return callback(err); } - loadDependencies( - { components: [componentPath], logger }, - dependencies => { + handleDependencies( + { components: [path.resolve(componentPath)], logger }, + (err, dependencies) => { + if (err) { + logger.err(err); + return callback(err); + } packageAndCompress((err, component) => { if (err) { errorMessage = format( diff --git a/src/resources/index.js b/src/resources/index.js index bec796068..959640a19 100644 --- a/src/resources/index.js +++ b/src/resources/index.js @@ -95,6 +95,8 @@ module.exports = { COMPONENT_HREF_NOT_FOUND: "The specified path is not a valid component's url", COMPONENTS_NOT_FOUND: 'no components found in specified path', + DEPENDENCIES_INSTALL_FAIL: + 'An error happened when installing the dependencies', FOLDER_IS_NOT_A_FOLDER: '"{0}" must be a directory', FOLDER_NOT_FOUND: '"{0}" not found', DEV_FAIL: 'An error happened when initialising the dev runner: {0}', @@ -121,8 +123,9 @@ module.exports = { 'oc registries not found. Run "oc registry add "', SERVERJS_DEPENDENCY_NOT_DECLARED: 'Missing dependencies from package.json => {0}', - TEMPLATE_NOT_FOUND: 'file {0} not found', - TEMPLATE_TYPE_NOT_VALID: 'the template is not valid', + TEMPLATE_NOT_FOUND: 'Error requiring oc-template: "{0}" not found', + TEMPLATE_TYPE_NOT_VALID: + 'Error requiring oc-template: "{0}" is not a valid oc-template', TEMPLATE_DEP_MISSING: 'Template dependency missing. To fix it run:\n\nnpm install --save-dev {0}-compiler --prefix {1}\n\n' }, @@ -174,7 +177,7 @@ Happy coding HOT_RELOADING_DISABLED: 'OC dev is running with hot reloading disabled so changes will be ignored', INSTALLING_DEPS: - "Trying to install missing modules: {0}\nIf you aren't connected to the internet, or npm isn't configured then this step will fail", + "Trying to install missing modules: {0}\nIf you aren't connected to the internet, or npm isn't configured then this step will fail...", MOCKED_PLUGIN: 'Mock for plugin has been registered: {0} () => {1}', NO_SUCH_COMMAND: "No such command '{0}'", NOT_VALID_REGISTRY_COMMAND: diff --git a/src/utils/clean-require.js b/src/utils/clean-require.js new file mode 100644 index 000000000..8dc21f653 --- /dev/null +++ b/src/utils/clean-require.js @@ -0,0 +1,11 @@ +'use strict'; + +const tryRequire = require('try-require'); + +module.exports = (path, { justTry }) => { + const shouldThrow = !justTry; + if (require.cache && !!require.cache[path]) { + delete require.cache[path]; + } + return shouldThrow ? require(path) : tryRequire(path); +}; diff --git a/src/utils/is-template-valid.js b/src/utils/is-template-valid.js new file mode 100644 index 000000000..659969ad7 --- /dev/null +++ b/src/utils/is-template-valid.js @@ -0,0 +1,17 @@ +'use strict'; + +const _ = require('lodash'); + +module.exports = function isTemplateValid(template, options) { + if (!_.isObject(template)) { + return false; + } + + const api = ['getInfo', 'getCompiledTemplate', 'render']; + + if (options && options.compiler === true) { + api.push('compile'); + } + + return api.every(method => _.isFunction(template[method])); +}; diff --git a/src/utils/isValidTemplate.js b/src/utils/isValidTemplate.js deleted file mode 100644 index 507c3a22e..000000000 --- a/src/utils/isValidTemplate.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict'; - -module.exports = function isValidTemplate(template, options) { - if (typeof template !== 'object') { - return false; - } - - let api = ['getInfo', 'getCompiledTemplate', 'render']; - - if (options.compiler === true) { - api = api.concat('compile'); - } - - return api.every(method => typeof template[method] === 'function'); -}; diff --git a/src/utils/npm-utils.js b/src/utils/npm-utils.js index 966480f56..01674cfb4 100644 --- a/src/utils/npm-utils.js +++ b/src/utils/npm-utils.js @@ -15,9 +15,9 @@ const buildInstallCommand = options => { }; const executeCommand = (options, callback) => { - const cmd = spawn('npm', options.command, { - cwd: options.installPath, - stdio: 'inherit' + const cmd = spawn('npm', [...options.command, '--no-package-lock'], { + cwd: options.path, + stdio: options.silent ? 'ignore' : 'inherit' }); cmd.on('error', () => callback('error')); @@ -26,43 +26,42 @@ const executeCommand = (options, callback) => { const moduleName = dependency => dependency.split('@')[0]; +const getFullPath = ({ installPath, dependency }) => + path.join(installPath, 'node_modules', moduleName(dependency)); + module.exports = { + init: (options, callback) => { + const { initPath, silent } = options; + const npminit = ['init', '--yes']; + const cmdOptions = { path: initPath, command: npminit, silent }; + + executeCommand(cmdOptions, callback); + }, installDependencies: (options, callback) => { - const { dependencies, installPath } = options; + const { dependencies, installPath, silent } = options; const npmi = buildInstallCommand(options); - const cmdOptions = { installPath, command: [...npmi, ...dependencies] }; + const cmdOptions = { + command: [...npmi, ...dependencies], + path: installPath, + silent + }; - executeCommand(cmdOptions, err => - callback( - err, - err - ? null - : { - dest: dependencies.map(dependency => - path.join(installPath, 'node_modules', moduleName(dependency)) - ) - } - ) + const dest = dependencies.map(dependency => + getFullPath({ installPath, dependency }) ); + + executeCommand(cmdOptions, err => callback(err, err ? null : { dest })); }, installDependency: (options, callback) => { - const { dependency, installPath } = options; + const { dependency, installPath, silent } = options; const npmi = buildInstallCommand(options); - const cmdOptions = { installPath, command: [...npmi, dependency] }; + const cmdOptions = { + command: [...npmi, dependency], + path: installPath, + silent + }; + const dest = getFullPath({ installPath, dependency }); - executeCommand(cmdOptions, err => - callback( - err, - err - ? null - : { - dest: path.join( - installPath, - 'node_modules', - moduleName(dependency) - ) - } - ) - ); + executeCommand(cmdOptions, err => callback(err, err ? null : { dest })); } }; diff --git a/src/utils/require-template.js b/src/utils/require-template.js deleted file mode 100644 index a1c2dfdb2..000000000 --- a/src/utils/require-template.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; - -const format = require('stringformat'); -const path = require('path'); - -const isValidTemplate = require('./isValidTemplate'); - -const templateNotFound = 'Error requiring oc-template: "{0}" not found'; -const templateNotValid = - 'Error requiring oc-template: "{0}" is not a valid oc-template'; - -const getOcTemplate = path => { - if (require.cache && !!require.cache[path]) { - delete require.cache[path]; - } - return require(path); -}; - -module.exports = function(template, options) { - const requireOptions = options || {}; - let ocTemplate; - if (template === 'jade') { - template = 'oc-template-jade'; - } - if (template === 'handlebars') { - template = 'oc-template-handlebars'; - } - if (requireOptions.compiler === true) { - template = template + '-compiler'; - } - - const localTemplate = path.join( - __dirname, - '../../', - 'node_modules', - template - ); - const relativeTemplate = path.resolve('.', 'node_modules', template); - - try { - ocTemplate = getOcTemplate(template); - } catch (err) { - try { - ocTemplate = getOcTemplate(localTemplate); - } catch (err) { - try { - ocTemplate = getOcTemplate(relativeTemplate); - } catch (err) { - throw new Error(format(templateNotFound, template)); - } - } - } - - if (!isValidTemplate(ocTemplate, requireOptions)) { - throw new Error(format(templateNotValid, template)); - } - - return ocTemplate; -}; diff --git a/test/unit/cli-domain-get-components-deps.js b/test/unit/cli-domain-get-components-deps.js deleted file mode 100644 index 4bf788bef..000000000 --- a/test/unit/cli-domain-get-components-deps.js +++ /dev/null @@ -1,132 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const injectr = require('injectr'); -const sinon = require('sinon'); - -describe('cli : domain : get-components-deps', () => { - let error, result; - const execute = readJsonStub => { - const getComponentsDeps = injectr( - '../../src/cli/domain/get-components-deps.js', - { - 'builtin-modules': ['fs', 'url'], - 'fs-extra': { - readJsonSync: readJsonStub - }, - path: { - join: (a, b) => `${a}/${b}` - } - } - ); - - try { - result = getComponentsDeps([ - '/path/to/components/component1', - '/path/to/components/component2' - ]); - } catch (e) { - error = e; - } - }; - - const baseOcFragment = templateType => ({ - files: { template: { type: templateType } } - }); - - describe('happy path', () => { - before(() => { - const readJsonStub = sinon.stub(); - - readJsonStub.onFirstCall().returns({ - oc: baseOcFragment('jade'), - dependencies: { lodash: '' } - }); - - readJsonStub.onSecondCall().returns({ - oc: baseOcFragment('handlebars'), - dependencies: { underscore: '' } - }); - - execute(readJsonStub); - }); - - it('should return core dependencies + package depenndencies', () => { - expect(result.modules.sort()).to.eql([ - 'fs', - 'lodash', - 'underscore', - 'url' - ]); - }); - - it('should return just package dependencies with versions', () => { - expect(result.withVersions).to.eql(['lodash', 'underscore']); - }); - - it('should return no templates when using legacy templates', () => { - expect(result.templates).to.eql([]); - }); - }); - - describe('custom templates and package dependencies with versions', () => { - before(() => { - const readJsonStub = sinon.stub(); - - readJsonStub.onFirstCall().returns({ - oc: baseOcFragment('oc-template-react'), - devDependencies: { - 'oc-template-react-compiler': '1.2.3', - prettier: '1.5.3' - } - }); - - readJsonStub.onSecondCall().returns({ - oc: baseOcFragment('handlebars'), - dependencies: { underscore: '5.6.7' }, - devDependencies: { prettier: '1.5.3' } - }); - - execute(readJsonStub); - }); - - it('should return core dependencies + package depenendencies', () => { - expect(result.modules.sort()).to.eql( - ['fs', 'underscore', 'url', 'oc-template-react-compiler'].sort() - ); - }); - - it('should return package dependencies with versions', () => { - expect(result.withVersions.sort()).to.eql( - ['underscore@5.6.7', 'oc-template-react-compiler@1.2.3'].sort() - ); - }); - - it('should return the custom templates', () => { - expect(result.templates).to.eql(['oc-template-react']); - }); - }); - - describe('when custom template not correctly referenced in the package.json', () => { - before(() => { - const readJsonStub = sinon.stub(); - - readJsonStub.onFirstCall().returns({ - oc: baseOcFragment('oc-template-handlebars') - }); - - readJsonStub.onSecondCall().returns({ - oc: baseOcFragment('handlebars'), - dependencies: { underscore: '5.6.7' } - }); - - execute(readJsonStub); - }); - - it('should error', () => { - expect(error.toString()).to.include( - 'Error: Template dependency missing. To fix it run:\n\nnpm install --save-dev oc-template-handlebars-compiler --prefix /path/to/components/component1\n\n' - ); - }); - }); -}); diff --git a/test/unit/cli-domain-get-local-npm-modules.js b/test/unit/cli-domain-get-local-npm-modules.js deleted file mode 100644 index 96db6232a..000000000 --- a/test/unit/cli-domain-get-local-npm-modules.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const injectr = require('injectr'); -const path = require('path'); -const sinon = require('sinon'); -const _ = require('lodash'); - -const initialise = function(){ - - const fsMock = { - existsSync: sinon.stub(), - lstatSync: sinon.stub(), - mkdirSync: sinon.spy(), - readdirSync: sinon.stub(), - readFileSync: sinon.stub(), - readJson: sinon.stub(), - readJsonSync: sinon.stub(), - writeFile: sinon.stub().yields(null, 'ok'), - writeJson: sinon.stub().yields(null, 'ok') - }; - - const pathMock = { - extname: path.extname, - join: path.join, - resolve: function(){ - return _.toArray(arguments).join('/'); - } - }; - - const GetLocalNpmModules = injectr('../../src/cli/domain/get-local-npm-modules.js', { - 'fs-extra': fsMock, - path: pathMock - }, { __dirname: '' }); - - const local = new GetLocalNpmModules(); - - return { local: local, fs: fsMock }; -}; - -const executeGetLocalNpmModules = function(local){ - return local('.'); -}; - -describe('cli : domain : get-local-npm-modules', () => { - - describe('when reading modules from dir', () => { - - let result; - beforeEach(() => { - - const data = initialise(); - - data.fs.readdirSync.onCall(0).returns([ - 'a-module', - 'a-file.json', - 'another-module' - ]); - - data.fs.existsSync.onCall(0).returns(true); - - data.fs.lstatSync.onCall(0).returns({ isDirectory: function(){ return true; }}); - data.fs.lstatSync.onCall(1).returns({ isDirectory: function(){ return false; }}); - data.fs.lstatSync.onCall(2).returns({ isDirectory: function(){ return true; }}); - - result = executeGetLocalNpmModules(data.local); - }); - - it('should return only the folders', () => { - expect(result).to.eql(['a-module', 'another-module']); - }); - }); - - describe('when node_modules directory doesn\'t exist', () => { - - let result; - beforeEach(() => { - - const data = initialise(); - - data.fs.existsSync.onCall(0).returns(false); - - result = executeGetLocalNpmModules(data.local); - }); - - it('should return an empty array', () => { - expect(result).to.eql([]); - }); - }); -}); diff --git a/test/unit/cli-domain-handle-dependencies-ensure-compiler-is-declared-as-devDependency.js b/test/unit/cli-domain-handle-dependencies-ensure-compiler-is-declared-as-devDependency.js new file mode 100644 index 000000000..dc9d942ea --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-ensure-compiler-is-declared-as-devDependency.js @@ -0,0 +1,62 @@ +'use strict'; + +const expect = require('chai').expect; + +describe('cli : domain : handle-dependencies : ensure-compiler-is-declared-as-devDependency', () => { + const ensure = require('../../src/cli/domain/handle-dependencies/ensure-compiler-is-declared-as-devDependency'); + describe('when compiler is declared as devDependency', () => { + let error, result; + beforeEach(done => { + ensure( + { + componentPath: '/path/to/component/', + pkg: { + devDependencies: { + 'oc-template-react-compiler': '1.x.x' + } + }, + template: 'oc-template-react' + }, + (err, compilerDep) => { + error = err; + result = compilerDep; + done(); + } + ); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should return the compiler dependency', () => { + expect(result).to.equal('oc-template-react-compiler'); + }); + }); + + describe('when compiler is not declared as devDependency', () => { + let error, result; + beforeEach(done => { + ensure( + { + componentPath: '/path/to/component/', + pkg: { + devDependencies: {} + }, + template: 'oc-template-react' + }, + (err, compilerDep) => { + error = err; + result = compilerDep; + done(); + } + ); + }); + + it('should return the error', () => { + expect(error).to.contain( + 'Template dependency missing. To fix it run:\n\nnpm install --save-dev oc-template-react-compiler --prefix /path/to/component/' + ); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies-get-compiler.js b/test/unit/cli-domain-handle-dependencies-get-compiler.js new file mode 100644 index 000000000..cbb63086c --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-get-compiler.js @@ -0,0 +1,123 @@ +'use strict'; + +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); +const _ = require('lodash'); + +describe('cli : domain : handle-dependencies : get-compiler', () => { + let cleanRequireStub, error, installCompilerStub, result; + const execute = (opts, done) => { + done = done || opts; + const compilerVersion = opts.compilerVersionEmpty ? '' : '1.2.3'; + const options = { + compilerDep: 'oc-template-handlebars-compiler', + componentPath: '/path/to/component', + logger: {}, + pkg: { + name: 'my-component', + devDependencies: { + 'oc-template-handlebars-compiler': compilerVersion + } + } + }; + + const getCompiler = injectr( + '../../src/cli/domain/handle-dependencies/get-compiler.js', + { + path: { join: (...args) => args.join('/') }, + '../../../utils/clean-require': cleanRequireStub, + './install-compiler': installCompilerStub + } + ); + + getCompiler(_.cloneDeep(options), (err, res) => { + error = err; + result = res; + done(); + }); + }; + + describe("when compiler is already installed inside the component's folder", () => { + beforeEach(done => { + installCompilerStub = sinon.stub().yields(null, { ok: true }); + cleanRequireStub = sinon.stub().returns({ thisIsACompiler: true }); + execute(done); + }); + + it("should try to require it from the component's path", () => { + expect(cleanRequireStub.args[0][0]).to.equal( + '/path/to/component/node_modules/oc-template-handlebars-compiler' + ); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should not try to install it', () => { + expect(installCompilerStub.called).to.be.false; + }); + }); + + describe("when compiler is not installed inside the component's folder", () => { + describe('when compiler version is specified', () => { + beforeEach(done => { + installCompilerStub = sinon.stub().yields(null, { ok: true }); + cleanRequireStub = sinon.stub().returns(undefined); + + execute(done); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should install it', () => { + expect(installCompilerStub.args[0][0]).to.deep.equal({ + compilerPath: + '/path/to/component/node_modules/oc-template-handlebars-compiler', + componentName: 'my-component', + componentPath: '/path/to/component', + dependency: 'oc-template-handlebars-compiler@1.2.3', + logger: {} + }); + }); + }); + + describe('when compiler version is not specified', () => { + beforeEach(done => { + installCompilerStub = sinon.stub().yields(null, { ok: true }); + cleanRequireStub = sinon.stub().returns(undefined); + execute({ compilerVersionEmpty: true }, done); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should install it', () => { + expect(installCompilerStub.args[0][0]).to.deep.equal({ + compilerPath: + '/path/to/component/node_modules/oc-template-handlebars-compiler', + componentName: 'my-component', + componentPath: '/path/to/component', + dependency: 'oc-template-handlebars-compiler', + logger: {} + }); + }); + }); + + describe('when install fails', () => { + beforeEach(done => { + installCompilerStub = sinon.stub().yields('Install failed!'); + cleanRequireStub = sinon.stub().returns(undefined); + execute(done); + }); + + it('should return the error', () => { + expect(error).to.be.equal('Install failed!'); + }); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies-get-missing-dependencies.js b/test/unit/cli-domain-handle-dependencies-get-missing-dependencies.js new file mode 100644 index 000000000..b2199f8e3 --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-get-missing-dependencies.js @@ -0,0 +1,59 @@ +'use strict'; + +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); +const _ = require('lodash'); + +describe('cli : domain : handle-dependencies - get-missing-dependencies', () => { + const scenarios = [ + { + dependencies: { lodash: '1.2.3', underscore: '' }, + installed: { lodash: true, underscore: false }, + output: ['underscore@latest'] + }, + { + dependencies: { lodash: '1.2.3', underscore: '4.5.6' }, + installed: { lodash: true, underscore: false }, + output: ['underscore@4.5.6'] + }, + { + dependencies: { lodash: '1.2.3', underscore: '' }, + installed: { lodash: true, underscore: true }, + output: [] + } + ]; + + scenarios.forEach(scenario => { + const { dependencies, installed, output } = scenario; + describe(`When dependencies: ${JSON.stringify( + dependencies + )} and installed: ${JSON.stringify(installed)}`, () => { + const pathResolveSpy = sinon.spy(); + const getMissingDependencies = injectr( + '../../src/cli/domain/handle-dependencies/get-missing-dependencies.js', + { + '../../../utils/clean-require': x => + installed[x] ? { dependency: true } : undefined, + path: { + resolve: (...args) => { + pathResolveSpy(...args); + return args[args.length - 1]; + } + } + } + ); + + it(`should output ${JSON.stringify(output)}`, () => { + expect(getMissingDependencies(dependencies)).to.deep.equal(output); + }); + + it('should resolve the dependency relative to where the oc cli is running', () => { + pathResolveSpy.args.forEach((pathResolveCall, i) => { + expect(pathResolveCall[0]).to.equal('node_modules/'); + expect(pathResolveCall[1]).to.equal(_.keys(dependencies)[i]); + }); + }); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies-install-compiler.js b/test/unit/cli-domain-handle-dependencies-install-compiler.js new file mode 100644 index 000000000..1d4b898ef --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-install-compiler.js @@ -0,0 +1,117 @@ +'use strict'; + +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); + +describe('cli : domain : handle-dependencies : install-compiler', () => { + let cleanRequireStub, installDependencyMock, isTemplateValidStub, loggerMock; + let error, result; + + const initialise = (options, done) => { + cleanRequireStub = sinon.stub().returns({ theCompiler: true }); + installDependencyMock = sinon + .stub() + .yields(options.shouldInstallFail ? 'install error' : null); + isTemplateValidStub = sinon.stub().returns(!options.shouldValidationFail); + loggerMock = { + err: sinon.stub(), + ok: sinon.stub(), + warn: sinon.stub() + }; + + const installOptions = { + compilerPath: '/path/to/components/component/node_modules/', + componentName: 'component', + componentPath: '/path/to/components/component/', + dependency: 'oc-template-react-compiler@1.2.3', + logger: loggerMock + }; + + const installCompiler = injectr( + '../../src/cli/domain/handle-dependencies/install-compiler.js', + { + '../../../utils/clean-require': cleanRequireStub, + '../../../utils/is-template-valid': isTemplateValidStub, + '../../../utils/npm-utils': { installDependency: installDependencyMock } + } + ); + + installCompiler(installOptions, (err, compiler) => { + error = err; + result = compiler; + done(); + }); + }; + + describe('when succeeds', () => { + beforeEach(done => initialise({}, done)); + + it('should run npm install with correct parameters', () => { + expect(installDependencyMock.args[0][0]).to.deep.equal({ + dependency: 'oc-template-react-compiler@1.2.3', + installPath: '/path/to/components/component/', + save: false, + silent: true + }); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should return the compiler', () => { + expect(result).to.deep.equal({ theCompiler: true }); + }); + + it('should log progress', () => { + expect(loggerMock.warn.args[0][0]).to.contain( + `Trying to install missing modules: oc-template-react-compiler@1.2.3` + ); + expect(loggerMock.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(loggerMock.ok.args[0][0]).to.equal('OK'); + }); + }); + + describe('when install fails', () => { + beforeEach(done => initialise({ shouldInstallFail: true }, done)); + + it('should return an error', () => { + expect(error).to.be.equal( + 'There was a problem while installing the compiler' + ); + }); + + it('should log progress', () => { + expect(loggerMock.warn.args[0][0]).to.contain( + `Trying to install missing modules: oc-template-react-compiler@1.2.3` + ); + expect(loggerMock.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(loggerMock.err.args[0][0]).to.equal('FAIL'); + }); + }); + + describe('when install succeeds but validation fails', () => { + beforeEach(done => initialise({ shouldValidationFail: true }, done)); + + it('should return an error', () => { + expect(error).to.be.equal( + 'There was a problem while installing the compiler' + ); + }); + + it('should log progress', () => { + expect(loggerMock.warn.args[0][0]).to.contain( + `Trying to install missing modules: oc-template-react-compiler@1.2.3` + ); + expect(loggerMock.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(loggerMock.ok.args[0][0]).to.equal('OK'); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies-install-missing-dependencies.js b/test/unit/cli-domain-handle-dependencies-install-missing-dependencies.js new file mode 100644 index 000000000..3e02028d2 --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-install-missing-dependencies.js @@ -0,0 +1,166 @@ +'use strict'; + +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); + +describe('cli : domain : handle-dependencies : install-missing-dependencies', () => { + let error, logger; + const initialise = (options, done) => { + const { dependencies, stubs } = options; + logger = { + fail: sinon.spy(), + ok: sinon.spy(), + warn: sinon.spy() + }; + const installMissingDependencies = injectr( + '../../src/cli/domain/handle-dependencies/install-missing-dependencies.js', + { + './get-missing-dependencies': stubs.getMissingDependencies, + '../../../utils/npm-utils': { + installDependencies: stubs.installDependencies + }, + path: { resolve: () => '/path/to/oc-running' } + } + ); + + const installOptions = { dependencies, logger }; + installMissingDependencies(installOptions, err => { + error = err; + done(); + }); + }; + + describe('when there is no missing dependency', () => { + let dependencies, stubs; + beforeEach(done => { + stubs = { + getMissingDependencies: sinon.stub().returns([]), + installDependencies: sinon.stub().yields(null) + }; + + dependencies = { lodash: '1.2.3' }; + initialise({ dependencies, stubs }, done); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should not install anything', () => { + expect(stubs.installDependencies.called).to.be.false; + }); + }); + + describe('when there are missing dependencies and install succeeds', () => { + let dependencies, stubs; + + beforeEach(done => { + stubs = { + getMissingDependencies: sinon.stub(), + installDependencies: sinon.stub().yields(null) + }; + + stubs.getMissingDependencies + .onCall(0) + .returns(['lodash@1.2.3', 'underscore@latest']); + stubs.getMissingDependencies.onCall(1).returns([]); + + dependencies = { lodash: '1.2.3', underscore: '' }; + initialise({ dependencies, stubs }, done); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should install the missing dependencies', () => { + expect(stubs.installDependencies.args[0][0]).to.deep.equal({ + dependencies: ['lodash@1.2.3', 'underscore@latest'], + installPath: '/path/to/oc-running', + save: false, + silent: true + }); + }); + + it('should log progress', () => { + expect(logger.warn.args[0][0]).to.contain( + 'Trying to install missing modules: lodash@1.2.3, underscore@latest' + ); + expect(logger.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(logger.ok.args[0][0]).to.equal('OK'); + }); + }); + + describe('when there are missing dependencies and install fails', () => { + let dependencies, stubs; + + beforeEach(done => { + stubs = { + getMissingDependencies: sinon.stub(), + installDependencies: sinon.stub().yields('got an error') + }; + + stubs.getMissingDependencies + .onCall(0) + .returns(['lodash@1.2.3', 'underscore@latest']); + stubs.getMissingDependencies.onCall(1).returns([]); + + dependencies = { lodash: '1.2.3', underscore: '' }; + initialise({ dependencies, stubs }, done); + }); + + it('should return the error', () => { + expect(error).to.equal( + 'An error happened when installing the dependencies' + ); + }); + + it('should log progress', () => { + expect(logger.warn.args[0][0]).to.contain( + 'Trying to install missing modules: lodash@1.2.3, underscore@latest' + ); + expect(logger.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(logger.fail.args[0][0]).to.equal('FAIL'); + }); + }); + + describe('when there are missing dependencies and install succeeds but the dependencies are still not requireable', () => { + let dependencies, stubs; + + beforeEach(done => { + stubs = { + getMissingDependencies: sinon.stub(), + installDependencies: sinon.stub().yields(null) + }; + + stubs.getMissingDependencies.returns([ + 'lodash@1.2.3', + 'underscore@latest' + ]); + + dependencies = { lodash: '1.2.3', underscore: '' }; + initialise({ dependencies, stubs }, done); + }); + + it('should return the error', () => { + expect(error).to.equal( + 'An error happened when installing the dependencies' + ); + }); + + it('should log progress', () => { + expect(logger.warn.args[0][0]).to.contain( + 'Trying to install missing modules: lodash@1.2.3, underscore@latest' + ); + expect(logger.warn.args[0][0]).to.contain( + "If you aren't connected to the internet, or npm isn't configured then this step will fail..." + ); + expect(logger.fail.args[0][0]).to.equal('FAIL'); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies-require-template.js b/test/unit/cli-domain-handle-dependencies-require-template.js new file mode 100644 index 000000000..ee614ebf6 --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies-require-template.js @@ -0,0 +1,195 @@ +'use strict'; + +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); +const _ = require('lodash'); + +describe('cli : domain : handle-dependencies : require-template', () => { + const isTemplateValid = sinon.stub().returns(true); + + let result, error; + const execute = options => { + error = null; + result = null; + const requireTemplate = injectr( + '../../src/cli/domain/handle-dependencies/require-template.js', + { + '../../../utils/clean-require': options.requireMock, + '../../../utils/is-template-valid': + options.isTemplateValidMock || sinon.stub().returns(true), + path: { + join: (...args) => args.join('/').replace(/\/\//gi, '/'), + resolve: (...args) => + ['path/to/oc-cli'].concat(args.slice(1)).join('/') + } + }, + { + __dirname: '__dirname' + } + ); + try { + result = requireTemplate(options.template, options.options); + } catch (e) { + error = e; + } + }; + + describe('when requiring template', () => { + describe('when module not found', () => { + const requireMock = sinon.stub().returns(undefined); + beforeEach(() => + execute({ + requireMock, + template: 'oc-template-jade', + options: { componentPath: '/path/to/component' } + }) + ); + + it('should try requiring from component folder first', () => { + expect(requireMock.args[0][0]).to.equal( + '/path/to/component/node_modules/oc-template-jade' + ); + }); + + it('should try requiring it as absolute as second attempt', () => { + expect(requireMock.args[1][0]).to.equal('oc-template-jade'); + }); + + it('should try requiring it relatively to the oc runtime as third attempt', () => { + expect(requireMock.args[2][0]).to.equal( + '__dirname/../../node_modules/oc-template-jade' + ); + }); + + it('should try requiring it relatively to the oc cli as fourth attempt', () => { + expect(requireMock.args[3][0]).to.equal( + 'path/to/oc-cli/node_modules/oc-template-jade' + ); + }); + + it('should then throw an exeption', () => { + expect(error.toString()).to.contain( + 'Error requiring oc-template: "oc-template-jade" not found' + ); + }); + }); + + describe('when template is legacy (jade)', () => { + const requireMock = sinon.stub().returns({ + thisIsAValidTemplate: true + }); + beforeEach(() => + execute({ + requireMock, + template: 'jade', + options: { componentPath: '/path/to/component' } + }) + ); + + it('should require the oc-template-jade instead', () => { + expect(requireMock.args[0][0]).to.equal( + '/path/to/component/node_modules/oc-template-jade' + ); + }); + + it('should return the template', () => { + expect(result).to.deep.equal({ + thisIsAValidTemplate: true + }); + }); + }); + + describe('when template is legacy (handlebars)', () => { + const requireMock = sinon.stub().returns({ + thisIsAValidTemplate: true + }); + beforeEach(() => + execute({ + requireMock, + template: 'handlebars', + options: { componentPath: '/path/to/component' } + }) + ); + + it('should require the oc-template-handlebars instead', () => { + expect(requireMock.args[0][0]).to.equal( + '/path/to/component/node_modules/oc-template-handlebars' + ); + }); + + it('should return the template', () => { + expect(result).to.deep.equal({ + thisIsAValidTemplate: true + }); + }); + }); + + describe('when requiring a compiler (oc-template-react)', () => { + const requireMock = sinon.stub().returns({ + thisIsAValidTemplate: true + }); + beforeEach(() => + execute({ + requireMock, + template: 'oc-template-react', + options: { componentPath: '/path/to/component', compiler: true } + }) + ); + + it('should require the oc-template-react-compiler', () => { + expect(requireMock.args[0][0]).to.equal( + '/path/to/component/node_modules/oc-template-react-compiler' + ); + }); + + it('should return the template', () => { + expect(result).to.deep.equal({ + thisIsAValidTemplate: true + }); + }); + }); + + describe("when module found in the component's folder", () => { + describe('when the template is valid', () => { + const requireMock = sinon.stub().returns({ + thisIsAValidTemplate: true + }); + beforeEach(() => + execute({ + requireMock, + template: 'oc-template-jade', + options: { componentPath: '/path/to/component' } + }) + ); + + it('should return the template', () => { + expect(result).to.deep.equal({ + thisIsAValidTemplate: true + }); + }); + }); + + describe('when the template is not valid', () => { + const requireMock = sinon.stub().returns({ + thisIsAValidTemplate: true + }); + const isTemplateValidMock = sinon.stub().returns(false); + beforeEach(() => + execute({ + requireMock, + isTemplateValidMock, + template: 'oc-template-jade', + options: { componentPath: '/path/to/component' } + }) + ); + + it('should throw an error', () => { + expect(error.toString()).to.contain( + 'Error requiring oc-template: "oc-template-jade" is not a valid oc-template' + ); + }); + }); + }); + }); +}); diff --git a/test/unit/cli-domain-handle-dependencies.js b/test/unit/cli-domain-handle-dependencies.js new file mode 100644 index 000000000..148fe8df2 --- /dev/null +++ b/test/unit/cli-domain-handle-dependencies.js @@ -0,0 +1,144 @@ +'use strict'; + +const coreModules = require('builtin-modules'); +const expect = require('chai').expect; +const injectr = require('injectr'); +const sinon = require('sinon'); +const _ = require('lodash'); + +describe('cli : domain : handle-dependencies', () => { + describe('happy path', () => { + const components = { + '/path/to/components/handlebars-legacy': { + name: 'handlebars-legacy', + oc: { files: { template: { type: 'handlebars' } } }, + dependencies: { lodash: '1.2.3' } + }, + '/path/to/components/handlebars': { + name: 'handlebars', + oc: { files: { template: { type: 'oc-template-handlebars' } } }, + devDependencies: { 'oc-template-handlebars-compiler': '1.x.x' } + }, + '/path/to/components/jade-legacy': { + name: 'jade-legacy', + oc: { files: { template: { type: 'jade' } } }, + dependencies: { moment: '2.x.x' } + }, + '/path/to/components/jade': { + name: 'jade', + oc: { files: { template: { type: 'oc-template-jade' } } }, + devDependencies: { 'oc-template-jade-compiler': '1.x.x' } + }, + '/path/to/components/react': { + name: 'react', + oc: { files: { template: { type: 'oc-template-react' } } }, + devDependencies: { 'oc-template-react-compiler': 'x.x.x' } + } + }; + + let logger, spies; + let error, result; + beforeEach(done => { + spies = { + ensureCompilerIsDeclaredAsDevDependency: sinon.spy(), + getCompiler: sinon.spy(), + installMissingDependencies: sinon.spy() + }; + + const handleDependencies = injectr( + '../../src/cli/domain/handle-dependencies/index.js', + { + 'fs-extra': { + readJson: (path, cb) => + cb(null, components[path.replace('/package.json', '')]) + }, + path: { join: (...args) => args.join('/') }, + './ensure-compiler-is-declared-as-devDependency': (options, cb) => { + spies.ensureCompilerIsDeclaredAsDevDependency(options); + cb(null, `${options.template}-compiler`); + }, + './get-compiler': (options, cb) => { + spies.getCompiler(options); + cb(null, { thisIsACompiler: true }); + }, + './install-missing-dependencies': (options, cb) => { + spies.installMissingDependencies(options); + cb(null); + } + } + ); + + logger = { + fail: sinon.spy(), + ok: sinon.spy(), + warn: sinon.spy() + }; + + handleDependencies( + { components: _.keys(components), logger }, + (err, res) => { + error = err; + result = res; + done(); + } + ); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should return modules plus the node.js core modules', () => { + expect(result.modules).to.deep.equal( + _.union(coreModules, ['lodash', 'moment']).sort() + ); + }); + + it('should return templates', () => { + expect(result.templates.length).to.equal(3); + expect(result.templates).to.deep.equal([ + { thisIsACompiler: true }, + { thisIsACompiler: true }, + { thisIsACompiler: true } + ]); + }); + + it('should log progress', () => { + expect(logger.warn.args[0][0]).to.equal( + 'Ensuring dependencies are loaded...' + ); + }); + + it('should make sure compilers are declared as devDependencies', () => { + const args = spies.ensureCompilerIsDeclaredAsDevDependency.args; + expect(args.length).to.equal(3); + expect(args[0][0].pkg).to.deep.equal( + components['/path/to/components/handlebars'] + ); + expect(args[1][0].pkg).to.deep.equal( + components['/path/to/components/jade'] + ); + expect(args[2][0].pkg).to.deep.equal( + components['/path/to/components/react'] + ); + }); + + it('should fetch the compilers', () => { + const args = spies.getCompiler.args; + expect(args.length).to.equal(3); + expect(args[0][0].compilerDep).to.equal( + 'oc-template-handlebars-compiler' + ); + expect(args[1][0].compilerDep).to.equal('oc-template-jade-compiler'); + expect(args[2][0].compilerDep).to.equal('oc-template-react-compiler'); + }); + + it('should install the dependencies if missing', () => { + const args = spies.installMissingDependencies.args; + expect(args[0][0].dependencies).to.deep.equal({ + lodash: '1.2.3', + moment: '2.x.x' + }); + }); + }); +}); diff --git a/test/unit/cli-domain-init-template--createComponentDir.js b/test/unit/cli-domain-init-template--createComponentDir.js deleted file mode 100644 index ecd4c12e8..000000000 --- a/test/unit/cli-domain-init-template--createComponentDir.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const sinon = require('sinon'); -const injectr = require('injectr'); - -const deps = { - 'fs-extra': { - ensureDirSync: sinon.spy() - } -}; - -const createComponentDir = injectr( - '../../src/cli/domain/init-template/create-component-dir.js', - deps, - {} -); - -describe('cli : domain : init-template createComponentDir', () => { - describe('when invoked', () => { - const config = { - componentPath: 'path/to/component', - packageName: 'myComponent', - logger: { - log: sinon.spy() - } - }; - - createComponentDir(config); - - it('should correctly invoke ensureDirSync', () => { - expect( - deps['fs-extra'].ensureDirSync.calledWith('path/to/component') - ).to.equal(true); - }); - }); -}); diff --git a/test/unit/cli-domain-init-template--initPackage.js b/test/unit/cli-domain-init-template--initPackage.js deleted file mode 100644 index 1755206f7..000000000 --- a/test/unit/cli-domain-init-template--initPackage.js +++ /dev/null @@ -1,35 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const sinon = require('sinon'); -const injectr = require('injectr'); - -const deps = { - 'cross-spawn': { - sync: sinon.spy() - } -}; - -const initPackage = injectr( - '../../src/cli/domain/init-template/init-package.js', - deps, - {} -); - -describe('cli : domain : init-template initPackage', () => { - describe('when invoked', () => { - const options = { - componentPath: 'path/to/component' - }; - - initPackage(options); - - it('should spawn the right process', () => { - expect( - deps['cross-spawn'].sync.calledWith('npm', ['init', '--yes'], { - cwd: 'path/to/component' - }) - ).to.equal(true); - }); - }); -}); diff --git a/test/unit/cli-domain-init-template--installTemplate.js b/test/unit/cli-domain-init-template-install-template.js similarity index 85% rename from test/unit/cli-domain-init-template--installTemplate.js rename to test/unit/cli-domain-init-template-install-template.js index 0011b67de..7f9a5c5cf 100644 --- a/test/unit/cli-domain-init-template--installTemplate.js +++ b/test/unit/cli-domain-init-template-install-template.js @@ -4,14 +4,14 @@ const expect = require('chai').expect; const sinon = require('sinon'); const injectr = require('injectr'); -describe('cli : domain : init-template installTemplate', () => { +describe('cli : domain : init-template : install-template', () => { const npmUtils = { installDependency: sinon.stub() }; - const isValidTemplate = sinon.stub(); + const isTemplateValid = sinon.stub(); const installTemplate = injectr( '../../src/cli/domain/init-template/install-template.js', { '../../../utils/npm-utils': npmUtils, - '../../../utils/isValidTemplate': isValidTemplate, + '../../../utils/is-template-valid': isTemplateValid, 'try-require': sinon.stub().returns({ getInfo: () => ({ version: '1.2.3' }) }) @@ -32,7 +32,7 @@ describe('cli : domain : init-template installTemplate', () => { const dest = 'path/to/component/node_modules/oc-template-jade-compiler'; npmUtils.installDependency.reset(); npmUtils.installDependency.yields(null, { dest }); - isValidTemplate.returns(true); + isTemplateValid.returns(true); installTemplate(config, (err, res) => { error = err; result = res; @@ -50,8 +50,8 @@ describe('cli : domain : init-template installTemplate', () => { }); it('should validate the template', () => { - expect(isValidTemplate.called).to.be.true; - expect(typeof isValidTemplate.args[0][0].getInfo).to.equal('function'); + expect(isTemplateValid.called).to.be.true; + expect(typeof isTemplateValid.args[0][0].getInfo).to.equal('function'); }); it('should return no error', () => { diff --git a/test/unit/cli-domain-init-template.js b/test/unit/cli-domain-init-template.js index f80849059..66cd5d6ad 100644 --- a/test/unit/cli-domain-init-template.js +++ b/test/unit/cli-domain-init-template.js @@ -5,153 +5,138 @@ const sinon = require('sinon'); const injectr = require('injectr'); describe('cli : domain : init-template', () => { - describe('when invoking init-template', () => { - describe('when the template is available in the dev registry', () => { - const deps = { - './scaffold': sinon.stub(), - './install-template': sinon.stub().returnsArg(1), - './create-component-dir': sinon.spy(), - './init-package': sinon.spy(), - path: { - join: sinon.stub().onFirstCall().returnsArg(2) - }, + let error; + const initialise = stubs => cb => { + const initTemplate = injectr( + '../../src/cli/domain/init-template/index.js', + { 'fs-extra': { - stat: (path, cb) => cb(null, true) - } - }; - - const globals = { - process: { - cwd: () => '' - } - }; - - const initTemplate = injectr( - '../../src/cli/domain/init-template/index.js', - deps, - globals - ); + ensureDir: stubs.fsExtraStub + }, + path: { join: (...args) => args.join('/') }, + './install-template': stubs.installTemplateStub, + '../../../utils/npm-utils': { + init: stubs.npmStub + }, + './scaffold': stubs.scaffoldStub + } + ); - const options = { - compiler: 'oc-template-jade-compiler', - componentPath: 'path/to/myJadeComponent', - componentName: 'myJadeComponent', - templateType: 'oc-template-jade', - logger: { - log: sinon.spy() - } - }; - - const result = initTemplate(options, () => {}); - - it('should correctly call createComponentDir', () => { - expect(deps['./create-component-dir'].calledOnce).to.equal(true); - expect(deps['./create-component-dir'].args[0][0]).to.deep.equal({ - componentPath: 'path/to/myJadeComponent' - }); - }); - it('should correctly call scaffold', () => { - expect(deps['./scaffold'].calledOnce).to.equal(true); - expect(deps['./scaffold'].args[0][0]).to.deep.equal({ - compiler: 'oc-template-jade-compiler', - compilerPath: 'oc-template-jade-compiler', - logger: options.logger, - componentPath: 'path/to/myJadeComponent', - componentName: 'myJadeComponent', - templateType: 'oc-template-jade' - }); - }); - it('should not call initPackage', () => { - expect(deps['./init-package'].notCalled).to.equal(true); - }); - it('should not call installTemplate', () => { - expect(deps['./install-template'].notCalled).to.equal(true); - }); + const options = { + compiler: 'oc-template-react-compiler', + componentName: 'new-component', + componentPath: 'path/to/new-component', + logger: { log: () => {} }, + templateType: 'oc-template-react' + }; + + initTemplate(options, err => { + error = err; + cb(); }); + }; - describe('when the template is not available in the dev registry', () => { - const installTemplate = (options, cb) => { - cb(null, 'foo'); - }; - const scaffold = sinon.spy(); - const deps = { - './scaffold': scaffold, - './install-template': sinon.spy(installTemplate), - './create-component-dir': sinon.spy(), - './init-package': sinon.spy(), - './utils': { getPackageName: sinon.stub().returnsArg(0) }, - path: { - join: sinon - .stub() - .onFirstCall() - .returnsArg(2) - .onSecondCall() - .returnsArg(0) - }, - 'fs-extra': { - stat: (path, cb) => cb(true) - } - }; + describe('happy path', () => { + const fsExtraStub = sinon.stub().yields(null, 'ok'); + const npmStub = sinon.stub().yields(null, 'ok'); + const installTemplateStub = sinon.stub().yields(null, 'ok'); + const scaffoldStub = sinon.stub().yields(null, 'ok'); - const globals = { - process: { - cwd: () => '' - }, - require: sinon.stub().returns(true) - }; + beforeEach( + initialise({ + fsExtraStub, + npmStub, + installTemplateStub, + scaffoldStub + }) + ); - const initTemplate = injectr( - '../../src/cli/domain/init-template/index.js', - deps, - globals - ); + it('should return no error', () => { + expect(error).to.be.null; + }); - const options = { - compiler: 'oc-template-hipster-compiler', - componentName: 'supaComp', - componentPath: 'path/to/supaComp', - templateType: 'oc-template-hipster', - logger: { - log: sinon.spy() - } - }; - - const result = initTemplate(options, () => {}); - - it('should correctly call createComponentDir', () => { - expect(deps['./create-component-dir'].calledOnce).to.equal(true); - expect(deps['./create-component-dir'].args[0][0]).to.deep.equal({ - componentPath: 'path/to/supaComp' - }); - }); - it('should correctly call initPackage', () => { - expect(deps['./init-package'].calledOnce).to.equal(true); - expect(deps['./init-package'].args[0][0].componentPath).to.equal( - 'path/to/supaComp' - ); - }); - it('should correctly call installTemplate', () => { - expect(deps['./install-template'].called).to.equal(true); - expect(deps['./install-template'].args[0][0]).to.deep.equal({ - compiler: 'oc-template-hipster-compiler', - compilerPath: 'path/to/supaComp', - logger: options.logger, - componentPath: 'path/to/supaComp', - componentName: 'supaComp', - templateType: 'oc-template-hipster' - }); - }); - it('should call scaffold on installTemplate success', () => { - expect(deps['./scaffold'].calledOnce).to.equal(true); - expect(deps['./scaffold'].args[0][0]).to.deep.equal({ - compiler: 'oc-template-hipster-compiler', - compilerPath: 'path/to/supaComp', - logger: options.logger, - componentPath: 'path/to/supaComp', - componentName: 'supaComp', - templateType: 'oc-template-hipster' - }); + it('should call ensureDir with correct params', () => { + expect(fsExtraStub.args[0][0]).to.equal('path/to/new-component'); + }); + + it('should call npm init with correct parameters', () => { + expect(npmStub.args[0][0]).to.deep.equal({ + initPath: 'path/to/new-component', + silent: true }); }); + + it('should call installTemplate with correct parameters', () => { + const o = installTemplateStub.args[0][0]; + expect(o.compiler).to.equal('oc-template-react-compiler'); + expect(o.componentPath).to.equal('path/to/new-component'); + expect(o.logger.log).to.be.a('function'); + expect(o.templateType).to.equal('oc-template-react'); + }); + + it('should call scaffold with correct parameters', () => { + const o = scaffoldStub.args[0][0]; + expect(o.compiler).to.equal('oc-template-react-compiler'); + expect(o.compilerPath).to.equal( + 'path/to/new-component/node_modules/oc-template-react-compiler' + ); + expect(o.componentName).to.equal('new-component'); + expect(o.componentPath).to.equal('path/to/new-component'); + expect(o.templateType).to.equal('oc-template-react'); + }); + }); + + describe('when component folder creation fails', () => { + beforeEach( + initialise({ + fsExtraStub: sinon.stub().yields('folder creation error') + }) + ); + + it('should return an error', () => { + expect(error).to.equal('folder creation error'); + }); + }); + + describe('when npm init fails', () => { + beforeEach( + initialise({ + fsExtraStub: sinon.stub().yields(null, 'ok'), + npmStub: sinon.stub().yields('npm init failed') + }) + ); + + it('should return an error', () => { + expect(error).to.equal('npm init failed'); + }); + }); + + describe('when template compiler installation fails', () => { + beforeEach( + initialise({ + fsExtraStub: sinon.stub().yields(null, 'ok'), + npmStub: sinon.stub().yields(null, 'ok'), + installTemplateStub: sinon.stub().yields('npm install failed') + }) + ); + + it('should return an error', () => { + expect(error).to.equal('npm install failed'); + }); + }); + + describe('when template compiler installation fails', () => { + beforeEach( + initialise({ + fsExtraStub: sinon.stub().yields(null, 'ok'), + npmStub: sinon.stub().yields(null, 'ok'), + installTemplateStub: sinon.stub().yields(null, 'ok'), + scaffoldStub: sinon.stub().yields('scaffolding failed') + }) + ); + + it('should return an error', () => { + expect(error).to.equal('scaffolding failed'); + }); }); }); diff --git a/test/unit/cli-domain-package-components.js b/test/unit/cli-domain-package-components.js index 23e3dcac5..d6a096f9d 100644 --- a/test/unit/cli-domain-package-components.js +++ b/test/unit/cli-domain-package-components.js @@ -54,7 +54,7 @@ describe('cli : domain : package-components', () => { { 'fs-extra': fsMock, path: pathMock, - '../../utils/require-template': requireTemplateMock + './handle-dependencies/require-template': requireTemplateMock }, { __dirname: '' } ); diff --git a/test/unit/cli-facade-init.js b/test/unit/cli-facade-init.js index 80ca40f17..13ba6d2df 100644 --- a/test/unit/cli-facade-init.js +++ b/test/unit/cli-facade-init.js @@ -59,7 +59,7 @@ describe('cli : facade : init', () => { it('should show an error', () => { const expected = - 'An error happened when initialising the component: the template is not valid'; + 'An error happened when initialising the component: Error requiring oc-template: "invalid-type" is not a valid oc-template'; expect(logSpy.err.args[0][0]).to.equal(expected); }); }); diff --git a/test/unit/cli-facade-publish.js b/test/unit/cli-facade-publish.js index a8ff22b9a..8833c25e7 100644 --- a/test/unit/cli-facade-publish.js +++ b/test/unit/cli-facade-publish.js @@ -17,8 +17,8 @@ describe('cli : facade : publish', () => { read: readStub }), publishFacade = new PublishFacade({ - registry: registry, - local: local, + registry, + local, logger: logSpy }); @@ -157,8 +157,8 @@ describe('cli : facade : publish', () => { execute(() => { registry.putComponent.restore(); - expect(logSpy.ok.args[1][0]).to.include('http://www.api.com'); - expect(logSpy.ok.args[2][0]).to.include( + expect(logSpy.ok.args[0][0]).to.include('http://www.api.com'); + expect(logSpy.ok.args[1][0]).to.include( 'http://www.api2.com' ); done(); @@ -289,7 +289,7 @@ describe('cli : facade : publish', () => { }); it('should not prompt for credentials', () => { - expect(logSpy.ok.args[1][0]).to.equal( + expect(logSpy.ok.args[0][0]).to.equal( 'Using specified credentials' ); }); @@ -309,8 +309,8 @@ describe('cli : facade : publish', () => { }); it('should show a message', () => { - expect(logSpy.ok.args[1][0]).to.include('Published -> '); - expect(logSpy.ok.args[1][0]).to.include( + expect(logSpy.ok.args[0][0]).to.include('Published -> '); + expect(logSpy.ok.args[0][0]).to.include( 'http://www.api.com/hello-world/1.0.0' ); }); diff --git a/test/unit/utils-npm-utils.js b/test/unit/utils-npm-utils.js index 5e1963372..cef3f45d4 100644 --- a/test/unit/utils-npm-utils.js +++ b/test/unit/utils-npm-utils.js @@ -13,6 +13,54 @@ describe('utils : npm-utils', () => { }); const installPath = 'path/to/component'; + const initPath = installPath; + + describe('init()', () => { + const scenarios = [ + { + input: { initPath }, + output: ['init', '--yes', '--no-package-lock'], + cmdCall: { cwd: initPath, stdio: 'inherit' } + }, + { + input: { initPath, silent: true }, + output: ['init', '--yes', '--no-package-lock'], + cmdCall: { cwd: initPath, stdio: 'ignore' } + } + ]; + + scenarios.forEach(scenario => { + const { initPath, silent } = scenario.input; + describe(`when invoked for ${initPath} with silent=${silent}`, () => { + let error, onStub; + beforeEach(done => { + onStub = sinon.stub(); + crossSpawnStub.reset(); + crossSpawnStub.returns({ on: onStub }); + npmUtils.init(scenario.input, (err, res) => { + error = err; + done(); + }); + onStub.args[1][1](0); + }); + + it('should spawn the process with correct parameters', () => { + expect(crossSpawnStub.args[0][0]).to.equal('npm'); + expect(crossSpawnStub.args[0][1]).to.deep.equal(scenario.output); + expect(crossSpawnStub.args[0][2]).to.deep.equal(scenario.cmdCall); + }); + + it('should return no error', () => { + expect(error).to.be.null; + }); + + it('should correctly setup on error and on close listeners', () => { + expect(onStub.args[0][0]).to.equal('error'); + expect(onStub.args[1][0]).to.equal('close'); + }); + }); + }); + }); describe('installDependency()', () => { const scenarios = [ @@ -29,7 +77,8 @@ describe('utils : npm-utils', () => { 'path/to/component', '--save-exact', '--save-dev', - 'oc-template-jade-compiler' + 'oc-template-jade-compiler', + '--no-package-lock' ] }, { @@ -39,7 +88,13 @@ describe('utils : npm-utils', () => { isDev: true, save: false }, - output: ['install', '--prefix', 'path/to/component', 'lodash'] + output: [ + 'install', + '--prefix', + 'path/to/component', + 'lodash', + '--no-package-lock' + ] }, { input: { @@ -54,12 +109,19 @@ describe('utils : npm-utils', () => { 'path/to/component', '--save-exact', '--save', - 'underscore' + 'underscore', + '--no-package-lock' ] }, { input: { dependency: 'oc-client@~1.2.3', installPath, save: false }, - output: ['install', '--prefix', 'path/to/component', 'oc-client@~1.2.3'] + output: [ + 'install', + '--prefix', + 'path/to/component', + 'oc-client@~1.2.3', + '--no-package-lock' + ] } ]; @@ -82,6 +144,10 @@ describe('utils : npm-utils', () => { it('should spawn the process with correct parameters', () => { expect(crossSpawnStub.args[0][0]).to.equal('npm'); expect(crossSpawnStub.args[0][1]).to.deep.equal(scenario.output); + expect(crossSpawnStub.args[0][2]).to.deep.equal({ + cwd: installPath, + stdio: 'inherit' + }); }); it('should return no error', () => { @@ -118,7 +184,8 @@ describe('utils : npm-utils', () => { '--save-exact', '--save-dev', 'oc-template-jade-compiler', - 'lodash' + 'lodash', + '--no-package-lock' ] }, { @@ -128,7 +195,14 @@ describe('utils : npm-utils', () => { isDev: true, save: false }, - output: ['install', '--prefix', 'path/to/component', 'moment', 'lodash'] + output: [ + 'install', + '--prefix', + 'path/to/component', + 'moment', + 'lodash', + '--no-package-lock' + ] }, { input: { @@ -144,7 +218,8 @@ describe('utils : npm-utils', () => { '--save-exact', '--save', 'underscore', - 'oc-client' + 'oc-client', + '--no-package-lock' ] }, { @@ -158,7 +233,8 @@ describe('utils : npm-utils', () => { '--prefix', 'path/to/component', 'oc-client@~1.2.3', - 'oc-template-react-compiler' + 'oc-template-react-compiler', + '--no-package-lock' ] } ]; @@ -184,6 +260,10 @@ describe('utils : npm-utils', () => { it('should spawn the process with correct parameters', () => { expect(crossSpawnStub.args[0][0]).to.equal('npm'); expect(crossSpawnStub.args[0][1]).to.deep.equal(scenario.output); + expect(crossSpawnStub.args[0][2]).to.deep.equal({ + cwd: installPath, + stdio: 'inherit' + }); }); it('should return no error', () => { diff --git a/test/unit/utils-require-templates.js b/test/unit/utils-require-templates.js deleted file mode 100644 index 935e03c44..000000000 --- a/test/unit/utils-require-templates.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; - -const requireTemplate = require('../../src/utils/require-template'); - -describe('utils : require-template', () => { - it('expect type of `requireTemplate` to be function', () => { - expect(typeof requireTemplate).to.equal('function'); - }); - - describe('valid', () => { - const scenarios = [ - { name: 'oc-template-handlebars', compiler: true }, - { name: 'oc-template-jade', compiler: false }, - { name: 'oc-template-jade' } - ]; - - scenarios.forEach(scenario => { - it(scenario.name, () => { - const template = requireTemplate(scenario.name, { - compiler: scenario.compiler - }); - let api = ['getCompiledTemplate', 'getInfo', 'render']; - if (scenario.compiler) { - api = api.concat('compile'); - } - - api.forEach(method => { - expect(template).to.have.property(method); - }); - }); - }); - }); - - describe('not valid', () => { - const scenarios = [{ name: 'lodash' }, { name: 'oc-invalid-template' }]; - - scenarios.forEach(scenario => { - it(scenario.name, () => { - expect(() => requireTemplate(scenario.name)).to.throw(); - }); - }); - }); -});