diff --git a/.eslintignore b/.eslintignore index f34d1e9..44c5bd7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ +./dist .npm cover node_modules diff --git a/.gitignore b/.gitignore index f5fd01c..9a86e9c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.log *.pid *.seed +./dist .DS_Store .idea .node_repl_history @@ -16,4 +17,4 @@ npm-debug.log package pids reports -www +www \ No newline at end of file diff --git a/.yo-rc.json b/.yo-rc.json new file mode 100644 index 0000000..e3ad1e8 --- /dev/null +++ b/.yo-rc.json @@ -0,0 +1,9 @@ +{ + "generator-openstack": { + "srcDir": "./generators", + "distDir": "./dist", + "testDir": "./spec", + "engine": "node", + "language": "es5" + } +} \ No newline at end of file diff --git a/generators/app/index.js b/generators/app/index.js index 68997e8..0db8604 100644 --- a/generators/app/index.js +++ b/generators/app/index.js @@ -9,6 +9,7 @@ var gerrit = require('./lib/component/gerrit'); var editorconfig = require('./lib/component/editorconfig'); var license = require('./lib/component/license'); + var structure = require('./lib/component/structure'); var eslint = require('./lib/component/eslint'); var gitignore = require('./lib/component/gitignore'); var nsp = require('./lib/component/nsp'); @@ -34,6 +35,7 @@ .then(gerrit.init) // Gerrit .then(editorconfig.init) // Editorconfig .then(license.init) // Licensing + .then(structure.init) // Project Structure .then(eslint.init) // Linting .then(gitignore.init) // Gitignore .then(nsp.init) // NSP @@ -51,6 +53,7 @@ .then(gerrit.prompt) // Gerrit .then(editorconfig.prompt) // Editorconfig .then(license.prompt) // Licensing + .then(structure.prompt) // Project Structure .then(eslint.prompt) // Linting .then(gitignore.prompt) // Gitignore .then(nsp.prompt) // NSP @@ -68,6 +71,7 @@ .then(gerrit.configure) // Gerrit .then(editorconfig.configure) // Editorconfig .then(license.configure) // Licensing + .then(structure.configure) // Project Structure .then(eslint.configure) // Linting .then(gitignore.configure) // Gitignore .then(nsp.configure) // NSP diff --git a/generators/app/lib/component/structure.js b/generators/app/lib/component/structure.js new file mode 100644 index 0000000..049cb57 --- /dev/null +++ b/generators/app/lib/component/structure.js @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2016 Hewlett Packard Enterprise Development Company, LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * This generator module handles questions regarding the project's structure, + * such as engine, common output directories, and language level. It informs + * other generators, such as test framework generation, packaging tools, + * and/or configuration files. + */ +(function () { + 'use strict'; + + var Q = require('q'); + var projectBuilder = require('../project_builder'); + + /** + * Initialize the component by setting configuration defaults. These, or previously set + * versions, will be accessible immediately, however it's good practice not to access them + * until after the prompting phase, as we cannot guarantee that they will be properly set. + * + * @param {generator} generator The currently active generator. + * @returns {generator} The passed generator, for promise chaining. + */ + function initialize (generator) { + + // Set our defaults: + generator.config.defaults({ + engine: 'browser', + language: 'es5', + srcDir: './src', + distDir: './dist', + testDir: './test' + }); + + return generator; + } + + /** + * If applicable, prompt the user for a project type. + * + * @param {generator} generator The currently active generator. + * @returns {generator} The passed generator, for promise chaining. + */ + function prompt (generator) { + var deferred = Q.defer(); + + // We default to a node.js project. + if (!generator.options['non-interactive']) { + // Go through the prompts. + generator.prompt( + [{ + type: 'list', + name: 'engine', + message: 'Structure- Runtime Engine:', + choices: [ + { + name: 'Browser', + value: 'browser' + }, + { + name: 'Node.js', + value: 'node' + } + ], + default: generator.config.get('engine') + }, { + type: 'list', + name: 'language', + message: 'Structure- Language:', + choices: [ + { + name: 'ECMAScript 5', + value: 'es5' + }, + { + name: 'ECMAScript 6', + value: 'es6' + } + ], + default: generator.config.get('language') + }, { + type: 'input', + name: 'srcDir', + message: 'Structure- Source Directory:', + default: generator.config.get('srcDir') + }, { + type: 'input', + name: 'testDir', + message: 'Structure- Test Directory:', + default: generator.config.get('testDir') + }, { + type: 'input', + name: 'distDir', + message: 'Structure- Dist Directory:', + default: generator.config.get('distDir'), + when: function (answers) { + return answers.engine === 'browser'; + } + }], + function (answers) { + generator.config.set(answers); + deferred.resolve(generator); + }); + } else { + deferred.resolve(generator); + } + return deferred.promise; + } + + /** + * Add any output directories to the ignore files. + * + * @param {generator} generator The currently active generator. + * @returns {generator} The passed generator, for promise chaining. + */ + function configure (generator) { + projectBuilder.ignoreFile(generator.config.get('distDir')); + return generator; + } + + module.exports = { + init: initialize, + prompt: prompt, + configure: configure + }; +})(); diff --git a/spec/app/lib/component/structure.js b/spec/app/lib/component/structure.js new file mode 100644 index 0000000..56730f5 --- /dev/null +++ b/spec/app/lib/component/structure.js @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2016 Hewlett Packard Enterprise Development Company, LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +(function () { + 'use strict'; + var libDir = '../../../../generators/app/lib'; + + var structure = require(libDir + '/component/structure'); + var projectBuilder = require(libDir + '/project_builder'); + var mocks = require('../../../helpers/mocks'); + var mockGenerator; + + var expectedDefaults = { + engine: 'browser', + language: 'es5', + srcDir: './src', + distDir: './dist', + testDir: './test' + }; + + describe('generator-openstack:lib/component/structure', function () { + + beforeEach(function () { + mockGenerator = mocks.buildGenerator(); + jasmine.clock().install(); + }); + + afterEach(function () { + jasmine.clock().uninstall(); + }); + + it('should define init, prompt, and configure', + function () { + expect(typeof structure.init).toBe('function'); + expect(typeof structure.prompt).toBe('function'); + expect(typeof structure.configure).toBe('function'); + }); + + describe('init()', function () { + it('should return a generator', + function () { + var outputGenerator = structure.init(mockGenerator); + expect(outputGenerator).toEqual(mockGenerator); + }); + + it('should set configuration defaults', + function () { + var spy = spyOn(mockGenerator.config, 'defaults'); + structure.init(mockGenerator); + expect(spy).toHaveBeenCalledWith(expectedDefaults); + }); + }); + + describe('prompt()', function () { + + it('should return a promise that resolves with a generator', + function () { + var generator = mocks.buildGenerator(); + var outputPromise = structure.prompt(generator); + outputPromise.then(function (outputGenerator) { + expect(outputGenerator).toEqual(generator); + }); + }); + + it('should revert to config defaults if no answers provided', + function () { + var config = {}; + var mockAnswers = {}; + var generator = mocks.buildGenerator(config, mockAnswers); + + // Call the component + structure.init(generator); + structure.prompt(generator); + structure.configure(generator); + + Object.keys(expectedDefaults).forEach(function (key) { + expect(generator.config.get(key)).toEqual(expectedDefaults[key]); + }); + }); + + it('should not show a prompt if non-interactive is set', + function () { + var generator = mocks.buildGenerator(null, null, {'non-interactive': true}); + var promptSpy = spyOn(generator, 'prompt'); + + structure.init(generator); + structure.prompt(generator); + + expect(promptSpy.calls.any()).toBeFalsy(); + }); + + it('should configure answers if answers provided', + function () { + var config = {}; + var mockAnswers = { + language: 'es6', + srcDir: './dir', + distDir: './foo', + testDir: './bar' + }; + var generator = mocks.buildGenerator(config, mockAnswers); + + // Set defaults + structure.init(generator); + structure.prompt(generator); + structure.configure(generator); + + Object.keys(mockAnswers).forEach(function (key) { + expect(generator.config.get(key)).toEqual(mockAnswers[key]); + }); + }); + + it('should not configure the dist directory for a node project', + function () { + var config = {}; + var mockAnswers = { + engine: 'node', + distDir: './foo' // This answer should never be read. + }; + var generator = mocks.buildGenerator(config, mockAnswers); + + // Set defaults + structure.init(generator); + structure.prompt(generator); + structure.configure(generator); + + expect(generator.config.get('distDir')).not.toBe(mockAnswers.distDir); + }); + + it('should configure the dist directory for a browser project', + function () { + var config = {}; + var mockAnswers = { + engine: 'browser', + distDir: './foo' // This answer should never be read. + }; + var generator = mocks.buildGenerator(config, mockAnswers); + + // Set defaults + structure.init(generator); + structure.prompt(generator); + structure.configure(generator); + + expect(generator.config.get('distDir')).toBe(mockAnswers.distDir); + }); + }); + + describe('configure()', function () { + it('should return a generator', + function () { + var outputGenerator = structure.configure(mockGenerator); + expect(outputGenerator).toEqual(mockGenerator); + }); + + it('should add the dist directory to the ignoreFile.', + function () { + var ignoreSpy = spyOn(projectBuilder, 'ignoreFile'); + + var generator = mocks.buildGenerator(); + + structure.init(generator); + structure.prompt(generator); + structure.configure(generator); + + expect(ignoreSpy).toHaveBeenCalledWith('./dist'); + }); + }); + }); +})();