├── .gitignore ├── LICENSE ├── README.md ├── demo.gif ├── generators ├── app │ ├── index.js │ └── option-parser.js ├── appveyor │ ├── index.js │ └── templates │ │ └── _appveyor.yml ├── cli │ ├── index.js │ └── templates │ │ ├── _commander.js │ │ ├── _meow.js │ │ ├── _minimist.js │ │ └── _yargs.js ├── conditional-subgen.js ├── git │ └── index.js ├── github │ └── index.js ├── gulp │ ├── index.js │ └── templates │ │ ├── _.babelrc │ │ ├── _gulpfile.babel.js │ │ ├── _gulpfile.js │ │ └── tasks │ │ └── build.js ├── npm │ ├── deep-sort-object.js │ ├── guess-author.js │ ├── index.js │ ├── normal-or-empty-url.js │ ├── param-case-name.js │ └── templates │ │ ├── _.gitignore │ │ ├── _index.js │ │ └── license │ │ ├── bsd2 │ │ ├── bsd3 │ │ └── mit ├── readme │ ├── index.js │ ├── strip-scope.js │ └── templates │ │ └── _readme.md └── travis │ ├── index.js │ └── templates │ └── _.travis.yml ├── package.json └── test ├── fixtures └── npm │ ├── index-cjs.js │ └── index-esm.js ├── gulp.js ├── npm.js └── util ├── index.js └── runner.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | .idea 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 ironSource 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-nom 2 | 3 | **A modular [Yeoman](http://yeoman.io) generator to create or update node modules. It's composed of several subgenerators, relatively unopinionated, usable by themselves. Most but not all input values are remembered.** 4 | 5 | [![npm status](http://img.shields.io/npm/v/generator-nom.svg?style=flat-square)](https://www.npmjs.org/package/generator-nom) [![Dependency status](https://img.shields.io/david/ironsource/node-generator-nom.svg?style=flat-square)](https://david-dm.org/ironsource/node-generator-nom) 6 | 7 | ![demo](https://github.com/ironSource/node-generator-nom/raw/master/demo.gif) 8 | 9 | ## what it does 10 | 11 | *`yo nom`* will ask you which subgenerators to execute, then executes those in order as listed below. Those you successfully ran before will be disabled by default. Run any of them separately with `yo nom:*`. For example: `yo nom:travis` to just setup Travis. 12 | 13 | ### `npm` 14 | 15 | Create `package.json`, `.gitignore`, install test framework (tape, tap, mocha, grunt, cake, or ava), add `LICENSE` file (MIT, BSD2 or BSD3). If a `package.json` already exists, it will be merged, used for default answers and for sort order of the top-level fields. At deeper levels, like dependencies, nom behaves like npm and sorts lexicographically. 16 | 17 | ### `git` 18 | 19 | Initialize local git repository, unless `.git` directory exists. 20 | 21 | ### `github` 22 | 23 | Create public or private GitHub project, named "module-name" or "node-module-name". Unless local git already has configured remotes. Asks for access token and repository owner (which defaults to the owner of the token), skips creation if the repository already exists, adds URLs to `package.json` and adds remote origin. 24 | 25 | ### `travis` 26 | 27 | Add `.travis.yml` for node 0.10 and iojs, setup GitHub hook. The `travis` tool asks for username and password. 28 | 29 | ### `appveyor` 30 | 31 | Add `appveyor.yml` for node 0.10 and iojs, setup GitHub hook. Asks for access token. 32 | 33 | ### `cli` 34 | 35 | Create a CLI app (with [meow](https://github.com/sindresorhus/meow)) and add a `bin` field to `package.json`. Asks for name and path. 36 | 37 | ### `style` (todo) 38 | 39 | Add `standard` or `xo` as a pretest script. 40 | 41 | ### `readme` 42 | 43 | Add `readme.md` with common sections and [shield.io](https://shield.io) badges for npm and david. If you did the travis and/or appveyor setup, badges for those services will be added as well. 44 | 45 | ### `gulp` 46 | 47 | Create an ES5 or ES6 gulpfile and `tasks` directory. Installs `gulp` and if ES6, `babel-core`. 48 | 49 | ## usage 50 | 51 | ``` 52 | mkdir my-module 53 | cd my-module 54 | yo nom 55 | ``` 56 | 57 | ## install 58 | 59 | Install Yeoman and generator-nom globally with [npm](https://npmjs.org): 60 | 61 | ``` 62 | npm i yo generator-nom -g 63 | ``` 64 | 65 | ## changelog 66 | 67 | ### 2.0.0 68 | 69 | - Gulpfile can be in ES5 or ES6. 70 | - Disable gulp subgenerator by default 71 | - A `package.json` will always be created (for example, when running `nom:gulp` by itself). 72 | - Generation of main file (`index.js`) is optional 73 | - Move CLI to own subgenerator, with new questions for name and path. 74 | - Preserve sort order of existing `package.json`, except for dependencies which are always sorted lexicographically (like npm does). 75 | - CI targets node 0.10 and 4 76 | - Install latest npm on AppVeyor 77 | - Deduce author's name and email from (in order): author field of existing `package.json`, npm config (legacy formats too) and git config. Because Yeoman reads the git config by spawning git, it is done lazily, as a last resort. 78 | - Don't humanize author's URL, to keep it "clickable" in editors and other places 79 | - Author URL is optional 80 | - Pin Babel to 5 81 | 82 | ## license and acknowledgments 83 | 84 | [MIT](http://opensource.org/licenses/MIT) © [ironSource](http://www.ironsrc.com/). Originally forked from [generator-n](https://www.npmjs.com/package/generator-n) © Andrei Kashcha. Small parts borrowed from [generator-nm](https://github.com/sindresorhus/generator-nm) © [Sindre Sorhus](http://sindresorhus.com/). 85 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ironSource/node-generator-nom/ac0a45c7c45bff1e420e31485d8e735b98d8ca6c/demo.gif -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const after = require('after') 4 | const Base = require('yeoman-generator').Base 5 | const optParser = require('./option-parser') 6 | 7 | const subgenerators = 8 | [ 'npm' 9 | , 'git' 10 | , 'github' 11 | , 'travis' 12 | , 'appveyor' 13 | , 'cli' 14 | , 'readme' 15 | , 'gulp' ] 16 | 17 | const self = module.exports = class NomGenerator extends Base { 18 | constructor(args, options, config) { 19 | super(args, options, config) 20 | 21 | // "--name beep", "--description boop" 22 | ;['name', 'description'].forEach(option => { 23 | this.option(option, { 24 | type: 'String', 25 | desc: 'Module option passed to every subgenerator' 26 | }) 27 | 28 | this.options[option] = optParser.strictString(this.options[option], undefined) 29 | }) 30 | 31 | // "--esnext" or "--no-esnext" or "--esnext true" 32 | this.option('esnext', { 33 | type: 'Boolean', 34 | desc: `Use ES6 when possible or never (--no-esnext)` 35 | }) 36 | 37 | this.options.esnext = optParser.looseBoolean(this.options.esnext, undefined) 38 | 39 | // "--modules es6" 40 | this.option('modules', { 41 | type: 'String', 42 | desc: 'Module format, case insensitive: ES6 or CommonJS' 43 | }) 44 | 45 | let modules 46 | = this.options.modules 47 | = optParser.strictString(this.options.modules, undefined) 48 | 49 | if (modules !== undefined) { 50 | modules = this.options.modules = modules.toLowerCase() 51 | if (modules !== 'es6' && modules !== 'commonjs') { 52 | throw new Error('Module format must be "es6", "commonjs" or undefined') 53 | } 54 | } 55 | 56 | if (this.options.esnext === false) this.options.modules = 'commonjs' 57 | 58 | // "--enable a --enable b" or "--enable a b" 59 | ;['enable', 'disable'].forEach( (option, i) => { 60 | let action = option[0].toUpperCase() + option.slice(1) 61 | let example = i===0 ? ' (e.g. "git,gulp")' : '' 62 | 63 | this.option(option, { 64 | desc: `${action} subgenerators${example} and skip questions.` 65 | }) 66 | 67 | let value = this.options[option] 68 | 69 | if (typeof value === 'string') { 70 | value = value.split(/[ ,;\/|]+/) 71 | } else if (!Array.isArray(value)) { 72 | value = [] 73 | } 74 | 75 | this.options[option] = value.map(g => g.trim().toLowerCase()) 76 | }) 77 | 78 | subgenerators.forEach(subgen => { 79 | // Note: not supported on command line 80 | this.option(subgen, { type: 'Object', defaults: {}, hide: true }) 81 | }) 82 | } 83 | 84 | collectTasks() { 85 | this.tasksToRun = [] 86 | 87 | let primary = [] 88 | , secondary = [] 89 | , done = this.async() 90 | , next = after(subgenerators.length, err => { 91 | if (err) return done(err) 92 | this._promptForTasks(primary, secondary, done) 93 | }) 94 | 95 | subgenerators.forEach(subgen => { 96 | let path = require.resolve(`../${subgen}`) 97 | , Generator = require(path) 98 | 99 | let task = Generator.task 100 | let shouldRun = Generator.shouldRun 101 | let regenerate = Generator.regenerate 102 | let runByDefault = Generator.runByDefault == null ? true : !!Generator.runByDefault 103 | 104 | let generatorOptions = this._getGeneratorOptions(subgen) 105 | 106 | shouldRun(this, generatorOptions, (err, should) => { 107 | if (err) return next(err) 108 | 109 | // TODO: maybe save these in rc too? So when nom (or a subgen) is executed 110 | // instead of the parent, these options aren't lost. 111 | let enable = this.options.enable.indexOf(subgen) >= 0 112 | let disable = this.options.disable.indexOf(subgen) >= 0 113 | 114 | if (disable) return next() // Skip subgen, no matter what 115 | 116 | if (should && enable) { // Skip question, subgen is required by parent 117 | this.tasksToRun.push(subgen) 118 | return next() 119 | } 120 | 121 | if (!should) { 122 | if (!regenerate) return next() 123 | task = regenerate 124 | } 125 | 126 | // TODO: remember choices independent of `should` 127 | ;(should ? primary : secondary).push({ 128 | value: subgen, 129 | name: task, 130 | checked: should ? runByDefault : false 131 | }) 132 | 133 | next() 134 | }) 135 | }) 136 | } 137 | 138 | _promptForTasks(primary, secondary, done) { 139 | let questions = [] 140 | 141 | if (primary.length) questions.push({ 142 | type: 'checkbox', 143 | name: 'primary', 144 | message: 'Which module tasks would you like to run?', 145 | choices: primary 146 | }) 147 | 148 | if (secondary.length) questions.push({ 149 | type: 'checkbox', 150 | name: 'secondary', 151 | message: 'Which additional module tasks would you like to run?', 152 | choices: secondary 153 | }) 154 | 155 | if (!questions.length) return done() 156 | 157 | this.prompt(questions, (answers) => { 158 | let primary = answers.primary || [] 159 | let secondary = answers.secondary || [] 160 | 161 | this.tasksToRun = this.tasksToRun.concat(primary).concat(secondary) 162 | done() 163 | }) 164 | } 165 | 166 | _sub(generator) { 167 | if (this.tasksToRun.indexOf(generator) < 0) return 168 | 169 | let options = this._getGeneratorOptions(generator) 170 | 171 | this.composeWith(`nom:${generator}`, { options, args: [] }, { 172 | local: require.resolve(`../${generator}`), 173 | link: 'strong' 174 | }) 175 | } 176 | 177 | _getGeneratorOptions(generator) { 178 | return Object.assign({ 179 | name: this.options.name, 180 | description: this.options.description, 181 | esnext: this.options.esnext, 182 | modules: this.options.modules, 183 | skipInstall: this.options.skipInstall, 184 | skipCache: this.options.skipCache 185 | }, this.options[generator]) 186 | } 187 | } 188 | 189 | // Add subgens in same order, regardless of which the user picked 190 | subgenerators.forEach(sub => { 191 | self.prototype['run_' + sub] = function() { 192 | this._sub(sub) 193 | } 194 | }) 195 | -------------------------------------------------------------------------------- /generators/app/option-parser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function looseBoolean(value, notSet) { 4 | if (typeof value === 'boolean') { 5 | return value 6 | } else if (typeof value === 'string') { 7 | value = value.toLowerCase() 8 | return value === 'true' || value === '1' || value[0] === 'y' 9 | } else if (typeof value === 'number') { 10 | return value === 1 11 | } else { 12 | return notSet 13 | } 14 | } 15 | 16 | // Yeoman's option parsing is not strict enough for 17 | // our purposes. Don't allow empty strings, and take 18 | // the first of "--name a --name b". 19 | function strictString(value, notSet) { 20 | if (Array.isArray(value)) return strictString(value[0], notSet) 21 | if (typeof value !== 'string') return notSet 22 | 23 | value = value.trim() 24 | return value === '' ? notSet : value 25 | } 26 | 27 | exports.looseBoolean = looseBoolean 28 | exports.strictString = strictString 29 | -------------------------------------------------------------------------------- /generators/appveyor/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , AppVeyor = require('appveyor') 5 | 6 | const self = module.exports = class AppVeyorGenerator extends Conditional { 7 | prompting() { 8 | this.appveyor = new WrappedAppVeyor() 9 | if (this.appveyor.hasToken()) return 10 | 11 | let done = this.async() 12 | this.log('\nPlease create a token for AppVeyor at https://ci.appveyor.com/api-token\n') 13 | 14 | this.prompt([{ 15 | type: 'input', 16 | name: 'token', 17 | message: 'What is your AppVeyor token?', 18 | filter: val => (typeof val === 'string' ? val.trim() : '') 19 | }], answers => { 20 | let token = answers.token 21 | 22 | if (!token) { 23 | this.log.error('A token is required to access AppVeyor on your behalf.') 24 | this.canceled = true 25 | return done() 26 | } 27 | 28 | this.appveyor.auth({ token }) 29 | done() 30 | }) 31 | } 32 | 33 | writing() { 34 | if (this.canceled) return 35 | this.copy('_appveyor.yml', 'appveyor.yml') 36 | } 37 | 38 | // The appveyor tool needs git info, so we need to run this 39 | // after everything else has been written to disk. 40 | end() { 41 | if (this.canceled) return 42 | 43 | let done = this.async() 44 | this.appveyor.hookWithCallback(err => { 45 | if (err) this.log.error('Failed to create AppVeyor hook: ' + err) 46 | else this.log.create('AppVeyor hook') 47 | 48 | done() 49 | }) 50 | } 51 | } 52 | 53 | self.task = 'Setup AppVeyor' 54 | self.regenerate = 'Setup AppVeyor again' 55 | self.runByDefault = false 56 | 57 | self.shouldRun = function (ctx, opts, done) { 58 | done(null, !ctx.fs.exists('appveyor.yml')) 59 | } 60 | 61 | class WrappedAppVeyor extends AppVeyor { 62 | // AppVeyor._getToken is the most annoying API. It's synchronous, 63 | // but uses a callback. If the token is not set, it does not call 64 | // the callback, but emits an error. Yeah.. no. 65 | justGetToken() { 66 | return this.configstore.get('token') || undefined 67 | } 68 | 69 | hasToken() { 70 | return this.justGetToken() !== undefined 71 | } 72 | 73 | // And wrap hook() in callback style 74 | hookWithCallback(done) { 75 | let onError = (err) => { 76 | this.removeListener('hook', onHook) 77 | done(err) 78 | } 79 | 80 | let onHook = (data) => { 81 | this.removeListener('error', onError) 82 | done(null, data) 83 | } 84 | 85 | this.once('error', onError) 86 | this.once('hook', onHook) 87 | 88 | this.hook() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /generators/appveyor/templates/_appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | build: off 3 | skip_tags: true 4 | 5 | environment: 6 | matrix: 7 | - nodejs_version: "4" 8 | - nodejs_version: "5" 9 | - nodejs_version: "6" 10 | - nodejs_version: "7" 11 | - nodejs_version: "8" 12 | 13 | platform: 14 | - x86 15 | - x64 16 | 17 | install: 18 | - ps: Install-Product node $env:nodejs_version $env:platform 19 | - set PATH=%APPDATA%\npm;%PATH%;%APPVEYOR_BUILD_FOLDER%\node_modules\.bin 20 | - npm i 21 | 22 | test_script: 23 | - npm test 24 | -------------------------------------------------------------------------------- /generators/cli/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , camelCase = require('camel-case') 5 | , paramCase = require('param-case') 6 | , colors = require('chalk') 7 | , pad = require('pad-right') 8 | 9 | function paramCasePath(path) { 10 | path = path.toLowerCase() 11 | if (path.slice(-3) === '.js') path = path.slice(0, -3) 12 | 13 | return path.split(/[\/\\]+/).map((k, i) => { 14 | if (k === '.') return i === 0 ? '.' : '' 15 | return paramCase(k) 16 | }).filter(k=>k).join('/') 17 | } 18 | 19 | const CLI_MODULES = { 20 | none: { 21 | value: null 22 | }, 23 | minimist: { 24 | url: 'https://github.com/substack/minimist', 25 | dependencies: { 26 | minimist: null 27 | } 28 | }, 29 | yargs: { 30 | url: 'http://yargs.js.org', 31 | dependencies: { 32 | yargs: null 33 | } 34 | }, 35 | meow: { 36 | url: 'https://github.com/sindresorhus/meow', 37 | dependencies: { 38 | meow: null 39 | } 40 | }, 41 | commander: { 42 | url: 'https://github.com/tj/commander.js', 43 | dependencies: { 44 | commander: null 45 | } 46 | } 47 | } 48 | 49 | const self = module.exports = class CliGenerator extends Conditional { 50 | prompting() { 51 | let pack = this.fs.readJSON('package.json', false) 52 | 53 | if (!pack) return this.log.error('Cannot create CLI app because package.json is missing') 54 | if (!pack.name) return this.log.error('Cannot create CLI app because package name is missing') 55 | 56 | let prevBinName = pack.bin 57 | , prevBinPath 58 | 59 | if (typeof prevBinName === 'object') { 60 | let names = Object.keys(pack.bin) 61 | 62 | if (names.length === 0) { 63 | prevBinName = null 64 | } if (names.length === 1) { // bin: { [binName]: 'file.js' } 65 | prevBinName = names[0] 66 | prevBinPath = pack.bin[prevBinName] 67 | } else { 68 | return this.log.error('Cannot create CLI app because multiple binaries are not supported') 69 | } 70 | } else if (typeof prevBinName === 'string') { // bin: 'file.js' 71 | prevBinPath = prevBinName 72 | prevBinName = pack.name 73 | } else { 74 | prevBinName = null 75 | } 76 | 77 | let questions = [{ 78 | name: 'path', 79 | message: 'Where do you want to place the CLI source?', 80 | default: prevBinPath || 'cli.js', 81 | validate: (val) => paramCasePath(val).length ? true : 'You have to provide a CLI path', 82 | filter: (val) => paramCasePath(val) + '.js' 83 | }, { 84 | name: 'binName', 85 | message: 'What should be the executable name?', 86 | default: prevBinName || pack.name, 87 | validate: (val) => paramCase(val).length ? true : 'You have to provide a name', 88 | filter: (val) => paramCase(val) 89 | }, { 90 | name: 'cliModule', 91 | message: 'Select a cli module', 92 | type: 'list', 93 | choices: Object.keys(CLI_MODULES).map(key => { 94 | const mod = CLI_MODULES[key] 95 | , name = mod.url ? pad(key, 10, ' ') + colors.gray(mod.url) : key 96 | 97 | return { name: name, value: 'value' in mod ? mod.value : key } 98 | }), 99 | validate: (choice) => { // Used to validate option 100 | let choices = Object.keys(CLI_MODULES) 101 | if (choices.indexOf(choice) >= 0) return true 102 | return 'Must be one of ' + JSON.stringify(choices, null, 2) 103 | } 104 | }] 105 | 106 | let done = this.async() 107 | 108 | this.prompt(questions, (answers) => { 109 | this.answers = answers 110 | this.pack = pack 111 | 112 | done() 113 | }) 114 | } 115 | 116 | writing() { 117 | let pack = this.pack 118 | if (!pack) return 119 | 120 | let path = this.answers.path 121 | let binName = this.answers.binName 122 | let cliModule = this.answers.cliModule 123 | 124 | let moduleName = pack.name 125 | , camelModuleName = camelCase(moduleName) 126 | 127 | if (cliModule) { 128 | this.fs.copyTpl 129 | ( this.templatePath('_' + cliModule + '.js') 130 | , this.destinationPath(path) 131 | , { camelModuleName, binName }) 132 | } 133 | 134 | let binField = binName === moduleName ? path : { [binName]: path } 135 | 136 | if (pack.bin) { // Keep existing order 137 | pack.bin = binField 138 | } else { // Add after description, or in the beginning 139 | let clone = {} 140 | , keys = Object.keys(pack) 141 | , added = false 142 | 143 | for(let i=0, last=keys.length-1; i <= last; i++) { 144 | let k = keys[i] 145 | clone[k] = pack[k] 146 | 147 | if (!added && (k.toLowerCase() === 'description' || i > 3 || i === last)) { 148 | added = true 149 | clone.bin = binField 150 | } 151 | } 152 | 153 | pack = clone 154 | } 155 | 156 | this.fs.writeJSON('package.json', pack) 157 | 158 | // TODO: ask to keep previous module(s) 159 | if (cliModule) { 160 | this.saveDependencies(CLI_MODULES[cliModule].dependencies, this.async()) 161 | } 162 | } 163 | } 164 | 165 | self.task = 'Create CLI app' 166 | self.regenerate = 'Recreate CLI app' 167 | self.runByDefault = false 168 | 169 | self.shouldRun = function (ctx, opts, done) { 170 | let bin = ctx.fs.readJSON('package.json', {}).bin 171 | done(null, !bin) 172 | } 173 | -------------------------------------------------------------------------------- /generators/cli/templates/_commander.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const <%= camelModuleName %> = require('./') 5 | , program = require('commander') 6 | 7 | program 8 | .version('0.0.1') 9 | .option('-p, --peppers', 'Add peppers') 10 | .parse(process.argv) 11 | -------------------------------------------------------------------------------- /generators/cli/templates/_meow.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const meow = require('meow') 5 | , <%= camelModuleName %> = require('./') 6 | 7 | const cli = meow({ 8 | help: [ 9 | 'Usage', 10 | ' $ <%= binName %> [input]', 11 | '', 12 | 'Options', 13 | ' --flag Description (default: false)', 14 | '', 15 | 'Examples', 16 | ' $ <%= binName %>', 17 | ' unicorns', 18 | ' $ <%= binName %> ponies', 19 | ' ponies' 20 | ] 21 | }, { 22 | boolean: ['flag'], 23 | string: ['format'], 24 | default: { 25 | format: 'text' 26 | } 27 | }) 28 | 29 | console.log(<%= camelModuleName %>(cli.input[0] || 'unicorns')) 30 | -------------------------------------------------------------------------------- /generators/cli/templates/_minimist.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const <%= camelModuleName %> = require('./') 5 | , argv = require('minimist')(process.argv.slice(2)) 6 | -------------------------------------------------------------------------------- /generators/cli/templates/_yargs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | const <%= camelModuleName %> = require('./') 5 | , argv = require('yargs').argv 6 | -------------------------------------------------------------------------------- /generators/conditional-subgen.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ConfigStore = require('configstore') 4 | , after = require('after') 5 | , latest = require('latest-version') 6 | , Base = require('yeoman-generator').Base 7 | 8 | const pkg = require('../package.json') 9 | const moduleName = pkg.name 10 | const bugs = pkg.bugs 11 | 12 | const self = module.exports = class ConditionalGenerator extends Base { 13 | constructor(args, options, config) { 14 | super(args, options, config) 15 | this.settings = self.getSettings() 16 | } 17 | 18 | saveDependencies(deps, opts, done) { 19 | if (typeof opts === 'function') done = opts, opts = {} 20 | 21 | let group = opts && opts.dev ? 'devDependencies' : 'dependencies' 22 | 23 | if (Array.isArray(deps)) { 24 | let obj = Object.create(null) 25 | deps.forEach(dep => obj[dep] = null) 26 | deps = obj 27 | } 28 | 29 | let pkg = this.fs.readJSON('package.json', {}) 30 | 31 | // Will run during the 'install' phase of the run loop. Other 32 | // subgens have the opportunity to add dependencies before that. 33 | if (!self.hasInstallScheduled && !this.options.skipInstall) { 34 | self.hasInstallScheduled = true 35 | this.npmInstall() 36 | } 37 | 38 | let output = pkg[group] || {} 39 | , names = Object.keys(deps) 40 | 41 | let next = after(names.length, err => { 42 | if (err) return done(err) 43 | 44 | pkg[group] = output 45 | this.fs.writeJSON('package.json', pkg) 46 | 47 | done() 48 | }) 49 | 50 | names.forEach(dep => { 51 | let wished = deps[dep] 52 | let installed = this.fs.readJSON(this.destinationPath(`node_modules/${dep}/package.json`), {}).version 53 | 54 | if (wished || installed) { 55 | output[dep] = wished || ('~' + installed) 56 | return setImmediate(next) 57 | } 58 | 59 | latest(dep, (err, version) => { 60 | if (err) this.log.error('Could not fetch version of %s, please save manually: %s', dep, err) 61 | else output[dep] = '~' + version 62 | next() 63 | }) 64 | }) 65 | } 66 | 67 | moduleName() { 68 | return moduleName 69 | } 70 | 71 | moduleBugs() { 72 | return bugs 73 | } 74 | } 75 | 76 | self.getSettings = function () { 77 | return self.settings || (self.settings = new ConfigStore(moduleName)) 78 | } 79 | -------------------------------------------------------------------------------- /generators/git/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , git = require('gift') 5 | , pathExists = require('path-exists') 6 | 7 | const self = module.exports = class GitGenerator extends Conditional { 8 | initializing() { 9 | let done = this.async() 10 | 11 | self.shouldRun(this, this.options, (err, should) => { 12 | if (!should) { 13 | // TODO: prompt with warning 14 | this.log.skip('.git - removing is not yet implemented') 15 | return done() 16 | } 17 | 18 | git.init(this.destinationRoot(), (err, repo) => { 19 | if (err) return done(err) 20 | 21 | this.log.create('.git') 22 | setImmediate(done) 23 | }) 24 | }) 25 | } 26 | } 27 | 28 | self.task = 'Initialize local git repository' 29 | self.regenerate = 'Remove and re-initialize local repository' 30 | 31 | self.shouldRun = function (ctx, opts, done) { 32 | done(null, !pathExists.sync(ctx.destinationPath('.git'))) 33 | } 34 | -------------------------------------------------------------------------------- /generators/github/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , git = require('gift') 5 | , octo = require('octonode') 6 | , pathExists = require('path-exists') 7 | 8 | const self = module.exports = class GithubGenerator extends Conditional { 9 | // Should run after nom:npm.configuring 10 | configuring() { 11 | let done = this.async() 12 | 13 | self.shouldRun(this, this.options, (err, should, repo) => { 14 | if (err) return done(err) 15 | 16 | if (!repo) { 17 | this.log.error('Could not find local git repository') 18 | return done() 19 | } 20 | 21 | if (!should) { 22 | this.log.skip('GitHub - because local repository has existing remote origin') 23 | return done() 24 | } 25 | 26 | let _pkg = this.fs.readJSON('package.json', {}) 27 | let moduleName = _pkg.name 28 | let description = _pkg.description 29 | 30 | if (!moduleName) { 31 | return done(new Error( 32 | 'Package.json or module name is missing. Please describe how you got into this '+ 33 | 'situation here: ' + this.moduleBugs() 34 | )) 35 | } 36 | 37 | this.prompt([{ 38 | name: 'repositoryName', 39 | message: 'What would you like to call the repository?', 40 | type: 'list', 41 | default: moduleName, 42 | choices: [ 43 | moduleName, 44 | 'node-' + moduleName 45 | ] 46 | }], (answers) => { 47 | let repositoryName = answers.repositoryName 48 | 49 | this._create(repo, repositoryName, description, 0, (err, qualifiedName) => { 50 | this.qualifiedName = qualifiedName 51 | done(err) 52 | }) 53 | }) 54 | }) 55 | } 56 | 57 | _create(localRepo, repositoryName, description, retries, done) { 58 | if (retries > 2) { 59 | this.log.error('Failed to create GitHub repository after 2 retries') 60 | return done() 61 | } 62 | 63 | this._getToken((err, token) => { 64 | if (err || !token) return done(err) 65 | 66 | let client = octo.client(token) 67 | , ghme = client.me() 68 | 69 | // Get username 70 | ghme.info((err, data) => { 71 | if (err) { 72 | if (err.statusCode == 401) { 73 | // Clear token and try again 74 | this.settings.del('github_oauth') 75 | this.log.error('Could not authorize with github using your token. Have you revoked it?') 76 | return this._create(localRepo, repositoryName, description, retries + 1, done) 77 | } 78 | 79 | return done(err) 80 | } 81 | 82 | let defaultOwner = data.login 83 | 84 | // Prompt for repository owner (default to username) 85 | this.prompt([{ 86 | name: 'owner', 87 | message: 'Who should be the repository owner?', 88 | default: defaultOwner, 89 | filter: (v) => (typeof v === 'string' ? v.trim() : '') 90 | }], answers => { 91 | let owner = answers.owner 92 | let isDefaultOwner = owner.toLowerCase() === defaultOwner.toLowerCase() 93 | 94 | if (!owner) { 95 | this.log.skip('Will not create GitHub project, owner name is empty') 96 | return done() 97 | } 98 | 99 | let qualifiedName = `${owner}/${repositoryName}` 100 | 101 | let register = () => { 102 | // Add remote origin 103 | let origin = `git@github.com:${qualifiedName}.git` 104 | localRepo.remote_add('origin', origin, err => { 105 | if (err) this.log.error('Failed to add remote: ' + err) 106 | else this.log.create('Remote origin: ' + origin) 107 | 108 | // Lastly, schedule to update package.json 109 | done(null, qualifiedName) 110 | }) 111 | } 112 | 113 | // Check if repository exists 114 | client.repo(qualifiedName).info( (err, data) => { 115 | if (data && data.name) { 116 | this.log.skip('GitHub project already exists: ' + qualifiedName) 117 | return register() 118 | } 119 | 120 | // If not, create 121 | let api = isDefaultOwner ? ghme : client.org(owner) 122 | 123 | this._createGitHubProject(api, repositoryName, description, err => { 124 | if (err) { 125 | this.log.error('Failed to create GitHub project: "' + err + '"') 126 | return done() 127 | } 128 | 129 | register() 130 | }) 131 | }) 132 | }) 133 | }) 134 | }) 135 | } 136 | 137 | _getToken(done) { 138 | let token = this.settings.get('github_oauth'); 139 | if (token) return setImmediate(done.bind(null, null, token)) 140 | 141 | this.log('\nPlease create a new access token for Github at'+ 142 | 'https://github.com/settings/tokens/new - with'+ 143 | 'the "public_repo" and/or "repo" scopes.\n') 144 | 145 | this.prompt([{ 146 | type: 'input', 147 | name: 'token', 148 | message: 'What is your GitHub token?' , 149 | }], props => { 150 | let token = props.token 151 | 152 | if (!token) { 153 | this.log.error ( 154 | 'Github personal token is required to access GitHub on your behalf. '+ 155 | 'If you have any concerns about their security, please refer to '+ 156 | 'https://help.github.com/articles/creating-an-access-token-for-command-line-use' 157 | ) 158 | return done() 159 | } 160 | 161 | this.settings.set('github_oauth', token) 162 | done(null, token) 163 | }) 164 | } 165 | 166 | _createGitHubProject(api, name, description, done) { 167 | this.prompt([{ 168 | name: 'pub', 169 | message: 'Do you want to make this repository public?', 170 | type: 'confirm', 171 | default: true 172 | }], answers => { 173 | api.repo({ name, description, 'private': !answers.pub }, (err, body, headers) => { 174 | if (err) return done(err) 175 | 176 | let type = answers.pub ? 'Public' : 'Private' 177 | this.log.create(`${type} GitHub repository ${body.full_name}`) 178 | done() 179 | }) 180 | }) 181 | } 182 | 183 | writing() { 184 | let qualifiedName = this.qualifiedName 185 | 186 | if (!qualifiedName) { 187 | return this.log.skip('Update URLs of package.json - no GitHub repository name set') 188 | } 189 | 190 | let pkg = this.fs.readJSON('package.json', false) 191 | 192 | if (pkg) { 193 | if (!pkg.repository) pkg.repository = qualifiedName 194 | if (!pkg.bugs) pkg.bugs = 'https://github.com/' + qualifiedName + '/issues' 195 | if (!pkg.homepage) pkg.homepage = 'https://github.com/' + qualifiedName 196 | 197 | this.fs.writeJSON('package.json', pkg) 198 | this.log.info('Updated URLs of package.json') 199 | } else { 200 | this.log.error('Cannot update URLs: could not find package.json') 201 | } 202 | } 203 | } 204 | 205 | self.task = 'Host this project on GitHub' 206 | self.regenerate = false 207 | self.runByDefault = false 208 | 209 | self.shouldRun = function (ctx, opts, done) { 210 | let repo = self.getRepo(ctx) 211 | if (!repo) return done(null, true) 212 | 213 | repo.config((err, cfg) => { 214 | if (err) return done(err) 215 | 216 | let origin = cfg.items && cfg.items['remote.origin.url'] 217 | return done(null, !origin, repo) 218 | }) 219 | } 220 | 221 | self.getRepo = function (ctx) { 222 | // Must use real fs to check existence, not yeoman's virtual fs 223 | return pathExists.sync(ctx.destinationPath('.git')) ? git(ctx.destinationRoot()) : null 224 | } 225 | -------------------------------------------------------------------------------- /generators/gulp/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | const looseBoolean = require('../app/option-parser').looseBoolean 5 | 6 | const self = module.exports = class GulpGenerator extends Conditional { 7 | constructor(args, options, config) { 8 | super(args, options, config) 9 | 10 | this.option('tasks', { 11 | type: 'String', 12 | defaults: this.templatePath('tasks') + '/*' 13 | }) 14 | 15 | // "--esnext" or "--no-esnext" or "--esnext true" 16 | this.option('esnext', { 17 | type: 'Boolean', 18 | desc: `Use ES6 gulpfile or not (--no-esnext) and skip question` 19 | }) 20 | 21 | this.options.esnext = looseBoolean(this.options.esnext, undefined) 22 | } 23 | 24 | prompting() { 25 | if (this.options.esnext === undefined) { 26 | let questions = [ 27 | { type: 'confirm' 28 | , name: 'esnext' 29 | , message: 'Do you want an ES6 gulpfile?' 30 | , default: true 31 | , store: true } 32 | ] 33 | 34 | let done = this.async() 35 | this.prompt(questions, (answers) => { 36 | this.ctx = answers 37 | done() 38 | }) 39 | } else { 40 | this.ctx = { esnext: this.options.esnext } 41 | } 42 | } 43 | 44 | writing() { 45 | let deps = { 46 | 'gulp': '~3.9.0', 47 | 'gulp-util': '~3.0.6', 48 | 'glob': null // use latest version 49 | } 50 | 51 | if (this.ctx.esnext) { 52 | this._copyTpl('_gulpfile.babel.js', 'gulpfile.babel.js') 53 | this._copyTpl('_.babelrc', '.babelrc') 54 | deps['babel-core'] = '~5.8.33' 55 | } else { 56 | deps['object-assign'] = null 57 | this._copyTpl('_gulpfile.js', 'gulpfile.js') 58 | } 59 | 60 | let ctx = Object.assign({}, this.ctx, this.options.ctx) 61 | , tasks = [].concat(this.options.tasks) 62 | 63 | this.fs.copyTpl(tasks, this.destinationPath('tasks'), ctx) 64 | 65 | this.saveDependencies(deps, { dev: true }, this.async()) 66 | } 67 | 68 | _copyTpl(src, dest) { 69 | this.fs.copyTpl(this.templatePath(src), this.destinationPath(dest), this.ctx) 70 | } 71 | } 72 | 73 | self.task = 'Install gulp 3.9 and create gulpfile' 74 | self.regenerate = 'Reinstall gulp 3.9 and recreate gulpfile' 75 | self.runByDefault = false 76 | 77 | self.shouldRun = function (ctx, opts, done) { 78 | done(null, !ctx.fs.exists('gulpfile.js') && !ctx.fs.exists('gulpfile.babel.js')) 79 | } 80 | -------------------------------------------------------------------------------- /generators/gulp/templates/_.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "blacklist": [ "regenerator" ], 3 | "optional": [ "es7.objectRestSpread", "es7.classProperties" ] 4 | } 5 | -------------------------------------------------------------------------------- /generators/gulp/templates/_gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | , path = require('path') 3 | , glob = require('glob') 4 | 5 | glob.sync('tasks/*.js').forEach(file => { 6 | require( path.resolve(file) ) 7 | }) 8 | 9 | gulp.task('default', ['build']) 10 | -------------------------------------------------------------------------------- /generators/gulp/templates/_gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp') 2 | , path = require('path') 3 | , glob = require('glob') 4 | 5 | glob.sync('tasks/*.js').forEach(function(file) { 6 | require( path.resolve(file) ) 7 | }) 8 | 9 | gulp.task('default', ['build']) 10 | -------------------------------------------------------------------------------- /generators/gulp/templates/tasks/build.js: -------------------------------------------------------------------------------- 1 | <% if (esnext) { -%> 2 | const gulp = require('gulp') 3 | 4 | gulp.task('build', () => { 5 | return gulp.src('src/*.js') 6 | .pipe( gulp.dest('dist') ) 7 | }) 8 | <% } else { -%> 9 | var gulp = require('gulp') 10 | 11 | gulp.task('build', function() { 12 | return gulp.src('src/*.js') 13 | .pipe( gulp.dest('dist') ) 14 | }) 15 | <% } -%> 16 | -------------------------------------------------------------------------------- /generators/npm/deep-sort-object.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Adapted from github.com/npm/npm/blob/master/lib/utils/deep-sort-object.js 4 | 5 | const sortObject = require('sorted-object') 6 | , sortArray = require('stable') 7 | 8 | module.exports = function deepSortObject(obj, cmp) { 9 | if (Array.isArray(obj)) return sortArray(obj, cmp) 10 | if (obj == null || typeof obj !== 'object') return obj 11 | 12 | obj = sortObject(obj) 13 | 14 | Object.keys(obj).forEach(key => { 15 | obj[key] = deepSortObject(obj[key], cmp) 16 | }) 17 | 18 | return obj 19 | } 20 | -------------------------------------------------------------------------------- /generators/npm/guess-author.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const npmconf = require('npmconf') 4 | , parseAuthor = require('parse-author') 5 | , normalOrEmptyUrl = require('./normal-or-empty-url') 6 | 7 | // Get author info from existing package, npm config or git 8 | module.exports = function(prevAuthor, gitUser, done) { 9 | let author = prevAuthor 10 | 11 | if (typeof author === 'string') author = parseAuthor(author) 12 | if (!author) author = {} 13 | 14 | let pick = (sources) => { 15 | for (let i=0, l=sources.length; i { 29 | let npm = err ? (() => null) : (dashed) => { 30 | // Support dotted key, prefer dashed. See npm/npm#6642 31 | let dotted = dashed.replace(/-/g, '.') 32 | return pick([ conf.get(dashed), conf.get(dotted) ]) 33 | } 34 | 35 | let name = pick 36 | ([ author.name 37 | , npm('init-author-name') 38 | , gitUser.name.bind(gitUser) ]) 39 | 40 | let email = pick 41 | ([ author.email 42 | , author.mail 43 | , npm('init-author-email') 44 | , npm('email') 45 | , gitUser.email.bind(gitUser) ]) 46 | 47 | let url = pick 48 | ([ author.url 49 | , author.website 50 | , author.web 51 | , npm('init-author-url') ]) 52 | 53 | done(null, { name, email, url: normalOrEmptyUrl(url) }) 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /generators/npm/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , paramCase = require('param-case') 5 | , omit = require('lodash.omit') 6 | , normalOrEmptyUrl = require('./normal-or-empty-url') 7 | , guessAuthor = require('./guess-author') 8 | , mkdirp = require('mkdirp') 9 | , deepSortObject = require('./deep-sort-object') 10 | , paramCaseName = require('./param-case-name') 11 | 12 | const strictString = require('../app/option-parser').strictString 13 | 14 | const LICENSE_TEMPLATES = [ 'mit', 'bsd2', 'bsd3' ] 15 | 16 | const LICENSES = [ 17 | 'MIT', 18 | 'UNLICENSED', 19 | 'BSD2', 20 | 'BSD3' 21 | ] 22 | 23 | const TEST_FRAMEWORKS = [ 24 | 'none', 25 | 'tape', 26 | 'tap', 27 | 'mocha', 28 | 'grunt', 29 | 'cake', 30 | 'ava' 31 | ] 32 | 33 | const DEFAULT_MAIN = 'index.js' 34 | 35 | function paramCasePath(path) { 36 | return path.split(/[\/\\]+/).map(paramCase).filter(k=>k).join('/') 37 | } 38 | 39 | const self = module.exports = class NpmGenerator extends Conditional { 40 | constructor(args, options, config) { 41 | super(args, options, config) 42 | 43 | // "--main" or "--no-main" or "--main lib/index.js" 44 | this.option('main', { 45 | type: 'Boolean', 46 | desc: `Relative path to main file or none (--no-main)`, 47 | }) 48 | 49 | // "--modules es6" 50 | this.option('modules', { 51 | type: 'String', 52 | desc: 'Module format, case insensitive: ES6 or CommonJS', 53 | defaults: 'CommonJS' 54 | }) 55 | } 56 | 57 | initializing() { 58 | let done = this.async() 59 | let main = this.options.main 60 | 61 | if (main === true || main == null) { 62 | this.options.main = undefined // Use default or package.main 63 | } else if (typeof main === 'string') { 64 | if (main.slice(-3).toLowerCase() === '.js') { 65 | main = main.slice(0, -3) 66 | } 67 | 68 | let cased = paramCasePath(main) 69 | 70 | if (cased === '') { 71 | let msg = `Main path must be param cased: "${main}"` 72 | return this.env.error(msg) 73 | } 74 | 75 | this.options.main = cased + '.js' 76 | } else { 77 | this.options.main = false 78 | } 79 | 80 | let modules 81 | = this.options.modules 82 | = strictString(this.options.modules, 'CommonJS').toLowerCase() 83 | 84 | if (modules !== 'es6' && modules !== 'commonjs') { 85 | let msg = 'Module format must be "es6", "commonjs" or undefined' 86 | return this.env.error(msg) 87 | } 88 | 89 | this.pack = this.fs.readJSON('package.json', {}) 90 | 91 | this._getDefaults((err, defaults) => { 92 | if (err) return done(err) 93 | this.defaults = defaults 94 | done() 95 | }) 96 | } 97 | 98 | _getDefaults(done) { 99 | let name = this.pack.name 100 | let description = this.pack.description 101 | let author = this.pack.author 102 | let license = this.pack.license 103 | let devDependencies = this.pack.devDependencies || {} 104 | let dependencies = this.pack.dependencies || {} 105 | let version = this.pack.version 106 | let keywords = this.pack.keywords 107 | let main = this.pack.main 108 | 109 | guessAuthor(author, this.user.git, (err, author) => { 110 | if (err) return done(err) 111 | 112 | // Find previous test framework 113 | let testFramework = 'tape' 114 | let deps = Object.keys(devDependencies) 115 | for(let i=0, l=TEST_FRAMEWORKS.length; i=0 ) { 117 | testFramework = TEST_FRAMEWORKS[i] 118 | break 119 | } 120 | } 121 | 122 | if (this.options.main === false) { 123 | main = false 124 | } else { 125 | main = this.options.main || main || DEFAULT_MAIN 126 | } 127 | 128 | done(null, { 129 | dependencies, 130 | devDependencies, 131 | main, 132 | version: version || '0.0.1', 133 | moduleName: paramCaseName(name || this.appname), 134 | description: description || 'my module', 135 | license: license || 'MIT', 136 | testFramework, 137 | name: author.name, 138 | email: author.email, 139 | url: author.url, 140 | keywords: (keywords || []).filter(k => k).join(' ') 141 | }) 142 | }) 143 | } 144 | 145 | _getLicenses(defaultLicense) { 146 | // If unsupported license was set, add to choices 147 | let id = defaultLicense.toUpperCase().trim() 148 | if (LICENSES.indexOf(id) < 0) return [defaultLicense].concat(LICENSES) 149 | else return LICENSES 150 | } 151 | 152 | prompting() { 153 | // Any question can be skipped by providing an option 154 | let moduleName = this.options.name 155 | let description = this.options.description 156 | 157 | // Uh, what are we doing here? Refactor someday. 158 | let rest = omit(this.options, ['name', 'description']) 159 | let override = Object.assign({ moduleName, description }, rest) 160 | 161 | let defaults = this.defaults 162 | 163 | let questions = [{ 164 | name: 'moduleName', 165 | message: 'What do you want to name your module?', 166 | default: defaults.moduleName, 167 | validate: val => val.length ? true : 'You have to provide a module name', 168 | filter: paramCaseName 169 | }, 170 | { 171 | name: 'description', 172 | message: 'How would you like to describe your module?', 173 | default: defaults.description, 174 | validate: val => val.length ? true : 'You have to provide a description' 175 | }, 176 | { 177 | type: 'list', 178 | name: 'license', 179 | store: true, 180 | message: 'Select a license', 181 | default: defaults.license, 182 | choices: this._getLicenses(defaults.license).map(k => { 183 | return { value: k, name: k === 'UNLICENSED' ? k + ' (meaning proprietary)' : k } 184 | }) 185 | }, 186 | { 187 | type: 'list', 188 | name: 'testFramework', 189 | message: 'Select a test framework', 190 | store: true, 191 | default: defaults.testFramework, 192 | choices: TEST_FRAMEWORKS 193 | }, 194 | { 195 | name: 'name', 196 | message: 'For author, what is your full name?', 197 | store: true, 198 | default: defaults.name, 199 | validate: val => val.length ? true : 'You have to provide a name' 200 | }, 201 | { 202 | name: 'enableEmail', 203 | message: 'For author, want to provide an email address?', 204 | type: 'confirm', 205 | store: true, 206 | default: !!defaults.email 207 | }, 208 | { 209 | name: 'email', 210 | message: 'Nice. What is your email?', 211 | store: true, 212 | when: (answers) => answers.enableEmail, 213 | default: defaults.email, 214 | validate: val => val.length ? true : 'You have to provide an email address' 215 | }, 216 | { 217 | name: 'enableUrl', 218 | message: 'For author, want to provide a public URL?', 219 | type: 'confirm', 220 | store: true, 221 | default: !!defaults.url 222 | }, 223 | { 224 | name: 'url', 225 | message: 'Will do, what\'s your URL?', 226 | store: true, 227 | when: (answers) => answers.enableUrl, 228 | default: defaults.url, 229 | filter: normalOrEmptyUrl 230 | }, 231 | { 232 | name: 'copyrightHolder', 233 | message: 'Who or what entity is the copyright holder?', 234 | default: answers => answers.name, 235 | validate: val => val.length ? true : 'You have to provide a copyright holder' 236 | }, 237 | { 238 | name: 'keywords', 239 | message: 'What are the space separated keywords for your module?', 240 | default: defaults.keywords, 241 | filter: val => (val || '').toLowerCase().split(/[ ,;\/|]+/).filter(k => k) 242 | }] 243 | 244 | // Take existing dependencies 245 | this._askToKeepDeps(questions, 'dependencies', defaults.dependencies) 246 | this._askToKeepDeps(questions, 'devDependencies', defaults.devDependencies) 247 | 248 | // Skip prompting for overrides 249 | questions = questions.filter(q => override[q.name] == null || isDependencies(q.name)) 250 | 251 | let done = this.async() 252 | 253 | this.prompt(questions, ctx => { 254 | // Add overrides to context 255 | Object.keys(override).forEach(key => { 256 | if (override[key] == null) return 257 | 258 | if (isDependencies(key)) { 259 | let deps = Array.isArray(override[key]) ? override[key] : Object.keys(override[key]) 260 | , current = ctx[key] || [] 261 | 262 | ctx[key] = current.concat(deps.filter(d => current.indexOf(d) < 0)) 263 | ctx[key].sort() 264 | } else { 265 | ctx[key] = override[key] 266 | } 267 | }) 268 | 269 | // Add defaults that we didn't ask about to context 270 | Object.keys(defaults).forEach(key => { 271 | if (isDependencies(key)) return 272 | if (ctx[key] == null) ctx[key] = defaults[key] 273 | }) 274 | 275 | ctx.testFramework = ctx.testFramework === 'none' ? null : ctx.testFramework 276 | ctx.testCommand = ctx.testFramework ? ctx.testFramework + ' test/**/*.js' : '' 277 | 278 | ctx.dependencies || (ctx.dependencies = []) 279 | ctx.devDependencies || (ctx.devDependencies = []) 280 | 281 | if (ctx.testFramework && ctx.devDependencies.indexOf(ctx.testFramework) < 0) { 282 | ctx.devDependencies.push(ctx.testFramework) 283 | } 284 | 285 | let saveDeps = {} 286 | ;['dependencies', 'devDependencies'].forEach(group => { 287 | let save = saveDeps[group] = {} 288 | 289 | ctx[group].forEach(dep => { 290 | save[dep] = (override[group] && override[group][dep]) || defaults[group][dep] || null 291 | }) 292 | }) 293 | 294 | ctx.dependencies = saveDeps.dependencies 295 | ctx.devDependencies = saveDeps.devDependencies 296 | 297 | this.ctx = ctx 298 | done() 299 | }) 300 | } 301 | 302 | // Note: the order in which we set fields matters for new packages. 303 | _writePackage() { 304 | let ctx = this.ctx 305 | let pack = this.pack 306 | 307 | pack.name = ctx.moduleName 308 | 309 | ;['version', 'description', 'license'].forEach(key => { 310 | pack[key] = ctx[key] 311 | }) 312 | 313 | if (ctx.main === false) { 314 | delete pack.main 315 | } else if (ctx.main !== DEFAULT_MAIN) { 316 | pack.main = ctx.main 317 | } 318 | 319 | if (!ctx.enableEmail && !ctx.enableUrl) { 320 | pack.author = ctx.name 321 | } else { 322 | pack.author = { name: ctx.name } 323 | 324 | if (ctx.enableEmail && ctx.email) pack.author.email = ctx.email 325 | if (ctx.enableUrl && ctx.url) pack.author.url = ctx.url 326 | } 327 | 328 | if (!pack.scripts) pack.scripts = {} 329 | if (!pack.scripts.test && ctx.testCommand) pack.scripts.test = ctx.testCommand 330 | 331 | ;['dependencies', 'devDependencies', 'keywords'].forEach(key => { 332 | pack[key] = deepSortObject(ctx[key]) 333 | }) 334 | 335 | const engines = pack.engines || {} 336 | engines.node = engines.node || '>=4.0.0' 337 | engines.npm = engines.npm || '>=2.0.0' 338 | pack.engines = deepSortObject(engines) 339 | 340 | this.fs.writeJSON(this.destinationPath('package.json'), pack) 341 | } 342 | 343 | // Do this early in the run loop, b/c other subgens depend on it 344 | configuring() { 345 | let ctx = this.ctx 346 | let cp = (from, to) => this.fs.copyTpl(this.templatePath(from), this.destinationPath(to), ctx) 347 | 348 | this._writePackage() 349 | 350 | if (ctx.main !== false) cp('_index.js', ctx.main) 351 | 352 | cp('_.gitignore', '.gitignore') 353 | 354 | let license = ctx.license.toLowerCase() 355 | if (LICENSE_TEMPLATES.indexOf(license) >= 0) { 356 | cp('license/' + license, 'LICENSE') 357 | } 358 | 359 | let done = this.async() 360 | 361 | this.saveDependencies(ctx.dependencies, err => { 362 | if (err) return done(err) 363 | 364 | this.saveDependencies(ctx.devDependencies, { dev: true }, err => { 365 | if (err) done(err) 366 | else if (ctx.testCommand) { 367 | this.log.create('test directory') 368 | mkdirp(this.destinationPath('test'), done) 369 | } else done() 370 | }) 371 | }) 372 | } 373 | 374 | _askToKeepDeps(questions, group, deps) { 375 | let names = Object.keys(deps) 376 | 377 | if (names.length) questions.push({ 378 | type: 'checkbox', 379 | name: group, 380 | message: `Which ${group} do you want to keep?`, 381 | choices: names.map(name => { 382 | return { name, value: name, checked: true } 383 | }) 384 | }) 385 | } 386 | } 387 | 388 | self.task = 'Create package.json and common files' 389 | self.regenerate = 'Recreate package.json and common files' 390 | 391 | self.shouldRun = function (ctx, opts, done) { 392 | done(null, !ctx.fs.readJSON('package.json', false)) 393 | } 394 | 395 | function isDependencies(key) { 396 | return key === 'dependencies' || key === 'devDependencies' 397 | } 398 | -------------------------------------------------------------------------------- /generators/npm/normal-or-empty-url.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const normalizeUrl = require('normalize-url') 4 | 5 | module.exports = function(url) { 6 | if (typeof url !== 'string' || url.trim() === '') return '' 7 | return normalizeUrl(url) || '' 8 | } 9 | -------------------------------------------------------------------------------- /generators/npm/param-case-name.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const paramCase = require('param-case') 4 | 5 | module.exports = function (name) { 6 | if (name[0] === '@') { 7 | const a = name.split('/') 8 | const scope = a.shift().slice(1) 9 | const rest = a.join('/') 10 | 11 | if (!scope || !rest) { 12 | return paramCase(name) 13 | } 14 | 15 | return '@' + paramCase(scope) + '/' + paramCase(rest) 16 | } else { 17 | return paramCase(name) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /generators/npm/templates/_.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | .idea 17 | -------------------------------------------------------------------------------- /generators/npm/templates/_index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | <% if (modules === 'es6') { -%> 4 | export default function() { 5 | 6 | } 7 | <% } else { -%> 8 | module.exports = function() { 9 | 10 | } 11 | <% } -%> 12 | -------------------------------------------------------------------------------- /generators/npm/templates/license/bsd2: -------------------------------------------------------------------------------- 1 | Copyright (c) <%= (new Date()).getFullYear() %> <%= copyrightHolder %> 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /generators/npm/templates/license/bsd3: -------------------------------------------------------------------------------- 1 | Copyright (c) <%= (new Date()).getFullYear() %> <%= copyrightHolder %> 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the <%= copyrightHolder %> nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /generators/npm/templates/license/mit: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) <%= (new Date()).getFullYear() %> <%= copyrightHolder %> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /generators/readme/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , parseAuthor = require('parse-author') 5 | , camel = require('camel-case') 6 | , stripScope = require('./strip-scope') 7 | 8 | const self = module.exports = class ReadmeGenerator extends Conditional { 9 | // TODO: use new fs API 10 | writing() { 11 | let pkg = this.fs.readJSON('package.json', false) 12 | 13 | if (!pkg) { 14 | return this.log.error('Cannot create readme file because package.json is missing') 15 | } 16 | 17 | this.packageName = pkg.name 18 | this.camelCaseName = camel(stripScope(pkg.name)) 19 | 20 | let author = pkg.author 21 | , copyrightHolder = this.config.get('copyrightHolder') 22 | 23 | if (typeof author === 'string') author = parseAuthor(author) || {} 24 | 25 | if (copyrightHolder && (!author || copyrightHolder !== author.name)) { 26 | this.authorLink = copyrightHolder 27 | } else if (author) { 28 | if (author.url) this.authorLink = '['+author.name+']('+author.url+')' 29 | else this.authorLink = author.name 30 | } else { 31 | this.authorLink = '' 32 | } 33 | 34 | this.packageLicense = pkg.license 35 | this.packageDescription = pkg.description 36 | 37 | let repo = pkg.repository || '' 38 | , url = typeof repo == 'string' ? repo : repo.url || '' 39 | , match = url.match(/^(.*github.com\/)?([^\.#\/]+\/[^\.#\/$]+)/) 40 | 41 | this.repoName = (match && match[2]) || '' 42 | 43 | if (!this.repoName) { 44 | this.log.info('[readme] No repository name found') 45 | } 46 | 47 | this.hasTravis = this.fs.exists('.travis.yml') 48 | this.hasAppVeyor = this.fs.exists('appveyor.yml') 49 | 50 | this.template('_readme.md', 'readme.md') 51 | } 52 | } 53 | 54 | self.task = 'Create readme file' 55 | self.regenerate = 'Recreate readme file' 56 | 57 | self.shouldRun = function (ctx, opts, done) { 58 | done(null, !ctx.fs.exists('readme.md') && !ctx.fs.exists('readme.markdown') && !ctx.fs.exists('README.md')) 59 | } 60 | -------------------------------------------------------------------------------- /generators/readme/strip-scope.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (name) { 4 | if (name[0] === '@') { 5 | const a = name.split('/') 6 | const scope = a.shift().slice(1) 7 | const rest = a.join('/') 8 | 9 | if (!scope || !rest) { 10 | return name 11 | } 12 | 13 | return rest 14 | } else { 15 | return name 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /generators/readme/templates/_readme.md: -------------------------------------------------------------------------------- 1 | # <%= packageName %> 2 | 3 | **<%= packageDescription %>** 4 | 5 | [![npm status](http://img.shields.io/npm/v/<%= packageName %>.svg?style=flat-square)](https://www.npmjs.org/package/<%= packageName %>) [![node](https://img.shields.io/node/v/<%= packageName %>.svg?style=flat-square)](https://www.npmjs.org/package/<%= packageName %>) <% if (repoName) { %><% if (hasTravis) { %>[![Travis build status](https://img.shields.io/travis/<%= repoName %>.svg?style=flat-square&label=travis)](http://travis-ci.org/<%= repoName %>) <% } %><% if (hasAppVeyor) { %>[![AppVeyor build status](https://img.shields.io/appveyor/ci/<%= repoName %>.svg?style=flat-square&label=appveyor)](https://ci.appveyor.com/project/<%= repoName %>) <% } %>[![Dependency status](https://img.shields.io/david/<%= repoName %>.svg?style=flat-square)](https://david-dm.org/<%= repoName %>)<% } %> 6 | 7 | ## example 8 | 9 | ```js 10 | const <%= camelCaseName %> = require('<%= packageName %>') 11 | ``` 12 | 13 | ## api 14 | 15 | ### `<%= camelCaseName %>(arg, [opts])` 16 | 17 | ## install 18 | 19 | With [npm](https://npmjs.org) do: 20 | 21 | ``` 22 | npm install <%= packageName %> 23 | ``` 24 | 25 | ## license 26 | 27 | [<%= packageLicense %>](http://opensource.org/licenses/<%= packageLicense %>) © <%= authorLink %> 28 | -------------------------------------------------------------------------------- /generators/travis/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Conditional = require('../conditional-subgen') 4 | , travisjs = require.resolve('travisjs/bin/travisjs') 5 | 6 | const self = module.exports = class TravisGenerator extends Conditional { 7 | writing() { 8 | this.copy('_.travis.yml', '.travis.yml') 9 | } 10 | 11 | // The travis tool needs git info, so we need to run this 12 | // after everything else has been written to disk. 13 | end() { 14 | let done = this.async() 15 | this.spawnCommand('node', [travisjs, 'hook']).on('exit', done) 16 | } 17 | } 18 | 19 | self.task = 'Setup Travis' 20 | self.regenerate = 'Setup Travis again' 21 | self.runByDefault = false 22 | 23 | self.shouldRun = function (ctx, opts, done) { 24 | done(null, !ctx.fs.exists('.travis.yml')) 25 | } 26 | -------------------------------------------------------------------------------- /generators/travis/templates/_.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "5" 5 | - "6" 6 | - "7" 7 | - "8" 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-nom", 3 | "version": "2.0.0", 4 | "license": "MIT", 5 | "description": "A modular generator to create or update node modules", 6 | "keywords": [ 7 | "yeoman-generator" 8 | ], 9 | "main": "src/app/index.js", 10 | "files": [ 11 | "src" 12 | ], 13 | "scripts": { 14 | "test": "tape test/*.js", 15 | "prepublish": "npm test" 16 | }, 17 | "author": "Vincent Weevers", 18 | "dependencies": { 19 | "after": "~0.8.1", 20 | "appveyor": "~0.4.0", 21 | "camel-case": "~1.2.0", 22 | "chalk": "~1.1.3", 23 | "configstore": "~1.3.0", 24 | "gift": "~0.7.0", 25 | "latest-version": "~1.0.1", 26 | "lodash.omit": "~4.5.0", 27 | "mkdirp": "~0.5.1", 28 | "normalize-url": "~1.4.0", 29 | "npmconf": "~2.1.2", 30 | "octonode": "~0.7.4", 31 | "pad-right": "~0.2.2", 32 | "param-case": "~1.1.1", 33 | "parse-author": "~0.2.0", 34 | "path-exists": "~2.1.0", 35 | "sorted-object": "~1.0.0", 36 | "stable": "~0.1.5", 37 | "travisjs": "~0.7.0", 38 | "yeoman-generator": "~0.21.1" 39 | }, 40 | "devDependencies": { 41 | "tape": "~4.2.2" 42 | }, 43 | "engines": { 44 | "node": ">=4.3.0", 45 | "npm": ">=2.0.0" 46 | }, 47 | "repository": "ironSource/node-generator-nom", 48 | "bugs": "https://github.com/ironSource/node-generator-nom/issues", 49 | "homepage": "https://github.com/ironSource/node-generator-nom" 50 | } 51 | -------------------------------------------------------------------------------- /test/fixtures/npm/index-cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/npm/index-esm.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default function() { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /test/gulp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | , fs = require('fs') 5 | , u = require('./util') 6 | , run = require('./util/runner')('generators/gulp') 7 | 8 | test('esnext', (t) => { 9 | const ES6 = ['gulpfile.babel.js', '.babelrc'] 10 | , ES5 = ['gulpfile.js'] 11 | 12 | t.test('esnext option true', (t) => { 13 | t.plan(2) 14 | run({ options: { esnext: true }}, () => { 15 | u.files(t, ES6) 16 | u.notFiles(t, ES5) 17 | }) 18 | }) 19 | 20 | t.test('esnext option false', (t) => { 21 | t.plan(2) 22 | run({ options: { esnext: false }}, () => { 23 | u.files(t, ES5) 24 | u.notFiles(t, ES6) 25 | }) 26 | }) 27 | 28 | t.test('esnext prompt true', (t) => { 29 | t.plan(2) 30 | run({ prompts: { esnext: true }}, () => { 31 | u.files(t, ES6) 32 | u.notFiles(t, ES5) 33 | }) 34 | }) 35 | 36 | t.test('esnext prompt false', (t) => { 37 | t.plan(2) 38 | run({ prompts: { esnext: false }}, () => { 39 | u.files(t, ES5) 40 | u.notFiles(t, ES6) 41 | }) 42 | }) 43 | 44 | t.test('esnext option overrides prompt', (t) => { 45 | t.plan(2) 46 | 47 | run({options: { esnext: true }, prompts: { esnext: false }}, () => { 48 | u.files(t, ES6) 49 | u.notFiles(t, ES5) 50 | }) 51 | }) 52 | }) 53 | 54 | test('creates example gulp task', (t) => { 55 | t.plan(1) 56 | run(() => u.files(t, ['tasks/build.js'])) 57 | }) 58 | 59 | test('saves dependencies', (t) => { 60 | t.plan(1) 61 | 62 | run({ options: { esnext: true } }, () => { 63 | let expected = ['gulp', 'gulp-util', 'glob', 'babel-core'] 64 | , pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')) 65 | , actual = Object.keys(pkg.devDependencies) 66 | 67 | t.deepEqual(actual.sort(), expected.sort()) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/npm.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const test = require('tape') 4 | , fs = require('fs') 5 | , u = require('./util') 6 | , run = require('./util/runner')('generators/npm') 7 | 8 | test('main option', (t) => { 9 | function main(option, expected, msg, next) { 10 | run({ options: { main: option }}, (err) => { 11 | if (expected !== null) { 12 | if (option === false) u.notFiles(t, expected, msg) 13 | else u.files(t, expected, msg) 14 | } 15 | next && next(err) 16 | }) 17 | } 18 | 19 | t.plan(6) 20 | 21 | main(true, 'index.js', 'defaults to index.js (1)') 22 | main(undefined, 'index.js', 'defaults to index.js (2)') 23 | main(' // libA/index b.js', 'lib-a/index-b.js') 24 | main(false, 'index.js', 'can be disabled') 25 | main('', null, null, (e) => t.ok(e, 'cannot be empty')) 26 | main('.', null, null, (e) => t.ok(e, 'must be param case')) 27 | }) 28 | 29 | test('modules option', (t) => { 30 | t.plan(6) 31 | 32 | run(() => { 33 | let expected = u.read(u.fixture('npm/index-cjs.js')) 34 | t.equal(u.read('index.js'), expected, 'defaults to CommonJS') 35 | }) 36 | 37 | run({ options: { modules: 'CommonJS'} }, () => { 38 | let expected = u.read(u.fixture('npm/index-cjs.js')) 39 | t.equal(u.read('index.js'), expected, 'CommonJS') 40 | }) 41 | 42 | run({ options: { modules: 'commonjs'} }, () => { 43 | let expected = u.read(u.fixture('npm/index-cjs.js')) 44 | t.equal(u.read('index.js'), expected, 'commonjs') 45 | }) 46 | 47 | run({ options: { modules: 'ES6'} }, () => { 48 | let expected = u.read(u.fixture('npm/index-esm.js')) 49 | t.equal(u.read('index.js'), expected, 'ES6') 50 | }) 51 | 52 | run({ options: { modules: 'es6'} }, () => { 53 | let expected = u.read(u.fixture('npm/index-esm.js')) 54 | t.equal(u.read('index.js'), expected, 'es6') 55 | }) 56 | 57 | run({ options: { modules: 'invalid' }}, (err) => { 58 | t.ok(err, 'invalid option') 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/util/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const exists = require('path-exists') 4 | , fs = require('fs') 5 | , resolve = require('path').resolve 6 | 7 | exports.files = function files(t, files, msg) { 8 | files = [].concat(files) 9 | let passed = files.filter(f => exists.sync(f)) 10 | t.deepEqual(passed, files, msg || files.join(', ')) 11 | } 12 | 13 | exports.notFiles = function notFiles(t, files, msg) { 14 | files = [].concat(files) 15 | let passed = files.filter(f => !exists.sync(f)) 16 | t.deepEqual(passed, files, msg || ('not: ' + files.join(', '))) 17 | } 18 | 19 | exports.fixture = function fixture(path) { 20 | return resolve(__dirname, '..', 'fixtures', path) 21 | } 22 | 23 | exports.read = function read(path, encoding) { 24 | return fs.readFileSync(path, encoding || 'utf8') 25 | } 26 | -------------------------------------------------------------------------------- /test/util/runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const resolve = require('path').resolve 4 | , helpers = require('yeoman-generator').test 5 | , omit = require('lodash.omit') 6 | 7 | // Because Yeoman changes working directory, get it early 8 | const CWD = process.cwd() 9 | 10 | function unhandled(err) { 11 | if (err) throw err 12 | } 13 | 14 | module.exports = function runner (opts) { 15 | if (typeof opts === 'string') opts = { root: opts } 16 | else if (!opts) opts = {} 17 | 18 | const root = opts.root || '.' 19 | const queue = opts.queue || [] 20 | const sharedSpec = omit(opts, ['root', 'queue']) 21 | 22 | function next (fn) { 23 | fn = fn || queue.shift() 24 | if (fn) setImmediate(fn) 25 | else queue.running = false 26 | } 27 | 28 | return function run (generator, spec, done) { 29 | generator = generator || '.' 30 | spec = spec || {} 31 | done = done || unhandled 32 | 33 | if (typeof generator === 'function') { // (done) 34 | done = generator, generator = '.' 35 | } else if (typeof generator !== 'string') { // (spec[, done]) 36 | done = spec || unhandled, spec = generator, generator = '.' 37 | } else if (typeof spec === 'function') { // (generator, done) 38 | done = spec, spec = {} 39 | } 40 | 41 | const merged = Object.assign({}, sharedSpec, spec) 42 | , options = merged.options 43 | , prompts = merged.prompts 44 | , args = merged.args 45 | , config = merged.config 46 | , generators = merged.generators 47 | , path = resolve(CWD, root, generator) 48 | 49 | function start() { 50 | const ctx = helpers.run(path) 51 | , end = (err) => { next(), done(err) } 52 | 53 | if (options) ctx.withOptions(options) 54 | if (prompts) ctx.withPrompts(prompts) 55 | if (args) ctx.withArguments(args) 56 | if (config) ctx.withLocalConfig(config) 57 | if (generators) ctx.withGenerators(generators) 58 | 59 | ctx.on('end', end).on('error', end) 60 | if (start.init) start.init(ctx, merged) 61 | } 62 | 63 | if (!queue.running) { 64 | queue.running = true 65 | next(start) 66 | } else { 67 | queue.push(start) 68 | } 69 | 70 | return (fn) => { start.init = fn } 71 | } 72 | } 73 | --------------------------------------------------------------------------------