├── .gitattributes
├── .jshintignore
├── test
├── fixtures
│ ├── conflict.js
│ ├── dir-css-fixtures
│ │ ├── file1.css
│ │ └── file2.css
│ ├── dummy-project
│ │ ├── subdir
│ │ │ └── .gitkeep
│ │ └── .yo-rc.json
│ ├── foo.js
│ ├── testFile
│ ├── foo-copy.js
│ ├── testFile2
│ ├── file-conflict.txt
│ ├── foo-process.js
│ ├── dir-fixtures
│ │ ├── foo.js
│ │ ├── foo-process.js
│ │ └── foo-template.js
│ ├── template-tags.jst
│ ├── template.jst
│ ├── dummy-package
│ │ ├── index.js
│ │ └── package.json
│ ├── fooooooo-file.js
│ ├── foo-template.js
│ ├── template-setting.xml
│ ├── custom-template-setting.xml
│ ├── append-prepend-to-file.html
│ ├── config.json
│ ├── testFile.tar.gz
│ ├── yeoman-logo.png
│ ├── testRemoteFile.tar.gz
│ ├── lookup-project
│ │ ├── subdir
│ │ │ └── package.json
│ │ └── package.json
│ ├── js_block.html
│ ├── css_block_dir.html
│ ├── css_block.html
│ ├── js_block_with_attr.html
│ ├── js_block_dir.html
│ ├── mocha-generator
│ │ ├── package.json
│ │ └── main.js
│ ├── mocha-generator-base
│ │ ├── package.json
│ │ └── main.js
│ ├── options-generator
│ │ ├── package.json
│ │ └── main.js
│ ├── custom-generator-simple
│ │ ├── package.json
│ │ └── main.js
│ ├── custom-generator-extend
│ │ ├── support
│ │ │ ├── index.js
│ │ │ └── scaffold
│ │ │ │ └── main.js
│ │ └── package.json
│ └── help.txt
├── .jshintrc
├── generators
│ ├── test-testacular-app.js
│ ├── test-mocha-generator.js
│ ├── test-ember-starter.js
│ ├── test-angular.js
│ ├── test-quickstart.js
│ ├── test-bbb.js
│ ├── test-ember.js
│ └── test-backbone.js
├── fetch.js
├── invoke.js
├── user.js
├── conflicter.js
├── install.js
├── generators.js
├── storage.js
├── helpers.js
├── wiring.js
├── remote.js
├── prompt-suggestion.js
├── run-context.js
└── actions.js
├── .gitignore
├── .travis.yml
├── .npmignore
├── benchmark
└── module.js
├── .editorconfig
├── jsdoc.json
├── .jshintrc
├── lib
├── actions
│ ├── spawn_command.js
│ ├── string.js
│ ├── invoke.js
│ ├── file.js
│ ├── user.js
│ ├── fetch.js
│ ├── help.js
│ ├── install.js
│ ├── remote.js
│ ├── wiring.js
│ └── actions.js
├── named-base.js
├── util
│ ├── common.js
│ ├── engines.js
│ ├── storage.js
│ ├── prompt-suggestion.js
│ └── conflicter.js
└── test
│ ├── adapter.js
│ ├── run-context.js
│ └── helpers.js
├── .eslintrc
├── .jscsrc
├── gulpfile.js
├── readme.md
├── index.js
└── package.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 |
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | test/fixtures/**
--------------------------------------------------------------------------------
/test/fixtures/conflict.js:
--------------------------------------------------------------------------------
1 | var a = 1;
2 |
--------------------------------------------------------------------------------
/test/fixtures/dir-css-fixtures/file1.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/dir-css-fixtures/file2.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/dummy-project/subdir/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/foo.js:
--------------------------------------------------------------------------------
1 | var foo = 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/testFile:
--------------------------------------------------------------------------------
1 | Roses are red.
2 |
--------------------------------------------------------------------------------
/test/fixtures/dummy-project/.yo-rc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/test/fixtures/foo-copy.js:
--------------------------------------------------------------------------------
1 | var foo = 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/testFile2:
--------------------------------------------------------------------------------
1 | Violets are blue.
2 |
--------------------------------------------------------------------------------
/test/fixtures/file-conflict.txt:
--------------------------------------------------------------------------------
1 | initial content
2 |
--------------------------------------------------------------------------------
/test/fixtures/foo-process.js:
--------------------------------------------------------------------------------
1 | var foo = 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/dir-fixtures/foo.js:
--------------------------------------------------------------------------------
1 | var foo = 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/template-tags.jst:
--------------------------------------------------------------------------------
1 | ${bar} = <%= foo %>
2 |
--------------------------------------------------------------------------------
/test/fixtures/dir-fixtures/foo-process.js:
--------------------------------------------------------------------------------
1 | var foo = 'foo';
2 |
--------------------------------------------------------------------------------
/test/fixtures/template.jst:
--------------------------------------------------------------------------------
1 | var <%= foo %> = '<%= foo %>';
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | temp/
3 | temp.dev/
4 | coverage/
5 |
--------------------------------------------------------------------------------
/test/fixtures/dummy-package/index.js:
--------------------------------------------------------------------------------
1 | exports.yeoman = "Yo!";
2 |
--------------------------------------------------------------------------------
/test/fixtures/fooooooo-file.js:
--------------------------------------------------------------------------------
1 | var <%= foo %> = '<%= foo %>';
2 |
--------------------------------------------------------------------------------
/test/fixtures/dir-fixtures/foo-template.js:
--------------------------------------------------------------------------------
1 | var <%= foo %> = '<%= foo %>';
2 |
--------------------------------------------------------------------------------
/test/fixtures/dummy-package/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dummy"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/foo-template.js:
--------------------------------------------------------------------------------
1 | var <%= foo %> = '<%= foo %>';
2 | <%%= extra %>
3 |
--------------------------------------------------------------------------------
/test/fixtures/template-setting.xml:
--------------------------------------------------------------------------------
1 | {{= foo }} <%= foo %>;
2 |
--------------------------------------------------------------------------------
/test/fixtures/custom-template-setting.xml:
--------------------------------------------------------------------------------
1 | {{= foo }}{{ spy() }}
2 |
--------------------------------------------------------------------------------
/test/fixtures/append-prepend-to-file.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/fixtures/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "test": {
3 | "name": "test",
4 | "testFramework": "mocha"
5 | }
6 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | env:
5 | global:
6 | - DEBUG="generators:*"
7 |
--------------------------------------------------------------------------------
/test/fixtures/testFile.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/generator/master/test/fixtures/testFile.tar.gz
--------------------------------------------------------------------------------
/test/fixtures/yeoman-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/generator/master/test/fixtures/yeoman-logo.png
--------------------------------------------------------------------------------
/test/fixtures/testRemoteFile.tar.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stefanbuck/generator/master/test/fixtures/testRemoteFile.tar.gz
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test/
2 | doc/
3 | .npmignore
4 | .editorconfig
5 | .travis.yml
6 | .jshintrc
7 | .gitattributes
8 | contributing.md
9 | doc.js
10 |
--------------------------------------------------------------------------------
/test/fixtures/lookup-project/subdir/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lookup-subdir-dummy-env",
3 | "dependencies": {
4 | "generator-dummy": "~0.1.0"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/lookup-project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lookup-dummy-env",
3 | "dependencies": {
4 | "generator-commonjs": "*",
5 | "generator-dummy": "~0.1.0"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/test/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.jshintrc",
3 | "globals": [
4 | "describe",
5 | "it",
6 | "xit",
7 | "before",
8 | "after",
9 | "beforeEach",
10 | "afterEach"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/js_block.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/css_block_dir.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/css_block.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/benchmark/module.js:
--------------------------------------------------------------------------------
1 | /*global suite, bench */
2 | 'use strict';
3 |
4 | suite('yeoman-generator module', function () {
5 | bench('require', function () {
6 | require('..');
7 | delete require.cache[require.resolve('..')];
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/test/fixtures/js_block_with_attr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/test/fixtures/js_block_dir.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/jsdoc.json:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "include": ["index.js", "lib"],
4 | "includePattern": ".+\\.js(doc)?$"
5 | },
6 | "opts": {
7 | "recurse": true,
8 | "destination": "../yeoman-generator-doc/"
9 | },
10 | "plugins": [
11 | "plugins/markdown"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/test/fixtures/mocha-generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "name": "mocha-generator",
4 | "version": "0.0.0",
5 | "main": "main.js",
6 | "dependencies": {},
7 | "devDependencies": {},
8 | "optionalDependencies": {},
9 | "engines": {
10 | "node": "*"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/mocha-generator-base/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "name": "mocha-generator",
4 | "version": "0.0.0",
5 | "main": "main.js",
6 | "dependencies": {},
7 | "devDependencies": {},
8 | "optionalDependencies": {},
9 | "engines": {
10 | "node": "*"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/options-generator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "name": "options-generator",
4 | "version": "0.0.0",
5 | "main": "main.js",
6 | "dependencies": {},
7 | "devDependencies": {},
8 | "optionalDependencies": {},
9 | "engines": {
10 | "node": "*"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/custom-generator-simple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "name": "custom-generator-simple",
4 | "version": "0.0.0",
5 | "main": "main.js",
6 | "dependencies": {},
7 | "devDependencies": {},
8 | "optionalDependencies": {},
9 | "engines": {
10 | "node": "*"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/custom-generator-extend/support/index.js:
--------------------------------------------------------------------------------
1 | var generators = require('../../../..');
2 | var util = require('util');
3 |
4 | var Generator = module.exports = function Generator(args, options) {
5 | generators.NamedBase.apply(this, arguments);
6 | };
7 |
8 | util.inherits(Generator, generators.NamedBase);
9 |
--------------------------------------------------------------------------------
/test/fixtures/custom-generator-extend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "",
3 | "name": "custom-generator-extend",
4 | "version": "0.0.0",
5 | "main": "support/scaffold/main.js",
6 | "dependencies": {},
7 | "devDependencies": {},
8 | "optionalDependencies": {},
9 | "engines": {
10 | "node": "*"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/fixtures/help.txt:
--------------------------------------------------------------------------------
1 | Usage: init GENERATOR [args] [options]
2 |
3 | General options:
4 | -h, --help # Print generator's options and usage
5 | -f, --force # Overwrite files that already exist
6 |
7 | Please choose a generator below.
8 |
9 |
10 | Extend
11 | extend:support:scaffold
12 |
13 | Simple
14 | simple
15 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true,
4 | "bitwise": false,
5 | "curly": false,
6 | "eqeqeq": true,
7 | "eqnull": true,
8 | "immed": true,
9 | "latedef": false,
10 | "newcap": true,
11 | "noarg": true,
12 | "undef": true,
13 | "strict": true,
14 | "quotmark": "single",
15 | "scripturl": true
16 | }
17 |
--------------------------------------------------------------------------------
/lib/actions/spawn_command.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 | var spawn = require('cross-spawn');
4 |
5 | /**
6 | * Normalize a command across OS and spawn it.
7 | *
8 | * @param {String} command
9 | * @param {Array} args
10 | *
11 | * @mixin
12 | * @alias actions/spawn_command
13 | */
14 |
15 | module.exports = function spawnCommand(command, args, opt) {
16 | opt = opt || {};
17 | return spawn(command, args, _.defaults(opt, { stdio: 'inherit' }));
18 | };
19 |
--------------------------------------------------------------------------------
/lib/actions/string.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 | _.str = require('underscore.string');
4 | _.mixin(_.str.exports());
5 |
6 | /**
7 | * @mixin
8 | * @alias actions/string
9 | */
10 | var string = module.exports;
11 |
12 | /**
13 | * Mix in non-conflicting functions to underscore namespace and generators.
14 | *
15 | * @mixes lodash
16 | * @mixes underscore.string
17 | * @example
18 | *
19 | * this._.humanize('stuff-dash');
20 | * this._.classify('hello-model');
21 | */
22 |
23 | string._ = _;
24 |
--------------------------------------------------------------------------------
/test/generators/test-testacular-app.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Testacular:App generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('testacular:app', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('testacular.conf.js');
16 |
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/test/fixtures/custom-generator-extend/support/scaffold/main.js:
--------------------------------------------------------------------------------
1 |
2 | // in real use case, users need to require `yeoman-generators`
3 | // var generators = require('yeoman-generators');
4 | var generators = require('../../../../../');
5 | var util = require('util');
6 |
7 | var Generator = module.exports = function Generator(args, options) {
8 | generators.NamedBase.apply(this, arguments);
9 | // arguments === this.arguments
10 | // options === this.options
11 | };
12 |
13 | // namespace
14 | // Generator.namespace = 'custom-generator-extend';
15 |
16 | util.inherits(Generator, generators.NamedBase);
17 |
--------------------------------------------------------------------------------
/test/fixtures/mocha-generator-base/main.js:
--------------------------------------------------------------------------------
1 |
2 | // in real use case, users need to require `yeoman-generators`
3 | // var generators = require('yeoman-generators');
4 | var generators = require('../../../');
5 | var util = require('util');
6 |
7 | var Generator = module.exports = function Generator(args, options) {
8 | generators.NamedBase.apply(this, arguments);
9 | // arguments === this.arguments
10 | // options === this.options
11 | };
12 |
13 | // namespace
14 | Generator.namespace = 'mocha:generator-base';
15 |
16 | util.inherits(Generator, generators.NamedBase);
17 |
18 | Generator.prototype.doStuff = function() {};
19 |
--------------------------------------------------------------------------------
/lib/named-base.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var Base = require('./base');
3 |
4 | /**
5 | * The `NamedBase` object is only dealing with one argument: `name`.
6 | *
7 | * You can use it whenever you need at least one **required** positional
8 | * argument for your generator (which is a fairly common use case).
9 | *
10 | * @constructor
11 | * @augments Base
12 | * @alias NamedBase
13 | * @param {String|Array} args [description]
14 | * @param {Object} options [description]
15 | */
16 |
17 | module.exports = Base.extend({
18 | constructor: function () {
19 | Base.apply(this, arguments);
20 | this.argument('name', { type: String, required: true });
21 | }
22 | });
23 |
--------------------------------------------------------------------------------
/test/generators/test-mocha-generator.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Mocha:Generator generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('mocha:generator', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | // Use helpers.assertFile() to help you test the output of your generator
16 | //
17 | // Example:
18 | //
19 | // // check file exists
20 | // helpers.assertFile('app/model/post.js');
21 | // // Check content
22 | // helpers.assertFile('app/model/post.js', /Backbone\.model/);
23 | it('should create expected files');
24 |
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/lib/util/common.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var chalk = require('chalk');
3 |
4 | /**
5 | * Common/General utilities
6 | *
7 | * @mixin util/common
8 | */
9 | var common = module.exports;
10 |
11 | /**
12 | * 'Welcome to Yeoman' prompt intro
13 | * @type {String}
14 | * @memberof util/common
15 | */
16 | common.yeoman =
17 | '\n _-----_' +
18 | '\n | |' +
19 | '\n |' + chalk.red('--(o)--') + '| .--------------------------.' +
20 | '\n `---------´ | ' + chalk.yellow.bold('Welcome to Yeoman') + ', |' +
21 | '\n ' + chalk.yellow('(') + ' _' + chalk.yellow('´U`') + '_ ' + chalk.yellow(')') + ' | ' + chalk.yellow.bold('ladies and gentlemen!') + ' |' +
22 | '\n /___A___\\ \'__________________________\'' +
23 | '\n ' + chalk.yellow('| ~ |') +
24 | '\n __' + chalk.yellow('\'.___.\'') + '__' +
25 | '\n ´ ' + chalk.red('` |') + '° ' + chalk.red('´ Y') + ' `\n';
26 |
--------------------------------------------------------------------------------
/test/fixtures/options-generator/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Example of a generator with options.
4 | //
5 | // It takes a list of arguments (usually CLI args) and a Hash of options
6 | // (CLI options), the context of the function is a `new Generator.Base`
7 | // object, which means that you can use the API as if you were extending
8 | // `Base`.
9 |
10 | var yeoman = require('../../../');
11 |
12 | module.exports = yeoman.generators.Base.extend({
13 | constructor: function () {
14 | yeoman.generators.Base.apply(this, arguments);
15 |
16 | // this.log('as passed in: ', this.options.testOption);
17 | this.option('testOption', {
18 | type: Boolean,
19 | desc: 'Testing falsey values for option',
20 | defaults: true
21 | });
22 | },
23 |
24 | testOption: function () {
25 | // this.log('as rendered: ', this.options.testOption);
26 | }
27 | });
28 |
29 | module.exports.namespace = 'options:generator';
30 |
--------------------------------------------------------------------------------
/test/generators/test-ember-starter.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Ember-Starter generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('ember-starter', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('app/scripts');
16 |
17 | helpers.assertFile('app/styles');
18 |
19 | helpers.assertFile('app/scripts/app.js');
20 |
21 | helpers.assertFile('app/scripts/libs/ember-1.0.pre.js');
22 |
23 | helpers.assertFile('app/scripts/libs/handlebars-1.0.0.beta.6.js');
24 |
25 | helpers.assertFile('app/scripts/libs/jquery-1.7.2.min.js');
26 |
27 | helpers.assertFile('app/index.html');
28 |
29 | helpers.assertFile('app/styles/style.css');
30 |
31 | helpers.assertFile('Gruntfile.js');
32 |
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "amd": true,
4 | "mocha": true,
5 | "node": true
6 | },
7 | "rules": {
8 | "camelcase": 0,
9 | "consistent-return": 0,
10 | "curly": 0,
11 | "eol-last":0,
12 | "eqeqeq": [2, "smart"],
13 | "handle-callback-err": 0,
14 | "no-alert": 2,
15 | "no-bitwise": 0,
16 | "new-cap": 2,
17 | "no-caller": 2,
18 | "no-console": 0,
19 | "no-debugger": 1,
20 | "no-dupe-keys": 2,
21 | "no-eq-null": 0,
22 | "no-extra-bind": 0,
23 | "no-extra-parens": 0,
24 | "no-mixed-spaces-and-tabs": [2, true],
25 | "no-new": 0,
26 | "no-path-concat": 0,
27 | "no-process-exit": 0,
28 | "no-trailing-spaces": 2,
29 | "no-undef": 2,
30 | "no-underscore-dangle": 0,
31 | "no-unreachable": 1,
32 | "no-unused-vars": [2, {"vars": "all", "args": "none"}],
33 | "no-use-before-define": 2,
34 | "quotes": [2, "single"],
35 | "strict": 2,
36 | "valid-jsdoc": [2, { "requireReturn": false, "requireParamDescription": false }],
37 | "wrap-iife": [2, "any"],
38 | "yoda": 0
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/test/fixtures/custom-generator-simple/main.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // Example of a simple generator.
4 | //
5 | // A raw function that is executed when this generator is resolved.
6 | //
7 | // It takes a list of arguments (usually CLI args) and a Hash of options
8 | // (CLI options), the context of the function is a `new Generator.Base`
9 | // object, which means that you can use the API as if you were extending
10 | // `Base`.
11 | //
12 | // It works with simple generator. If you need to do a bit more complex
13 | // stuff, extend from Generator.Base and defines your generator steps
14 | // in several methods.
15 |
16 | module.exports = function(args, options) {
17 | console.log('Executing generator with', args, options);
18 | };
19 |
20 | module.exports.name = 'You can name your generator';
21 | module.exports.description = 'And add a custom description by adding a `description` property to your function.';
22 | module.exports.usage = 'Usage can be used to customize the help output';
23 |
24 | // namespace is resolved depending on the location of this generator,
25 | // unless you specifically define it.
26 | // module.exports.namespace = 'custom-generator';
27 |
--------------------------------------------------------------------------------
/lib/actions/invoke.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * Receives a `namespace`, and an Hash of `options` to invoke a given
5 | * generator. The usual list of arguments can be set with `options.args`
6 | * (ex. nopt's argv.remain array)
7 | *
8 | * DEPRECATION notice: As of version 0.17.0, `invoke()` should usually be
9 | * replaced by `composeWith()`.
10 | *
11 | * @param {String} namespace
12 | * @param {Object} options
13 | * @param {Function} cb
14 | *
15 | * @mixin
16 | * @alias actions/invoke
17 | */
18 |
19 | module.exports = function invoke(namespace, options, cb) {
20 | cb = cb || function () {};
21 | options = options || {};
22 | options.args = options.args || [];
23 |
24 | // Hack: create a clone of the environment because we don't want to share
25 | // the runLoop
26 | var env = require('yeoman-environment').util.duplicateEnv(this.env);
27 | var generator = env.create(namespace, options);
28 |
29 | this.log.emit('up');
30 | this.log.invoke(namespace);
31 | this.log.emit('up');
32 |
33 | generator.on('end', this.log.emit.bind(this.log, 'down'));
34 | generator.on('end', this.log.emit.bind(this.log, 'down'));
35 |
36 | return generator.run(cb);
37 | };
38 |
--------------------------------------------------------------------------------
/test/fixtures/mocha-generator/main.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | // Example of a simple generator.
4 | //
5 | // A raw function that is executed when this generator is resolved.
6 | //
7 | // It takes a list of arguments (usually CLI args) and a Hash of options
8 | // (CLI options), the context of the function is a `new Generator.Base`
9 | // object, which means that you can use the API as if you were extending
10 | // `Base`.
11 | //
12 | // It works with simple generator, if you need to do a bit more complex
13 | // stuff, extends from Generator.Base and defines your generator steps
14 | // in several methods.
15 | var util = require('util');
16 | var generators = require('../../../');
17 |
18 | module.exports = function(args, options) {
19 | generators.Base.apply(this, arguments);
20 | console.log('Executing generator with', args, options);
21 | };
22 | util.inherits(module.exports, generators.Base);
23 |
24 | module.exports.name = 'You can name your generator';
25 | module.exports.description = 'Ana add a custom description by adding a `description` property to your function.';
26 | module.exports.usage = 'Usage can be used to customize the help output';
27 |
28 | // namespace is resolved depending on the location of this generator,
29 | // unless you specifically define it.
30 | module.exports.namespace = 'mocha:generator';
31 |
--------------------------------------------------------------------------------
/test/generators/test-angular.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 | var generators = require('../..');
6 |
7 | describe('Angular generator test', function () {
8 | // cleanup the temp dir and cd into it
9 | before(helpers.before(path.join(__dirname, './temp')));
10 |
11 | before(function (done) {
12 | // setup the environment
13 | this.env = generators().lookup('*:*');
14 | this.env.run('angular:all foo bar', done);
15 | });
16 |
17 | it('creates expected files', function () {
18 | helpers.assertFile('app/.htaccess');
19 | helpers.assertFile('app/404.html');
20 | helpers.assertFile('app/favicon.ico');
21 | helpers.assertFile('app/robots.txt');
22 | helpers.assertFile('app/scripts/vendor/angular.js');
23 | helpers.assertFile('app/scripts/vendor/angular.min.js');
24 | helpers.assertFile('app/styles/main.css');
25 | helpers.assertFile('app/views/main.html');
26 | helpers.assertFile('Gruntfile.js');
27 | helpers.assertFile('package.json');
28 | helpers.assertFile('test/vendor/angular-mocks.js');
29 | helpers.assertFile('app/scripts/app.js');
30 | helpers.assertFile('app/index.html');
31 | helpers.assertFile('app/scripts/controllers/main.js');
32 | helpers.assertFile('test/spec/controllers/main.js');
33 | helpers.assertFile('testacular.conf.js');
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"],
3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch", "function"],
4 | "requireSpacesInFunctionExpression": {
5 | "beforeOpeningCurlyBrace": true
6 | },
7 | "disallowMultipleVarDecl": true,
8 | "requireSpacesInsideObjectBrackets": "allButNested",
9 | "disallowSpacesInsideArrayBrackets": true,
10 | "disallowSpacesInsideParentheses": true,
11 | "disallowSpaceAfterObjectKeys": true,
12 | "disallowQuotedKeysInObjects": true,
13 | "requireSpaceBeforeBinaryOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
14 | "disallowSpaceAfterBinaryOperators": ["!"],
15 | "requireSpaceAfterBinaryOperators": ["?", ",", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
16 | "disallowSpaceBeforeBinaryOperators": [","],
17 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
18 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
19 | "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
20 | "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
21 | "disallowImplicitTypeConversion": ["numeric", "binary", "string"],
22 | "disallowKeywords": ["with", "eval"],
23 | "disallowMultipleLineBreaks": true,
24 | "disallowKeywordsOnNewLine": ["else"],
25 | "requireLineFeedAtFileEnd": true,
26 | "excludeFiles": ["node_modules/**", "bower_components/**"],
27 | "validateIndentation": 2
28 | }
29 |
--------------------------------------------------------------------------------
/lib/actions/file.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fs = require('fs');
3 | var path = require('path');
4 | var glob = require('glob');
5 | var mkdirp = require('mkdirp');
6 |
7 | /**
8 | * @mixin
9 | * @alias actions/file
10 | */
11 | var file = module.exports;
12 |
13 | /**
14 | * Performs a glob search with the provided pattern and optional Hash of
15 | * options. Options can be any option supported by
16 | * [glob](https://github.com/isaacs/node-glob#options)
17 | *
18 | * @param {String} pattern
19 | * @param {Object} options
20 | */
21 |
22 | file.expand = function expand(pattern, options) {
23 | return glob.sync(pattern, options);
24 | };
25 |
26 | /**
27 | * Performs a glob search with the provided pattern and optional Hash of
28 | * options, filtering results to only return files (not directories). Options
29 | * can be any option supported by
30 | * [glob](https://github.com/isaacs/node-glob#options)
31 | *
32 | * @param {String} pattern
33 | * @param {Object} options
34 | */
35 |
36 | file.expandFiles = function expandFiles(pattern, options) {
37 | options = options || {};
38 | var cwd = options.cwd || process.cwd();
39 | return this.expand(pattern, options).filter(function (filepath) {
40 | return fs.statSync(path.join(cwd, filepath)).isFile();
41 | });
42 | };
43 |
44 | /**
45 | * Checks a given file path being absolute or not.
46 | * Borrowed from grunt's file API.
47 | */
48 |
49 | file.isPathAbsolute = function () {
50 | var filepath = path.join.apply(path, arguments);
51 | return path.resolve(filepath) === filepath;
52 | };
53 |
54 | file.mkdir = mkdirp.sync.bind(mkdirp);
55 |
--------------------------------------------------------------------------------
/lib/actions/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 | var shell = require('shelljs');
4 | var githubUsername = require('github-username');
5 |
6 | var nameCache = {};
7 | var emailCache = {};
8 |
9 | /**
10 | * @mixin
11 | * @alias actions/user
12 | */
13 | var user = module.exports;
14 |
15 | user.git = {};
16 | user.github = {};
17 |
18 | /**
19 | * Retrieves user's name from Git in the global scope or the project scope
20 | * (it'll take what Git will use in the current context)
21 | */
22 |
23 | user.git.name = function () {
24 | var name = nameCache[process.cwd()];
25 |
26 | if (name) {
27 | return name;
28 | }
29 |
30 | if (shell.which('git')) {
31 | name = shell.exec('git config --get user.name', { silent: true }).output.trim();
32 | nameCache[process.cwd()] = name;
33 | }
34 |
35 | return name;
36 | };
37 |
38 | /**
39 | * Retrieves user's email from Git in the global scope or the project scope
40 | * (it'll take what Git will use in the current context)
41 | */
42 |
43 | user.git.email = function () {
44 | var email = emailCache[process.cwd()];
45 |
46 | if (email) {
47 | return email;
48 | }
49 |
50 | if (shell.which('git')) {
51 | email = shell.exec('git config --get user.email', { silent: true }).output.trim();
52 | emailCache[process.cwd()] = email;
53 | }
54 |
55 | return email;
56 | };
57 |
58 | /**
59 | * Retrieves GitHub's username from the GitHub API.
60 | */
61 |
62 | user.github.username = function () {
63 | var args = _.toArray(arguments);
64 | args.unshift(user.git.email());
65 | return githubUsername.apply(null, args);
66 | };
67 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var gulp = require('gulp');
4 | var mocha = require('gulp-mocha');
5 | var jshint = require('gulp-jshint');
6 | var jscs = require('gulp-jscs');
7 | var eslint = require('gulp-eslint');
8 | var istanbul = require('gulp-istanbul');
9 | var coveralls = require('gulp-coveralls');
10 | var plumber = require('gulp-plumber');
11 |
12 | var handleErr = function (err) {
13 | console.log(err.message);
14 | process.exit(1);
15 | };
16 |
17 | gulp.task('static', function () {
18 | return gulp.src([
19 | 'test/*.js',
20 | 'lib/**/*.js',
21 | 'benchmark/**/*.js',
22 | 'index.js',
23 | 'doc.js',
24 | 'gulpfile.js'
25 | ])
26 | .pipe(jshint('.jshintrc'))
27 | .pipe(jshint.reporter('jshint-stylish'))
28 | .pipe(jshint.reporter('fail'))
29 | .pipe(jscs())
30 | .on('error', handleErr)
31 | .pipe(eslint())
32 | .pipe(eslint.format())
33 | .pipe(eslint.failOnError());
34 | });
35 |
36 | gulp.task('test', function (cb) {
37 | gulp.src([
38 | 'lib/**/*.js',
39 | 'index.js'
40 | ])
41 | .pipe(istanbul({
42 | includeUntested: true
43 | }))
44 | .pipe(istanbul.hookRequire())
45 | .on('finish', function () {
46 | gulp.src(['test/*.js'])
47 | .pipe(plumber())
48 | .pipe(mocha({
49 | reporter: 'spec'
50 | }))
51 | .pipe(istanbul.writeReports())
52 | .on('end', cb);
53 | });
54 | });
55 |
56 | gulp.task('coveralls', ['test'], function () {
57 | if (!process.env.CI) return;
58 | return gulp.src(path.join(__dirname, 'coverage/lcov.info'))
59 | .pipe(coveralls());
60 | });
61 |
62 | gulp.task('default', ['static', 'test', 'coveralls']);
63 |
--------------------------------------------------------------------------------
/lib/util/engines.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 |
4 | // TODO(mklabs):
5 | // - handle cache
6 | // - implement adpaters for others engines (but do not add hard deps on them,
7 | // should require manual install for anything that is not an underscore
8 | // template)
9 | // - put in multiple files, possibly other packages.
10 |
11 | var engines = module.exports;
12 |
13 | // Underscore
14 | // ----------
15 |
16 | // Underscore templates facade.
17 | //
18 | // Special kind of markers `<%%` for opening tags can be used to escape the
19 | // placeholder opening tag. This is often useful for templates including
20 | // snippet of templates you don't want to be interpolated.
21 |
22 | engines.underscore = function underscore(source, data, options) {
23 | source = source.replace(engines.underscore.options.matcher, function (m, content) {
24 | // let's add some funny markers to replace back when templating is done,
25 | // should be fancy enough to reduce frictions with files using markers like
26 | // this already.
27 | return '(;>%%<;)' + content + '(;>%<;)';
28 | });
29 |
30 | //let the user an option to use settings of _.template
31 | source = _.template(source, null, options)(data);
32 |
33 | source = source
34 | .replace(/\(;>%%<;\)/g, engines.underscore.options.start)
35 | .replace(/\(;>%<;\)/g, engines.underscore.options.end);
36 |
37 | return source;
38 | };
39 |
40 | engines.underscore.options = {
41 | matcher: /<%%([^%]+)%>/g,
42 | detecter: /<%%?[^%]+%>/,
43 | start: '<%',
44 | end: '%>'
45 | };
46 |
47 | engines.underscore.detect = function detect(body) {
48 | return engines.underscore.options.detecter.test(body);
49 | };
50 |
--------------------------------------------------------------------------------
/test/generators/test-quickstart.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Quickstart generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('quickstart', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('.editorconfig');
16 |
17 | helpers.assertFile('.gitattributes');
18 |
19 | helpers.assertFile('.gitignore');
20 |
21 | helpers.assertFile('.jshintrc');
22 |
23 | helpers.assertFile('app/.htaccess');
24 |
25 | helpers.assertFile('app/404.html');
26 |
27 | helpers.assertFile('app/favicon.ico');
28 |
29 | helpers.assertFile('app/index.html');
30 |
31 | helpers.assertFile('app/robots.txt');
32 |
33 | helpers.assertFile('app/scripts/vendor/jquery.min.js');
34 |
35 | helpers.assertFile('app/scripts/vendor/modernizr.min.js');
36 |
37 | helpers.assertFile('app/styles/main.css');
38 |
39 | helpers.assertFile('Gruntfile.js');
40 |
41 | helpers.assertFile('package.json');
42 |
43 | helpers.assertFile('test/index.html');
44 |
45 | helpers.assertFile('test/lib/chai.js');
46 |
47 | helpers.assertFile('test/lib/expect.js');
48 |
49 | helpers.assertFile('test/lib/mocha/mocha.css');
50 |
51 | helpers.assertFile('test/lib/mocha/mocha.js');
52 |
53 | helpers.assertFile('test/runner/mocha.js');
54 |
55 | helpers.assertFile('test/spec/.gitkeep');
56 |
57 | });
58 | });
59 |
--------------------------------------------------------------------------------
/test/fetch.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, beforeEach, it */
2 | 'use strict';
3 | var fs = require('fs');
4 | var path = require('path');
5 | var tmpdir = require('os').tmpdir();
6 | var nock = require('nock');
7 | var yeoman = require('yeoman-environment');
8 | var generators = require('..');
9 | var assert = generators.assert;
10 | var fetch = require('../lib/actions/fetch');
11 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
12 |
13 | var tmp = path.join(tmpdir, 'yeoman-fetch');
14 |
15 | describe('generators.Base (actions/fetch)', function () {
16 | beforeEach(function () {
17 | this.dummy = new generators.Base({
18 | env: yeoman.createEnv([], {}, new TestAdapter()),
19 | resolved: 'test:fetch'
20 | });
21 | });
22 |
23 | describe('#tarball()', function () {
24 | it('download and untar via the NPM download package', function (done) {
25 | var scope = nock('http://example.com')
26 | .get('/f.tar.gz')
27 | .replyWithFile(200, path.join(__dirname, 'fixtures/testFile.tar.gz'));
28 |
29 | this.dummy.tarball('http://example.com/f.tar.gz', tmp, function (err) {
30 | if (err) return done(err);
31 | assert(scope.isDone());
32 | done();
33 | });
34 | });
35 |
36 | it('aliases #extract()', function () {
37 | assert.equal(fetch.tarball, fetch.extract);
38 | });
39 | });
40 |
41 | describe('#fetch()', function () {
42 | it('allow the fetching of a single file', function (done) {
43 | var scope = nock('http://example.com')
44 | .get('/f.txt')
45 | .replyWithFile(200, path.join(__dirname, 'fixtures/help.txt'));
46 |
47 | this.dummy.fetch('http://example.com/f.txt', tmp, function (err) {
48 | if (err) return done(err);
49 | assert(scope.isDone());
50 | fs.stat(path.join(tmp, 'f.txt'), done);
51 | });
52 | });
53 | });
54 |
55 | });
56 |
--------------------------------------------------------------------------------
/lib/actions/fetch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 | var Download = require('download');
4 | var chalk = require('chalk');
5 |
6 | /**
7 | * @mixin
8 | * @alias actions/fetch
9 | */
10 | var fetch = module.exports;
11 |
12 | /**
13 | * Download a file to a given destination.
14 | *
15 | * @param {String} url
16 | * @param {String} destination
17 | * @param {Function} cb
18 | */
19 |
20 | fetch.fetch = function _fetch(url, destination, cb) {
21 | var log = this.log('... Fetching %s ...', url);
22 | var download = new Download()
23 | .get(url)
24 | .dest(destination)
25 | .use(function (res) {
26 | res.on('data', function () {
27 | log.write('.');
28 | });
29 | });
30 |
31 | download.run(function (err) {
32 | if (err) return cb(err);
33 |
34 | log.ok('Done in ' + destination).write();
35 | cb();
36 | });
37 | };
38 |
39 | /**
40 | * Fetch an archive and extract it to a given destination.
41 | *
42 | * @param {String} archive
43 | * @param {String} destination
44 | * @param {Object} opts
45 | * @param {Function} cb
46 | */
47 |
48 | fetch.extract = function _extract(archive, destination, opts, cb) {
49 | if (_.isFunction(opts) && !cb) {
50 | cb = opts;
51 | opts = { extract: true };
52 | }
53 |
54 | opts = _.assign({ extract: true }, opts);
55 |
56 | var log = this.log.write()
57 | .info('... Fetching %s ...', archive)
58 | .info(chalk.yellow('This might take a few moments'));
59 |
60 | var download = new Download(opts)
61 | .get(archive)
62 | .dest(destination)
63 | .use(function (res) {
64 | res.on('data', function () {
65 | log.write('.');
66 | });
67 | });
68 |
69 | download.run(function (err) {
70 | if (err) return cb(err);
71 |
72 | log.write().ok('Done in ' + destination).write();
73 | cb();
74 | });
75 | };
76 |
77 | /** @alias fetch.extract */
78 | fetch.tarball = fetch.extract;
79 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Generator [](http://travis-ci.org/yeoman/generator) [](https://coveralls.io/r/yeoman/generator)
2 |
3 | > A Rails-inspired generator system that provides scaffolding for your apps.
4 |
5 |
6 | ### [Getting Started](http://yeoman.io/authoring/getting-started.html) [API Documentation](http://yeoman.github.io/generator/)
7 |
8 |
9 | 
10 |
11 | 
12 |
13 |
14 | ## Getting Started
15 |
16 | If you're interested in writing your own Yeoman generator we recommend reading [the official getting started guide](http://yeoman.io/authoring/).
17 |
18 | There are typically two types of generators - simple boilerplate 'copiers' and more advanced generators which can use custom prompts, remote dependencies, wiring and much more.
19 |
20 | The docs cover how to create generators from scratch as well as recommending command-line generators for making other generators.
21 |
22 | For deeper research, read the code source or visit our [API documentation](http://yeoman.github.io/generator/).
23 |
24 |
25 | ### Debugging
26 |
27 | To debug a generator, you can pass Node.js debug's flags by running it like this:
28 |
29 | ```sh
30 | # OS X / Linux
31 | node --debug `which yo` [arguments]
32 |
33 | # Windows
34 | node --debug [arguments]
35 | ```
36 |
37 | Yeoman generators also use a debug mode to log relevant informations. You can activate it by setting the `DEBUG` environment variable to the desired scope (for the generator system scope is `generators:*`).
38 |
39 | ```sh
40 | # OS X / Linux
41 | DEBUG=generators/*
42 |
43 | # Windows
44 | set DEBUG=generators/*
45 | ```
46 |
47 |
48 | ## License
49 |
50 | [BSD license](http://opensource.org/licenses/bsd-license.php)
51 | Copyright (c) Google
52 |
--------------------------------------------------------------------------------
/lib/test/adapter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _ = require('lodash');
4 | var inquirer = require('inquirer');
5 | var sinon = require('sinon');
6 | var events = require('events');
7 |
8 | function DummyPrompt(answers, q) {
9 | this.answers = answers;
10 | this.question = q;
11 | }
12 |
13 | DummyPrompt.prototype.run = function (cb) {
14 | var answer = this.answers[this.question.name];
15 |
16 | var isSet;
17 | switch (this.question.type) {
18 | case 'list':
19 | // list prompt accepts any answer value including null
20 | isSet = answer !== undefined;
21 | break;
22 | case 'confirm':
23 | // ensure that we don't replace `false` with default `true`
24 | isSet = answer || answer === false;
25 | break;
26 | default:
27 | // other prompts treat all falsy values to default
28 | isSet = !!answer;
29 | }
30 |
31 | if (!isSet) {
32 | answer = this.question.default;
33 | if (answer === undefined && this.question.type === 'confirm') {
34 | answer = true;
35 | }
36 | }
37 |
38 | setImmediate(function () {
39 | cb(answer);
40 | });
41 | };
42 |
43 | function TestAdapter(answers) {
44 |
45 | answers = answers || {};
46 |
47 | this.prompt = inquirer.createPromptModule();
48 |
49 | Object.keys(this.prompt.prompts).forEach(function (promptName) {
50 | this.prompt.registerPrompt(promptName, DummyPrompt.bind(DummyPrompt, answers));
51 | }, this);
52 |
53 | this.diff = sinon.spy();
54 |
55 | this.log = sinon.spy();
56 |
57 | _.extend(this.log, events.EventEmitter.prototype);
58 |
59 | // make sure all log methods are defined
60 | [
61 | 'write',
62 | 'writeln',
63 | 'ok',
64 | 'error',
65 | 'skip',
66 | 'force',
67 | 'create',
68 | 'invoke',
69 | 'conflict',
70 | 'identical',
71 | 'info',
72 | 'table'
73 | ].forEach(function (methodName) {
74 | this.log[methodName] = sinon.stub().returns(this.log);
75 | }, this);
76 |
77 | }
78 |
79 | module.exports = {
80 | DummyPrompt: DummyPrompt,
81 | TestAdapter: TestAdapter
82 | };
83 |
--------------------------------------------------------------------------------
/test/generators/test-bbb.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Bbb generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('bbb', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('.editorconfig');
16 |
17 | helpers.assertFile('.gitattributes');
18 |
19 | helpers.assertFile('.gitignore');
20 |
21 | helpers.assertFile('.jshintrc');
22 |
23 | helpers.assertFile('app/.htaccess');
24 |
25 | helpers.assertFile('app/404.html');
26 |
27 | helpers.assertFile('app/favicon.ico');
28 |
29 | helpers.assertFile('app/index.html');
30 |
31 | helpers.assertFile('app/robots.txt');
32 |
33 | helpers.assertFile('app/scripts/app.js');
34 |
35 | helpers.assertFile('app/scripts/config.js');
36 |
37 | helpers.assertFile('app/scripts/libs/almond.js');
38 |
39 | helpers.assertFile('app/scripts/libs/backbone.js');
40 |
41 | helpers.assertFile('app/scripts/libs/jquery.js');
42 |
43 | helpers.assertFile('app/scripts/libs/lodash.js');
44 |
45 | helpers.assertFile('app/scripts/libs/require.js');
46 |
47 | helpers.assertFile('app/scripts/main.js');
48 |
49 | helpers.assertFile('app/scripts/plugins/backbone.layoutmanager.js');
50 |
51 | helpers.assertFile('app/scripts/router.js');
52 |
53 | helpers.assertFile('app/styles/h5bp.css');
54 |
55 | helpers.assertFile('app/styles/index.css');
56 |
57 | helpers.assertFile('Gruntfile.js');
58 |
59 | helpers.assertFile('package.json');
60 |
61 | helpers.assertFile('test/index.html');
62 |
63 | helpers.assertFile('test/lib/chai.js');
64 |
65 | helpers.assertFile('test/lib/expect.js');
66 |
67 | helpers.assertFile('test/lib/mocha/mocha.css');
68 |
69 | helpers.assertFile('test/lib/mocha/mocha.js');
70 |
71 | helpers.assertFile('test/runner/mocha.js');
72 |
73 | helpers.assertFile('test/spec/example.js');
74 |
75 | });
76 | });
77 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module yeoman-generator
3 | */
4 |
5 | 'use strict';
6 |
7 | var Environment = require('yeoman-environment');
8 |
9 | /**
10 | * The generator system is a framework for node to author reusable and
11 | * composable Generators, for a vast majority of use-case.
12 | *
13 | * Inspired and based off the work done on Thor and Rails 3 Generators, we try
14 | * to provide the same kind of infrastructure.
15 | *
16 | * Generators are registered by namespace, where namespaces are mapping the
17 | * structure of the file system with `:` being simply converted to `/`.
18 | *
19 | * Generators are standard node modules, they are simply required as usual, and
20 | * they can be shipped into reusable npm packages.
21 | *
22 | * The lookup is done depending on the configured load path, which is by
23 | * default `lib/generators` in every generators package installed (ie.
24 | * `node_modules/yeoman-backbone/lib/generators`)
25 | *
26 | * @example
27 | * var yeoman = require('yeoman-generators');
28 | *
29 | * var env = yeoman('angular:model')
30 | * .run(function(err) {
31 | * console.log('done!');
32 | * });
33 | *
34 | * @alias module:yeoman-generator
35 | */
36 |
37 | var yeoman = module.exports = function createEnv() {
38 | return Environment.createEnv.apply(Environment, arguments);
39 | };
40 |
41 | /**
42 | * Global file helpers methods
43 | * {@link https://github.com/SBoudrias/file-utils}
44 | */
45 | yeoman.file = require('file-utils');
46 |
47 | // hoist up top level class the generator extend
48 | yeoman.Base = require('./lib/base');
49 | yeoman.NamedBase = require('./lib/named-base');
50 |
51 | /**
52 | * Test helpers
53 | * {@link module:test/helpers}
54 | */
55 | yeoman.test = require('./lib/test/helpers');
56 |
57 | /**
58 | * Test assertions helpers
59 | * {@link https://github.com/yeoman/yeoman-assert}
60 | */
61 | yeoman.assert = require('yeoman-assert');
62 |
63 | /**
64 | * Yeoman base's generators
65 | * @enum generators
66 | */
67 | yeoman.generators = {
68 |
69 | /**
70 | * Base Generator
71 | * {@link Base}
72 | */
73 | Base: yeoman.Base,
74 |
75 | /**
76 | * Named Base Generator
77 | * {@link NamedBase}
78 | */
79 | NamedBase: yeoman.NamedBase
80 | };
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yeoman-generator",
3 | "version": "0.18.5",
4 | "description": "Rails-inspired generator system that provides scaffolding for your apps",
5 | "license": "BSD",
6 | "keywords": [
7 | "development",
8 | "dev",
9 | "build",
10 | "tool",
11 | "cli",
12 | "scaffold",
13 | "scaffolding",
14 | "generate",
15 | "generator",
16 | "yeoman",
17 | "app"
18 | ],
19 | "homepage": "http://yeoman.io",
20 | "author": "The Yeoman Team",
21 | "repository": "yeoman/generator",
22 | "engines": {
23 | "node": ">=0.10.0"
24 | },
25 | "scripts": {
26 | "test": "gulp",
27 | "doc": "jsdoc -c ./jsdoc.json ./readme.md",
28 | "test-generator": "mocha test/generators/*.js --reporter spec --timeout 100000",
29 | "prepublish": "nsp audit-package"
30 | },
31 | "dependencies": {
32 | "async": "^0.9.0",
33 | "chalk": "^0.5.1",
34 | "cheerio": "^0.18.0",
35 | "class-extend": "^0.1.0",
36 | "cross-spawn": "^0.2.3",
37 | "dargs": "^3.0.0",
38 | "debug": "^2.1.0",
39 | "detect-conflict": "^1.0.0",
40 | "diff": "^1.0.4",
41 | "download": "^3.1.2",
42 | "file-utils": "^0.2.0",
43 | "findup-sync": "^0.1.2",
44 | "github-username": "^1.0.0",
45 | "glob": "4.2.2",
46 | "gruntfile-editor": "^0.2.0",
47 | "inquirer": "^0.8.0",
48 | "isbinaryfile": "^2.0.0",
49 | "lodash": "^2.4.1",
50 | "mem-fs-editor": "^1.0.0",
51 | "mime": "^1.2.9",
52 | "mkdirp": "^0.5.0",
53 | "nopt": "^3.0.0",
54 | "rimraf": "^2.2.0",
55 | "run-async": "^0.1.0",
56 | "shelljs": "^0.3.0",
57 | "sinon": "^1.9.1",
58 | "text-table": "^0.2.0",
59 | "through2": "^0.6.3",
60 | "underscore.string": "^2.3.1",
61 | "user-home": "^1.1.0",
62 | "xdg-basedir": "^1.0.0",
63 | "yeoman-assert": "^1.0.0",
64 | "yeoman-environment": "^1.1.0"
65 | },
66 | "devDependencies": {
67 | "gulp": "^3.6.0",
68 | "gulp-coveralls": "^0.1.0",
69 | "gulp-eslint": "~0.1.8",
70 | "gulp-istanbul": "^0.5.0",
71 | "gulp-jscs": "^1.1.0",
72 | "gulp-jshint": "^1.5.3",
73 | "gulp-mocha": "^2.0.0",
74 | "gulp-plumber": "~0.6.6",
75 | "jshint-stylish": "^1.0.0",
76 | "mockery": "^1.4.0",
77 | "nock": "^0.51.0",
78 | "nsp": "*",
79 | "proxyquire": "^1.0.0"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/invoke.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, before, after, beforeEach, afterEach */
2 | 'use strict';
3 | var yeoman = require('yeoman-environment');
4 | var generators = require('..');
5 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
6 | var helpers = generators.test;
7 | var assert = generators.assert;
8 |
9 | describe('generators.Base#invoke()', function () {
10 | beforeEach(function () {
11 | this.env = yeoman.createEnv([], {}, new TestAdapter());
12 | this.Generator = helpers.createDummyGenerator();
13 | this.gen = new this.Generator({
14 | namespace: 'foo:lab',
15 | resolved: 'path/',
16 | env: this.env,
17 | 'skip-install': true
18 | });
19 | this.SubGen = generators.Base.extend({
20 | exec: function () { this.stubGenRunned = true; }
21 | });
22 | this.env.registerStub(this.SubGen, 'foo:bar');
23 | });
24 |
25 | it('invoke available generators', function (done) {
26 | var invoked = this.gen.invoke('foo:bar', {
27 | options: { 'skip-install': true }
28 | });
29 | invoked.on('end', function () {
30 | assert(invoked.stubGenRunned);
31 | done();
32 | });
33 | });
34 |
35 | it('accept a callback argument', function (done) {
36 | var invoked = this.gen.invoke('foo:bar', {
37 | options: { 'skip-install': true }
38 | }, function () {
39 | assert(invoked.stubGenRunned);
40 | done();
41 | });
42 | });
43 |
44 | it('works when invoked from runLoop', function (done) {
45 | var stubGenFinished = false;
46 | var running = 0;
47 |
48 | var asyncFunc = function () {
49 | var cb = this.async();
50 | running++;
51 | setTimeout(function () {
52 | assert.equal(running, 1);
53 | running--;
54 | cb();
55 | }, 5);
56 | };
57 |
58 | this.SubGen.prototype.asyncFunc = asyncFunc;
59 | this.gen.constructor.prototype.asyncFunc = asyncFunc;
60 |
61 | this.gen.constructor.prototype.initializing = function () {
62 | var cb = this.async();
63 | var invoked = this.invoke('foo:bar', {
64 | options: { 'skip-install': true }
65 | }, function () {
66 | stubGenFinished = true;
67 | assert(invoked.stubGenRunned, 'Stub generator should have runned');
68 | cb();
69 | });
70 | };
71 |
72 | this.gen.run(function () {
73 | assert(stubGenFinished, 'invoke callback should have been called');
74 | done();
75 | });
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/test/generators/test-ember.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Ember generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('ember', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('app/scripts/models');
16 |
17 | helpers.assertFile('app/scripts/controllers');
18 |
19 | helpers.assertFile('app/scripts/views');
20 |
21 | helpers.assertFile('app/scripts/routes');
22 |
23 | helpers.assertFile('app/scripts/helpers');
24 |
25 | helpers.assertFile('app/scripts/templates');
26 |
27 | helpers.assertFile('app/scripts/main.js');
28 |
29 | helpers.assertFile('app/scripts/routes/app-router.js');
30 |
31 | helpers.assertFile('app/scripts/store.js');
32 |
33 | helpers.assertFile('.gitattributes');
34 |
35 | helpers.assertFile('.gitignore');
36 |
37 | helpers.assertFile('app/.htaccess');
38 |
39 | helpers.assertFile('app/404.html');
40 |
41 | helpers.assertFile('app/favicon.ico');
42 |
43 | helpers.assertFile('app/index.html');
44 |
45 | helpers.assertFile('app/robots.txt');
46 |
47 | helpers.assertFile('app/scripts/vendor/ember-1.0.pre.min.js');
48 |
49 | helpers.assertFile('app/scripts/vendor/handlebars-1.0.0.beta.6.js');
50 |
51 | helpers.assertFile('app/scripts/vendor/jquery.min.js');
52 |
53 | helpers.assertFile('app/styles/main.css');
54 |
55 | helpers.assertFile('Gruntfile.js');
56 |
57 | helpers.assertFile('package.json');
58 |
59 | helpers.assertFile('test/index.html');
60 |
61 | helpers.assertFile('test/lib/chai.js');
62 |
63 | helpers.assertFile('test/lib/expect.js');
64 |
65 | helpers.assertFile('test/lib/mocha-1.2.2/mocha.css');
66 |
67 | helpers.assertFile('test/lib/mocha-1.2.2/mocha.js');
68 |
69 | helpers.assertFile('test/runner/mocha.js');
70 |
71 | helpers.assertFile('app/scripts/views/application-view.js');
72 |
73 | helpers.assertFile('app/scripts/templates/application.handlebars');
74 |
75 | helpers.assertFile('app/scripts/models/application-model.js');
76 |
77 | helpers.assertFile('app/scripts/controllers/application-controller.js');
78 |
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/test/user.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, it, after, before, beforeEach, afterEach */
2 | 'use strict';
3 | var assert = require('assert');
4 | var mkdirp = require('mkdirp');
5 | var nock = require('nock');
6 | var os = require('os');
7 | var path = require('path');
8 | var proxyquire = require('proxyquire');
9 | var rimraf = require('rimraf');
10 | var shell = require('shelljs');
11 | var sinon = require('sinon');
12 | var tmpdir = path.join(os.tmpdir(), 'yeoman-user');
13 | var generators = require('..');
14 |
15 | describe('generators.Base#user', function () {
16 | before(function () {
17 | this.prevCwd = process.cwd();
18 | this.tmp = tmpdir;
19 | mkdirp.sync(path.join(tmpdir, 'subdir'));
20 | process.chdir(tmpdir);
21 | shell.exec('git init --quiet');
22 | shell.exec('git config --local user.name Yeoman');
23 | shell.exec('git config --local user.email yo@yeoman.io');
24 | });
25 |
26 | after(function (done) {
27 | process.chdir(this.prevCwd);
28 | rimraf(tmpdir, done);
29 | });
30 |
31 | beforeEach(function () {
32 | process.chdir(this.tmp);
33 | this.shell = shell;
34 | sinon.spy(this.shell, 'exec');
35 |
36 | this.user = proxyquire('../lib/actions/user', {
37 | shelljs: this.shell
38 | });
39 | });
40 |
41 | afterEach(function () {
42 | this.shell.exec.restore();
43 | });
44 |
45 | it('is exposed on the Base generator', function () {
46 | assert.equal(require('../lib/actions/user'), generators.Base.prototype.user);
47 | });
48 |
49 | describe('.git', function () {
50 |
51 | describe('.name()', function () {
52 | it('is the name used by git', function () {
53 | assert.equal(this.user.git.name(), 'Yeoman');
54 | });
55 |
56 | it('cache the value', function () {
57 | this.user.git.name();
58 | this.user.git.name();
59 | assert.equal(this.shell.exec.callCount, 1);
60 | });
61 |
62 | it('cache is linked to the CWD', function () {
63 | this.user.git.name();
64 | process.chdir('subdir');
65 | this.user.git.name();
66 | assert.equal(this.shell.exec.callCount, 2);
67 | });
68 | });
69 |
70 | describe('.email()', function () {
71 | it('is the email used by git', function () {
72 | assert.equal(this.user.git.email(), 'yo@yeoman.io');
73 | });
74 |
75 | it('handle cache', function () {
76 | this.user.git.email();
77 | this.user.git.email();
78 | assert.equal(this.shell.exec.callCount, 1);
79 | });
80 |
81 | it('cache is linked to the CWD', function () {
82 | this.user.git.email();
83 | process.chdir('subdir');
84 | this.user.git.email();
85 | assert.equal(this.shell.exec.callCount, 2);
86 | });
87 | });
88 | });
89 |
90 | describe('.github', function () {
91 | describe('.username()', function () {
92 | beforeEach(function () {
93 | nock('https://api.github.com')
94 | .filteringPath(/q=[^&]*/g, 'q=XXX')
95 | .get('/search/users?q=XXX')
96 | .times(1)
97 | .reply(200, {
98 | items: [
99 | { login: 'mockname' }
100 | ]
101 | });
102 | });
103 |
104 | afterEach(function () {
105 | nock.restore();
106 | });
107 |
108 | it('is the username used by GitHub', function (done) {
109 | this.user.github.username(function (err, res) {
110 | assert.equal(res, 'mockname');
111 | done();
112 | });
113 | });
114 | });
115 |
116 | });
117 |
118 | });
119 |
--------------------------------------------------------------------------------
/test/generators/test-backbone.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var path = require('path');
4 | var helpers = require('../..').test;
5 |
6 | describe('Backbone generator test', function () {
7 | before(helpers.before(path.join(__dirname, './temp')));
8 |
9 | it('runs sucessfully', function (done) {
10 | helpers.runGenerator('backbone', done);
11 | });
12 |
13 | it('creates expected files', function () {
14 |
15 | helpers.assertFile('app/scripts/models');
16 |
17 | helpers.assertFile('app/scripts/collections');
18 |
19 | helpers.assertFile('app/scripts/views');
20 |
21 | helpers.assertFile('app/scripts/routes');
22 |
23 | helpers.assertFile('app/scripts/helpers');
24 |
25 | helpers.assertFile('app/scripts/templates');
26 |
27 | helpers.assertFile('app/scripts/main.js');
28 |
29 | helpers.assertFile('.gitattributes');
30 |
31 | helpers.assertFile('.gitignore');
32 |
33 | helpers.assertFile('app/.htaccess');
34 |
35 | helpers.assertFile('app/404.html');
36 |
37 | helpers.assertFile('app/favicon.ico');
38 |
39 | helpers.assertFile('app/index.html');
40 |
41 | helpers.assertFile('app/robots.txt');
42 |
43 | helpers.assertFile('app/scripts/vendor/backbone-min.js');
44 |
45 | helpers.assertFile('app/scripts/vendor/jquery.min.js');
46 |
47 | helpers.assertFile('app/scripts/vendor/lodash.min.js');
48 |
49 | helpers.assertFile('app/styles/main.css');
50 |
51 | helpers.assertFile('Gruntfile.js');
52 |
53 | helpers.assertFile('package.json');
54 |
55 | helpers.assertFile('test/index.html');
56 |
57 | helpers.assertFile('test/lib/chai.js');
58 |
59 | helpers.assertFile('test/lib/expect.js');
60 |
61 | helpers.assertFile('test/lib/mocha/mocha.css');
62 |
63 | helpers.assertFile('test/lib/mocha/mocha.js');
64 |
65 | helpers.assertFile('test/runner/mocha.js');
66 |
67 | helpers.assertFile('app/scripts/routes/application-router.js');
68 |
69 | helpers.assertFile('app/scripts/views/application-view.js');
70 |
71 | helpers.assertFile('app/scripts/templates/application.ejs');
72 |
73 | helpers.assertFile('app/scripts/models/application-model.js');
74 |
75 | helpers.assertFile('app/scripts/collections/application-collection.js');
76 |
77 | });
78 |
79 | it('runs sucessfully with --coffee as argument', function (done) {
80 | helpers.runGenerator('backbone', { coffee: true }, done);
81 | });
82 |
83 | it('creates expected files when run with --coffee as argument', function () {
84 | helpers.assertFile('app/scripts/main.coffee');
85 |
86 | helpers.assertFile('app/scripts/routes/application-router.coffee');
87 |
88 | helpers.assertFile('app/scripts/views/application-view.coffee');
89 |
90 | helpers.assertFile('app/scripts/models/application-model.coffee');
91 |
92 | helpers.assertFile('app/scripts/collections/application-collection.coffee');
93 | });
94 |
95 | it('runs successfully with --test-framework as argument', function (done) {
96 | helpers.runGenerator('backbone', { 'test-framework': 'jasmine' }, done);
97 | });
98 |
99 | it('creates jasmine files when run with --test-framework', function () {
100 | helpers.assertFile('test/runner/headless.js');
101 |
102 | helpers.assertFile('test/runner/html.js');
103 |
104 | helpers.assertFile('test/lib/jasmine-1.2.0/jasmine.css');
105 |
106 | helpers.assertFile('test/lib/jasmine-1.2.0/jasmine-html.js');
107 |
108 | helpers.assertFile('test/lib/jasmine-1.2.0/jasmine.js');
109 |
110 | helpers.assertFile('test/spec/');
111 |
112 | helpers.assertFile('test/spec/introduction.js');
113 | });
114 |
115 | });
116 |
--------------------------------------------------------------------------------
/lib/actions/help.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var fs = require('fs');
4 | var _ = require('lodash');
5 | var table = require('text-table');
6 |
7 | /**
8 | * @mixin
9 | * @alias actions/help
10 | */
11 | var help = module.exports;
12 |
13 | /**
14 | * Tries to get the description from a USAGE file one folder above the
15 | * source root otherwise uses a default description.
16 | */
17 |
18 | help.help = function help() {
19 | var filepath = path.join(this.sourceRoot(), '../USAGE');
20 | var exists = fs.existsSync(filepath);
21 |
22 | var out = [
23 | 'Usage:',
24 | ' ' + this.usage(),
25 | ''
26 | ];
27 |
28 | // build options
29 | if (Object.keys(this._options).length) {
30 | out = out.concat([
31 | 'Options:',
32 | this.optionsHelp(),
33 | ''
34 | ]);
35 | }
36 |
37 | // build arguments
38 | if (this._arguments.length) {
39 | out = out.concat([
40 | 'Arguments:',
41 | this.argumentsHelp(),
42 | ''
43 | ]);
44 | }
45 |
46 | // append USAGE file is any
47 | if (exists) {
48 | out.push(fs.readFileSync(filepath, 'utf8'));
49 | }
50 |
51 | return out.join('\n');
52 | };
53 |
54 | function formatArg(argItem) {
55 | var arg = '<' + argItem.name + '>';
56 |
57 | if (!argItem.config.required) {
58 | arg = '[' + arg + ']';
59 | }
60 |
61 | return arg;
62 | }
63 |
64 | /**
65 | * Output usage information for this given generator, depending on its arguments,
66 | * options or hooks.
67 | */
68 |
69 | help.usage = function usage() {
70 | var options = Object.keys(this._options).length ? '[options]' : '';
71 | var name = ' ' + this.options.namespace;
72 | var args = '';
73 |
74 | if (this._arguments.length) {
75 | args = this._arguments.map(formatArg).join(' ');
76 | }
77 |
78 | name = name.replace(/^yeoman:/, '');
79 |
80 | var out = 'yo' + name + ' ' + options + ' ' + args;
81 |
82 | if (this.description) {
83 | out += '\n\n' + this.description;
84 | }
85 |
86 | return out;
87 | };
88 |
89 | /**
90 | * Simple setter for custom `description` to append on help output.
91 | *
92 | * @param {String} description
93 | */
94 |
95 | help.desc = function desc(description) {
96 | this.description = description || '';
97 | return this;
98 | };
99 |
100 | /**
101 | * Get help text for arguments
102 | * @returns {String} Text of options in formatted table
103 | */
104 | help.argumentsHelp = function argumentsHelp() {
105 | var rows = this._arguments.map(function (arg) {
106 | var config = arg.config;
107 | return [
108 | '',
109 | arg.name ? arg.name : '',
110 | config.desc ? '# ' + config.desc : '',
111 | config.type ? 'Type: ' + config.type.name : '',
112 | 'Required: ' + config.required
113 | ];
114 | });
115 |
116 | return table(rows);
117 | };
118 |
119 | /**
120 | * Get help text for options
121 | * @returns {String} Text of options in formatted table
122 | */
123 | help.optionsHelp = function optionsHelp() {
124 | var options = _.reject(this._options, function (el) {
125 | return el.hide;
126 | });
127 |
128 | var hookOpts = this._hooks.map(function (hook) {
129 | return hook.generator && hook.generator._options;
130 | }).reduce(function (a, b) {
131 | a = a.concat(b);
132 | return a;
133 | }, []).filter(function (opts) {
134 | return opts && opts.name !== 'help';
135 | });
136 |
137 | var rows = options.concat(hookOpts).map(function (opt) {
138 | var defaults = opt.defaults;
139 | return [
140 | '',
141 | opt.alias ? '-' + opt.alias + ', ' : '',
142 | '--' + opt.name,
143 | opt.desc ? '# ' + opt.desc : '',
144 | defaults == null || defaults === '' ? '' : 'Default: ' + defaults
145 | ];
146 | });
147 |
148 | return table(rows);
149 | };
150 |
--------------------------------------------------------------------------------
/lib/util/storage.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var path = require('path');
3 | var assert = require('assert');
4 | var _ = require('lodash');
5 |
6 | /**
7 | * Storage instances handle a json file where Generator authors can store data.
8 | *
9 | * `Base` instantiate the storage as `config` by default.
10 | *
11 | * @constructor
12 | * @param {String} name The name of the new storage (this is a namespace)
13 | * @param {mem-fs-editor} fs A mem-fs editor instance
14 | * @param {String} configPath The filepath used as a storage. `.yo-rc.json` is used
15 | * by default
16 | *
17 | * @example
18 | * var MyGenerator = yeoman.generators.base.extend({
19 | * config: function() {
20 | * this.config.set('coffeescript', false);
21 | * }
22 | * });
23 | */
24 |
25 | var Storage = module.exports = function Storage(name, fs, configPath) {
26 | assert(name, 'A name parameter is required to create a storage');
27 |
28 | this.path = configPath || path.join(process.cwd(), '.yo-rc.json');
29 | this.name = name;
30 | this.fs = fs;
31 |
32 | this.existed = Object.keys(this._store()).length > 0;
33 | };
34 |
35 | /**
36 | * Return the current store as JSON object
37 | * @private
38 | * @return {Object} the store content
39 | */
40 | Storage.prototype._store = function () {
41 | try {
42 | return this.fs.readJSON(this.path)[this.name] || {};
43 | } catch (e) {
44 | return {};
45 | }
46 | };
47 |
48 | /**
49 | * Save a new object of values
50 | * @param {Object} val - Store new state
51 | * @return {null}
52 | */
53 |
54 | Storage.prototype.save = function (val) {
55 | var fullStore;
56 | try {
57 | fullStore = this.fs.readJSON(this.path);
58 | } catch (e) {
59 | fullStore = {};
60 | }
61 | fullStore[this.name] = val;
62 | this.fs.write(this.path, JSON.stringify(fullStore, null, ' '));
63 | };
64 |
65 | /**
66 | * Alias to save.
67 | * @deprecated don't use save yourself.
68 | * @return {null}
69 | */
70 |
71 | Storage.prototype.forceSave = function (val) { this.save(val); };
72 |
73 | /**
74 | * Get a stored value
75 | * @param {String} key The key under which the value is stored.
76 | * @return {*} The stored value. Any JSON valid type could be returned
77 | */
78 |
79 | Storage.prototype.get = function (key) {
80 | return this._store()[key];
81 | };
82 |
83 | /**
84 | * Get all the stored values
85 | * @return {Object} key-value object
86 | */
87 |
88 | Storage.prototype.getAll = function () {
89 | return _.cloneDeep(this._store());
90 | };
91 |
92 | /**
93 | * Assign a key to a value and schedule a save.
94 | * @param {String} key The key under which the value is stored
95 | * @param {*} val Any valid JSON type value (String, Number, Array, Object).
96 | * @return {*} val Whatever was passed in as val.
97 | */
98 |
99 | Storage.prototype.set = function (key, val) {
100 | assert(!_.isFunction(val), 'Storage value can\'t be a function');
101 |
102 | var store = this._store();
103 |
104 | if (_.isObject(key)) {
105 | val = _.extend(store, key);
106 | } else {
107 | store[key] = val;
108 | }
109 | this.save(store);
110 | return val;
111 | };
112 |
113 | /**
114 | * Delete a key from the store and schedule a save.
115 | * @param {String} key The key under which the value is stored.
116 | * @return {null}
117 | */
118 |
119 | Storage.prototype.delete = function (key) {
120 | var store = this._store();
121 | delete store[key];
122 | this.save(store);
123 | };
124 |
125 | /**
126 | * Setup the store with defaults value and schedule a save.
127 | * If keys already exist, the initial value is kept.
128 | * @param {Object} defaults Key-value object to store.
129 | * @return {*} val Returns the merged options.
130 | */
131 |
132 | Storage.prototype.defaults = function (defaults) {
133 | assert(_.isObject(defaults), 'Storage `defaults` method only accept objects');
134 | var val = _.defaults(this.getAll(), defaults);
135 | this.set(val);
136 | return val;
137 | };
138 |
--------------------------------------------------------------------------------
/test/conflicter.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, beforeEach, afterEach */
2 | 'use strict';
3 | var assert = require('assert');
4 | var fs = require('fs');
5 | var path = require('path');
6 | var _ = require('lodash');
7 | var sinon = require('sinon');
8 | var Conflicter = require('../lib/util/conflicter');
9 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
10 |
11 | describe('Conflicter', function () {
12 | beforeEach(function () {
13 | this.conflicter = new Conflicter(new TestAdapter());
14 | });
15 |
16 | it('#checkForCollision()', function () {
17 | var spy = sinon.spy();
18 | var contents = fs.readFileSync(__filename, 'utf8');
19 | this.conflicter.checkForCollision(__filename, contents, spy);
20 |
21 | var conflict = this.conflicter.conflicts.pop();
22 | assert.deepEqual(conflict.file.path, __filename);
23 | assert.deepEqual(conflict.file.contents, fs.readFileSync(__filename, 'utf8'));
24 | assert.deepEqual(conflict.callback, spy);
25 | });
26 |
27 | describe('#resolve()', function () {
28 | it('wihout conflict', function (done) {
29 | this.conflicter.resolve(done);
30 | });
31 |
32 | it('with a conflict', function (done) {
33 | var spy = sinon.spy();
34 | this.conflicter.force = true;
35 |
36 | this.conflicter.checkForCollision(__filename, fs.readFileSync(__filename), spy);
37 | this.conflicter.checkForCollision('foo.js', 'var foo = "foo";\n', spy);
38 |
39 | this.conflicter.resolve(function () {
40 | assert.equal(spy.callCount, 2);
41 | assert.equal(this.conflicter.conflicts.length, 0, 'Expected conflicter to be empty after running');
42 | done();
43 | }.bind(this));
44 | });
45 | });
46 |
47 | describe('#collision()', function () {
48 | beforeEach(function () {
49 | this.conflictingFile = { path: __filename, contents: '' };
50 | });
51 |
52 | it('identical status', function (done) {
53 | var me = fs.readFileSync(__filename, 'utf8');
54 | this.conflicter.collision({
55 | path: __filename,
56 | contents: me
57 | }, function (status) {
58 | assert.equal(status, 'identical');
59 | done();
60 | });
61 | });
62 |
63 | it('create status', function (done) {
64 | this.conflicter.collision({
65 | path: 'file-who-does-not-exist.js',
66 | contents: ''
67 | }, function (status) {
68 | assert.equal(status, 'create');
69 | done();
70 | });
71 | });
72 |
73 | it('user choose "yes"', function (done) {
74 | var conflicter = new Conflicter(new TestAdapter({ action: 'write' }));
75 | conflicter.collision(this.conflictingFile, function (status) {
76 | assert.equal(status, 'force');
77 | done();
78 | });
79 | });
80 |
81 | it('user choose "skip"', function (done) {
82 | var conflicter = new Conflicter(new TestAdapter({ action: 'skip' }));
83 | conflicter.collision(this.conflictingFile, function (status) {
84 | assert.equal(status, 'skip');
85 | done();
86 | });
87 | });
88 |
89 | it('user choose "force"', function (done) {
90 | var conflicter = new Conflicter(new TestAdapter({ action: 'force' }));
91 | conflicter.collision(this.conflictingFile, function (status) {
92 | assert.equal(status, 'force');
93 | done();
94 | });
95 | });
96 |
97 | it('force conflict status', function (done) {
98 | this.conflicter.force = true;
99 | this.conflicter.collision(this.conflictingFile, function (status) {
100 | assert.equal(status, 'force');
101 | done();
102 | });
103 | });
104 |
105 | it('does not give a conflict on same binary files', function (done) {
106 | this.conflicter.collision({
107 | path: path.join(__dirname, 'fixtures/yeoman-logo.png'),
108 | contents: fs.readFileSync(path.join(__dirname, 'fixtures/yeoman-logo.png'))
109 | }, function (status) {
110 | assert.equal(status, 'identical');
111 | done();
112 | }.bind(this));
113 | });
114 |
115 | it('does not provide a diff option for directory', function (done) {
116 | var conflicter = new Conflicter(new TestAdapter({ action: 'write' }));
117 | var spy = sinon.spy(conflicter.adapter, 'prompt');
118 | conflicter.collision({
119 | path: __dirname,
120 | contents: null
121 | }, function (status) {
122 | assert.equal(
123 | _.where(spy.firstCall.args[0][0].choices, { value: 'diff' }).length,
124 | 0
125 | );
126 | done();
127 | });
128 | });
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/test/install.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, before, after, beforeEach, afterEach */
2 | 'use strict';
3 | var yeoman = require('yeoman-environment');
4 | var generators = require('..');
5 | var helpers = generators.test;
6 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
7 | var sinon = require('sinon');
8 |
9 | var asyncStub = {
10 | on: function (key, cb) {
11 | if (key === 'exit') {
12 | cb();
13 | }
14 | return asyncStub;
15 | }
16 | };
17 |
18 | describe('generators.Base (actions/install mixin)', function () {
19 | beforeEach(function () {
20 | this.env = yeoman.createEnv([], {}, new TestAdapter());
21 | this.env.registerStub(helpers.createDummyGenerator(), 'dummy');
22 | this.dummy = this.env.create('dummy');
23 |
24 | // Keep track of all commands executed by spawnCommand.
25 | this.spawnCommandStub = sinon.stub(this.dummy, 'spawnCommand');
26 | this.spawnCommandStub.returns(asyncStub);
27 | });
28 |
29 | describe('#runInstall()', function () {
30 | it('takes a config object and passes it to the spawned process', function (done) {
31 | var spawnEnv = {
32 | env: {
33 | PATH: '/path/to/bin'
34 | }
35 | };
36 | var callbackSpy = sinon.spy();
37 | //args: installer, paths, options, cb
38 | this.dummy.runInstall('nestedScript', ['path1', 'path2'], spawnEnv, callbackSpy);
39 | this.dummy.run(function () {
40 | sinon.assert.calledWithExactly(
41 | this.spawnCommandStub,
42 | 'nestedScript',
43 | ['install', 'path1', 'path2'],
44 | spawnEnv
45 | );
46 | sinon.assert.calledOnce(callbackSpy);
47 | done();
48 | }.bind(this));
49 | });
50 | });
51 |
52 | describe('#bowerInstall()', function () {
53 | it('spawn a bower process once per commands', function (done) {
54 | this.dummy.bowerInstall();
55 | this.dummy.bowerInstall();
56 | this.dummy.run(function () {
57 | sinon.assert.calledOnce(this.spawnCommandStub);
58 | sinon.assert.calledWithExactly(this.spawnCommandStub, 'bower', ['install'], {});
59 | done();
60 | }.bind(this));
61 | });
62 |
63 | it('spawn a bower process with formatted options', function (done) {
64 | this.dummy.bowerInstall('jquery', { saveDev: true }, function () {
65 | sinon.assert.calledOnce(this.spawnCommandStub);
66 | sinon.assert.calledWithExactly(
67 | this.spawnCommandStub,
68 | 'bower',
69 | ['install', 'jquery', '--save-dev'],
70 | { saveDev: true }
71 | );
72 | done();
73 | }.bind(this));
74 | this.dummy.run();
75 | });
76 | });
77 |
78 | describe('#npmInstall()', function () {
79 | it('spawn an install process once per commands', function (done) {
80 | this.dummy.npmInstall();
81 | this.dummy.npmInstall();
82 | this.dummy.run(function () {
83 | sinon.assert.calledOnce(this.spawnCommandStub);
84 | sinon.assert.calledWithExactly(this.spawnCommandStub, 'npm', ['install'], {});
85 | done();
86 | }.bind(this));
87 | });
88 |
89 | it('run without callback', function (done) {
90 | this.dummy.npmInstall('yo', { save: true });
91 | this.dummy.run(function () {
92 | sinon.assert.calledOnce(this.spawnCommandStub);
93 | done();
94 | }.bind(this));
95 | });
96 |
97 | it('run with callback', function (done) {
98 | this.dummy.npmInstall('yo', { save: true }, function () {
99 | sinon.assert.calledOnce(this.spawnCommandStub);
100 | done();
101 | }.bind(this));
102 | this.dummy.run();
103 | });
104 | });
105 |
106 | describe('#installDependencies()', function () {
107 | it('spawn npm and bower', function (done) {
108 | this.dummy.installDependencies(function () {
109 | sinon.assert.calledTwice(this.spawnCommandStub);
110 | sinon.assert.calledWithExactly(this.spawnCommandStub, 'bower', ['install'], {});
111 | sinon.assert.calledWithExactly(this.spawnCommandStub, 'npm', ['install'], {});
112 | done();
113 | }.bind(this));
114 | this.dummy.run();
115 | });
116 |
117 | it('does not spawn anything with skipInstall', function (done) {
118 | this.dummy.installDependencies({ skipInstall: true });
119 | this.dummy.run(function () {
120 | sinon.assert.notCalled(this.spawnCommandStub);
121 | done();
122 | }.bind(this));
123 | });
124 |
125 | it('call callback if skipInstall', function (done) {
126 | this.dummy.installDependencies({ skipInstall: true, callback: done });
127 | this.dummy.run();
128 | });
129 |
130 | it('execute a callback after installs', function (done) {
131 | this.dummy.installDependencies({ callback: done });
132 | this.dummy.run();
133 | });
134 |
135 | it('accept and execute a function as its only argument', function (done) {
136 | this.dummy.installDependencies(done);
137 | this.dummy.run();
138 | });
139 | });
140 | });
141 |
--------------------------------------------------------------------------------
/lib/actions/install.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var _ = require('lodash');
3 | var dargs = require('dargs');
4 | var async = require('async');
5 | var chalk = require('chalk');
6 | var assert = require('assert');
7 |
8 | /**
9 | * @mixin
10 | * @alias actions/install
11 | */
12 | var install = module.exports;
13 |
14 | /**
15 | * Combine package manager cmd line arguments and run the `install` command.
16 | *
17 | * Every commands will be schedule to run once on the run loop during the `install` step.
18 | * (So don't combine the callback with `this.async()`)
19 | *
20 | * @param {String} installer Which package manager to use
21 | * @param {String|Array} [paths] Packages to install, empty string for `npm install`
22 | * @param {Object} [options] Options to invoke `install` with. These options will be parsed
23 | * by [dargs]{@link https://www.npmjs.org/package/dargs}
24 | * @param {Function} [cb]
25 | */
26 |
27 | install.runInstall = function (installer, paths, options, cb) {
28 | if (!cb && _.isFunction(options)) {
29 | cb = options;
30 | options = {};
31 | }
32 |
33 | options = options || {};
34 | cb = cb || function () {};
35 | paths = Array.isArray(paths) ? paths : (paths && paths.split(' ') || []);
36 |
37 | var args = ['install'].concat(paths).concat(dargs(options));
38 |
39 | this.env.runLoop.add('install', function (done) {
40 | this.emit(installer + 'Install', paths);
41 | this.spawnCommand(installer, args, options)
42 | .on('error', cb)
43 | .on('exit', function (err) {
44 | if (err === 127) {
45 | this.log.error('Could not find ' + installer + '. Please install with ' +
46 | '`npm install -g ' + installer + '`.');
47 | }
48 | this.emit(installer + 'Install:end', paths);
49 | cb(err);
50 | done();
51 | }.bind(this));
52 | }.bind(this), { once: installer + ' ' + args.join(' '), run: false });
53 |
54 | return this;
55 | };
56 |
57 | /**
58 | * Runs `npm` and `bower` in the generated directory concurrently and prints a
59 | * message to let the user know.
60 | *
61 | * @example
62 | * this.installDependencies({
63 | * bower: true,
64 | * npm: true,
65 | * skipInstall: false,
66 | * callback: function () {
67 | * console.log('Everything is ready!');
68 | * }
69 | * });
70 | *
71 | * @param {Object} [options]
72 | * @param {Boolean} [options.npm=true] - whether to run `npm install`
73 | * @param {Boolean} [options.bower=true] - whether to run `bower install`
74 | * @param {Boolean} [options.skipInstall=false] - whether to skip automatic installation
75 | * @param {Boolean} [options.skipMessage=false] - whether to log the used commands
76 | * @param {Function} [options.callback] - call once every commands are runned
77 | */
78 |
79 | install.installDependencies = function (options) {
80 | var msg = {
81 | commands: [],
82 | template: _.template('\n\nI\'m all done. ' +
83 | '<%= skipInstall ? "Just run" : "Running" %> <%= commands %> ' +
84 | '<%= skipInstall ? "" : "for you " %>to install the required dependencies.' +
85 | '<% if (!skipInstall) { %> If this fails, try running the command yourself.<% } %>\n\n')
86 | };
87 |
88 | var commands = [];
89 |
90 | if (_.isFunction(options)) {
91 | options = {
92 | callback: options
93 | };
94 | }
95 |
96 | options = _.defaults(options || {}, {
97 | bower: true,
98 | npm: true,
99 | skipInstall: false,
100 | skipMessage: false,
101 | callback: function () {}
102 | });
103 |
104 | if (options.bower) {
105 | msg.commands.push('bower install');
106 | commands.push(function (cb) {
107 | this.bowerInstall(null, null, cb);
108 | }.bind(this));
109 | }
110 |
111 | if (options.npm) {
112 | msg.commands.push('npm install');
113 | commands.push(function (cb) {
114 | this.npmInstall(null, null, cb);
115 | }.bind(this));
116 | }
117 |
118 | assert(msg.commands.length, 'installDependencies needs at least one of npm or bower to run.');
119 |
120 | if (!options.skipMessage) {
121 | this.env.adapter.log(msg.template(_.extend(options, {
122 | commands: chalk.yellow.bold(msg.commands.join(' & '))
123 | })));
124 | }
125 |
126 | if (options.skipInstall) {
127 | return options.callback();
128 | }
129 |
130 | async.parallel(commands, options.callback);
131 | };
132 |
133 | /**
134 | * Receives a list of `components`, and an `options` object to install through bower.
135 | *
136 | * The installation will automatically be runned during the run loop `install` phase.
137 | *
138 | * @param {String|Array} cmpnt Components to install
139 | * @param {Object} [options] Options to invoke `bower install` with, see `bower help install`
140 | * @param {Function} [cb]
141 | */
142 |
143 | install.bowerInstall = function install(cmpnt, options, cb) {
144 | return this.runInstall('bower', cmpnt, options, cb);
145 | };
146 |
147 | /**
148 | * Receives a list of `packages`, and an `options` object to install through npm.
149 | *
150 | * The installation will automatically be runned during the run loop `install` phase.
151 | *
152 | * @param {String|Array} pkgs Packages to install
153 | * @param {Object} [options] Options to invoke `npm install` with, see `npm help install`
154 | * @param {Function} [cb]
155 | */
156 |
157 | install.npmInstall = function install(pkgs, options, cb) {
158 | return this.runInstall('npm', pkgs, options, cb);
159 | };
160 |
--------------------------------------------------------------------------------
/lib/util/prompt-suggestion.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var assert = require('assert');
4 | var _ = require('lodash');
5 |
6 | /**
7 | * @mixin
8 | * @alias util/prompt-suggestion
9 | */
10 | var promptSuggestion = module.exports;
11 |
12 | /**
13 | * Returns the default value for a checkbox.
14 | *
15 | * @param {Object} question Inquirer prompt item
16 | * @param {*} defaultValue The stored default value
17 | * @return {*} Default value to set
18 | * @private
19 | */
20 | var getCheckboxDefault = function (question, defaultValue) {
21 | // For simplicity we uncheck all boxes and
22 | // use .default to set the active ones.
23 | _.each(question.choices, function (choice) {
24 | if (typeof choice === 'object') {
25 | choice.checked = false;
26 | }
27 | });
28 | return defaultValue;
29 | };
30 |
31 | /**
32 | * Returns the default value for a list.
33 | *
34 | * @param {Object} question Inquirer prompt item
35 | * @param {*} defaultValue The stored default value
36 | * @return {*} Default value to set
37 | * @private
38 | */
39 | var getListDefault = function (question, defaultValue) {
40 | var choiceValues = _.map(question.choices, function (choice) {
41 | if (typeof choice === 'object') {
42 | return choice.value;
43 | } else {
44 | return choice;
45 | }
46 | });
47 | return choiceValues.indexOf(defaultValue);
48 | };
49 |
50 | /**
51 | * Return true if the answer should be store in
52 | * the global store, otherwise false.
53 | *
54 | * @param {Object} question Inquirer prompt item
55 | * @param {String|Array} answer The inquirer answer
56 | * @return {Boolean} Answer to be stored
57 | * @private
58 | */
59 | var storeListAnswer = function (question, answer) {
60 | var choiceValues = _.pluck(question.choices, 'value');
61 | var choiceIndex = choiceValues.indexOf(answer);
62 | // Check if answer is not equal to default value
63 | if (question.default !== choiceIndex) {
64 | return true;
65 | }
66 | return false;
67 | };
68 |
69 | /**
70 | * Return true if the answer should be store in
71 | * the global store, otherwise false.
72 | *
73 | * @param {Object} question Inquirer prompt item
74 | * @param {String|Array} answer The inquirer answer
75 | * @return {Boolean} Answer to be stored
76 | * @private
77 | */
78 | var storeAnswer = function (question, answer) {
79 | // Check if answer is not equal to default value or is undefined
80 | if (answer && question.default !== answer) {
81 | return true;
82 | }
83 | return false;
84 | };
85 |
86 | /**
87 | * Prefill the defaults with values from the global store.
88 | *
89 | * @param {Store} store `.yo-rc-global` global config
90 | * @param {Array|Object} questions Original prompt questions
91 | * @return {Array} Prompt questions array with prefilled values.
92 | */
93 | promptSuggestion.prefillQuestions = function (store, questions) {
94 | assert(store, 'A store parameter is required');
95 | assert(questions, 'A questions parameter is required');
96 |
97 | var promptValues = store.get('promptValues') || {};
98 |
99 | if (!Array.isArray(questions)) {
100 | questions = [questions];
101 | }
102 |
103 | questions = questions.map(_.clone);
104 |
105 | // Write user defaults back to prompt
106 | return questions.map(function (question) {
107 | if (question.store !== true) return question;
108 |
109 | var storedValue = promptValues[question.name];
110 | if (storedValue == null) return question;
111 |
112 | // Override prompt default with the user's default
113 | switch (question.type) {
114 |
115 | case 'rawlist':
116 | case 'expand':
117 | question.default = getListDefault(question, storedValue);
118 | break;
119 | case 'checkbox':
120 | question.default = getCheckboxDefault(question, storedValue);
121 | break;
122 | default:
123 | question.default = storedValue;
124 | break;
125 | }
126 | return question;
127 | }.bind(this));
128 | };
129 |
130 | /**
131 | * Store the answers in the global store.
132 | *
133 | * @param {Store} store `.yo-rc-global` global config
134 | * @param {Array|Object} questions Original prompt questions
135 | * @param {Object} answers The inquirer answers
136 | */
137 | promptSuggestion.storeAnswers = function (store, questions, answers) {
138 | assert(store, 'A store parameter is required');
139 | assert(answers, 'A answers parameter is required');
140 | assert(questions, 'A questions parameter is required');
141 | assert.ok(_.isObject(answers), 'answers must be a object');
142 |
143 | var promptValues = store.get('promptValues') || {};
144 |
145 | if (!Array.isArray(questions)) {
146 | questions = [questions];
147 | }
148 |
149 | _.each(questions, function (question) {
150 | if (question.store !== true) return;
151 |
152 | var saveAnswer;
153 | var key = question.name;
154 | var answer = answers[key];
155 |
156 | switch (question.type) {
157 |
158 | case 'rawlist':
159 | case 'expand':
160 | saveAnswer = storeListAnswer(question, answer);
161 | break;
162 |
163 | default:
164 | saveAnswer = storeAnswer(question, answer);
165 | break;
166 | }
167 |
168 | if (saveAnswer) {
169 | promptValues[key] = answer;
170 | }
171 |
172 | }.bind(this));
173 |
174 | if (Object.keys(promptValues).length) {
175 | store.set('promptValues', promptValues);
176 | }
177 | };
178 |
--------------------------------------------------------------------------------
/lib/actions/remote.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fs = require('fs');
3 | var path = require('path');
4 | var _ = require('lodash');
5 | var fileUtils = require('file-utils');
6 | var rimraf = require('rimraf');
7 |
8 | /**
9 | * @mixin
10 | * @alias actions/remote
11 | */
12 | var remote = module.exports;
13 |
14 | /**
15 | * Remotely fetch a package from github (or an archive), store this into a _cache
16 | * folder, and provide a "remote" object as a facade API to ourself (part of
17 | * generator API, copy, template, directory). It's possible to remove local cache,
18 | * and force a new remote fetch of the package.
19 | *
20 | * ### Examples:
21 | *
22 | * this.remote('user', 'repo', function(err, remote) {
23 | * remote.copy('.', 'vendors/user-repo');
24 | * });
25 | *
26 | * this.remote('user', 'repo', 'branch', function(err, remote) {
27 | * remote.copy('.', 'vendors/user-repo');
28 | * });
29 | *
30 | * this.remote('http://foo.com/bar.zip', function(err, remote) {
31 | * remote.copy('.', 'vendors/user-repo');
32 | * });
33 | *
34 | * When fetching from Github
35 | * @param {String} username
36 | * @param {String} repo
37 | * @param {String} branch
38 | * @param {Function} cb
39 | * @param {Boolean} refresh
40 | *
41 | * @also
42 | * When fetching an archive
43 | * @param {String} url
44 | * @param {Function} cb
45 | * @param {Boolean} refresh
46 | */
47 |
48 | remote.remote = function () {
49 | var username;
50 | var repo;
51 | var branch;
52 | var cb;
53 | var refresh;
54 | var url;
55 | var cache;
56 |
57 | if (arguments.length <= 3 && typeof arguments[2] !== 'function') {
58 | url = arguments[0];
59 | cb = arguments[1];
60 | refresh = arguments[2];
61 | cache = path.join(this.cacheRoot(), _.slugify(url));
62 | } else {
63 | username = arguments[0];
64 | repo = arguments[1];
65 | branch = arguments[2];
66 | cb = arguments[3];
67 | refresh = arguments[4];
68 |
69 | if (!cb) {
70 | cb = branch;
71 | branch = 'master';
72 | }
73 |
74 | cache = path.join(this.cacheRoot(), username, repo, branch);
75 | url = 'https://github.com/' + [username, repo, 'archive', branch].join('/') + '.tar.gz';
76 | }
77 |
78 | var self = this;
79 |
80 | var done = function (err) {
81 | if (err) {
82 | return cb(err);
83 | }
84 |
85 | self.remoteDir(cache, cb);
86 | };
87 |
88 | fs.stat(cache, function (err) {
89 | // already cached
90 | if (!err) {
91 | // no refresh, so we can use this cache
92 | if (!refresh) {
93 | return done();
94 | }
95 |
96 | // otherwise, we need to remove it, to fetch it again
97 | rimraf(cache, function (err) {
98 | if (err) {
99 | return cb(err);
100 | }
101 | self.extract(url, cache, { strip: 1 }, done);
102 | });
103 |
104 | } else {
105 | self.extract(url, cache, { strip: 1 }, done);
106 | }
107 | });
108 |
109 | return this;
110 | };
111 |
112 | /**
113 | * Retrieve a stored directory and use as a remote reference. This is handy if
114 | * you have files you want to move that may have been downloaded differently to
115 | * using `this.remote()` (eg such as `node_modules` installed via `package.json`)
116 | * @param {String} cache
117 | * @param {Function} cb
118 | * @example
119 | * ### Examples
120 | *
121 | * this.remoteDir('foo/bar', function(err, remote) {
122 | * remote.copy('.', 'vendors/user-repo');
123 | * });
124 | */
125 | remote.remoteDir = function (cache, cb) {
126 | var self = this;
127 | var files = this.expandFiles('**', { cwd: cache, dot: true });
128 |
129 | var remoteFs = {};
130 | remoteFs.cachePath = cache;
131 |
132 | // simple proxy to `.copy(source, destination)`
133 | remoteFs.copy = function copy(source, destination) {
134 | source = path.join(cache, source);
135 | self.copy(source, destination);
136 | return this;
137 | };
138 |
139 | // simple proxy to `.bulkCopy(source, destination)`
140 | remoteFs.bulkCopy = function copy(source, destination) {
141 | source = path.join(cache, source);
142 | self.bulkCopy(source, destination);
143 | return this;
144 | };
145 |
146 | // same as `.template(source, destination, data)`
147 | remoteFs.template = function template(source, destination, data) {
148 | data = data || self;
149 | destination = destination || source;
150 | source = path.join(cache, source);
151 |
152 | var body = self.engine(self.read(source), data);
153 | self.write(destination, body);
154 | };
155 |
156 | // same as `.template(source, destination)`
157 | remoteFs.directory = function directory(source, destination) {
158 | var root = self.sourceRoot();
159 | self.sourceRoot(cache);
160 | self.directory(source, destination);
161 | self.sourceRoot(root);
162 | };
163 |
164 | // simple proxy to `.bulkDirectory(source, destination)`
165 | remoteFs.bulkDirectory = function directory(source, destination) {
166 | var root = self.sourceRoot();
167 | self.sourceRoot(cache);
168 | self.bulkDirectory(source, destination);
169 | self.sourceRoot(root);
170 | };
171 |
172 | var fileLogger = { write: _.noop, warn: _.noop };
173 |
174 | // Set the file-utils environments
175 | // Set logger as a noop as logging is handled by the yeoman conflicter
176 | remoteFs.src = fileUtils.createEnv({
177 | base: cache,
178 | dest: self.destinationRoot(),
179 | logger: fileLogger
180 | });
181 | remoteFs.dest = fileUtils.createEnv({
182 | base: self.destinationRoot(),
183 | dest: cache,
184 | logger: fileLogger
185 | });
186 |
187 | remoteFs.dest.registerValidationFilter('collision', self.getCollisionFilter());
188 | remoteFs.src.registerValidationFilter('collision', self.getCollisionFilter());
189 |
190 | cb(null, remoteFs, files);
191 | };
192 |
--------------------------------------------------------------------------------
/lib/util/conflicter.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var async = require('async');
6 | var detectConflict = require('detect-conflict');
7 | var _ = require('lodash');
8 |
9 | /**
10 | * The Conflicter is a module that can be used to detect conflict between files. Each
11 | * Generator file system helpers pass files through this module to make sure they don't
12 | * break a user file.
13 | *
14 | * When a potential conflict is detected, we prompt the user and ask them for
15 | * confirmation before proceeding with the actual write.
16 | *
17 | * @constructor
18 | * @property {Boolean} force - same as the constructor argument
19 | *
20 | * @param {TerminalAdapter} adapter - The generator adapter
21 | * @param {Boolean} force - When set to true, we won't check for conflict. (the
22 | * conflicter become a passthrough)
23 | */
24 | var Conflicter = module.exports = function Conflicter(adapter, force) {
25 | this.force = force === true;
26 | this.adapter = adapter;
27 | this.conflicts = [];
28 | };
29 |
30 | /**
31 | * Add a file to conflicter queue.
32 | *
33 | * @param {String} filepath - File destination path
34 | * @param {String} contents - File new contents
35 | * @param {Function} callback - callback to be called once we know if the user want to
36 | * proceed or not.
37 | */
38 | Conflicter.prototype.checkForCollision = function (filepath, contents, callback) {
39 | this.conflicts.push({
40 | file: {
41 | path: path.resolve(filepath),
42 | contents: contents
43 | },
44 | callback: callback
45 | });
46 | };
47 |
48 | /**
49 | * Process the _potential conflict_ queue and ask the user to resolve conflict when they
50 | * occur.
51 | *
52 | * The user is presented with the following options:
53 | *
54 | * - `Y` Yes, overwrite
55 | * - `n` No, do not overwrite
56 | * - `a` All, overwrite this and all others
57 | * - `q` Quit, abort
58 | * - `d` Diff, show the differences between the old and the new
59 | * - `h` Help, show this help
60 | *
61 | * @param {Function} cb Callback once every conflict are resolved. (note that each
62 | * file can specify it's own callback. See `#checkForCollision()`)
63 | */
64 | Conflicter.prototype.resolve = function (cb) {
65 | cb = cb || _.noop;
66 | var self = this;
67 | var resolveConflicts = function (conflict) {
68 | return function (next) {
69 | if (!conflict) return next();
70 |
71 | self.collision(conflict.file, function (status) {
72 | // Remove the resolved conflict from the queue
73 | _.pull(self.conflicts, conflict);
74 | conflict.callback(null, status);
75 | next();
76 | });
77 | };
78 | };
79 |
80 | async.series(this.conflicts.map(resolveConflicts), cb.bind(this));
81 | };
82 |
83 | /**
84 | * Check if a file conflict with the current version on the user disk.
85 | *
86 | * A basic check is done to see if the file exists, if it does:
87 | *
88 | * 1. Read its content from `fs`
89 | * 2. Compare it with the provided content
90 | * 3. If identical, mark it as is and skip the check
91 | * 4. If diverged, prepare and show up the file collision menu
92 | *
93 | * @param {Object} file File object respecting this interface: { path, contents }
94 | * @param {Function} cb Callback receiving a status string ('identical', 'create',
95 | * 'skip', 'force')
96 | * @return {null} nothing
97 | */
98 | Conflicter.prototype.collision = function (file, cb) {
99 | var rfilepath = path.relative(process.cwd(), file.path);
100 |
101 | if (!fs.existsSync(file.path)) {
102 | this.adapter.log.create(rfilepath);
103 | return cb('create');
104 | }
105 |
106 | if (this.force) {
107 | this.adapter.log.force(rfilepath);
108 | return cb('force');
109 | }
110 |
111 | if (detectConflict(file.path, file.contents)) {
112 | this.adapter.log.conflict(rfilepath);
113 | return this._ask(file, cb);
114 | } else {
115 | this.adapter.log.identical(rfilepath);
116 | return cb('identical');
117 | }
118 | };
119 |
120 | /**
121 | * Actual prompting logic
122 | * @private
123 | * @param {Object} file
124 | * @param {Function} cb
125 | */
126 | Conflicter.prototype._ask = function (file, cb) {
127 | var rfilepath = path.relative(process.cwd(), file.path);
128 |
129 | var prompt = {
130 | name: 'action',
131 | type: 'expand',
132 | message: 'Overwrite ' + rfilepath + '?',
133 | choices: [{
134 | key: 'y',
135 | name: 'overwrite',
136 | value: 'write'
137 | }, {
138 | key: 'n',
139 | name: 'do not overwrite',
140 | value: 'skip'
141 | }, {
142 | key: 'a',
143 | name: 'overwrite this and all others',
144 | value: 'force'
145 | }, {
146 | key: 'x',
147 | name: 'abort',
148 | value: 'abort'
149 | }]
150 | };
151 |
152 | // Only offer diff option for files
153 | if (fs.statSync(file.path).isFile()) {
154 | prompt.choices.push({
155 | key: 'd',
156 | name: 'show the differences between the old and the new',
157 | value: 'diff'
158 | });
159 | }
160 |
161 | this.adapter.prompt([prompt], function (result) {
162 | if (result.action === 'abort') {
163 | this.adapter.log.writeln('Aborting ...');
164 | return process.exit(0);
165 | }
166 |
167 | if (result.action === 'diff') {
168 | this.adapter.diff(fs.readFileSync(file.path, 'utf8'), file.contents);
169 | return this._ask(file, cb);
170 | }
171 |
172 | if (result.action === 'force') {
173 | this.force = true;
174 | }
175 |
176 | if (result.action === 'write') {
177 | result.action = 'force';
178 | }
179 |
180 | this.adapter.log[result.action](rfilepath);
181 | return cb(result.action);
182 | }.bind(this));
183 | };
184 |
--------------------------------------------------------------------------------
/test/generators.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, beforeEach, it */
2 | 'use strict';
3 | var path = require('path');
4 | var fs = require('fs');
5 | var os = require('os');
6 | var events = require('events');
7 | var file = require('file-utils');
8 | var generators = require('..');
9 | var assert = generators.assert;
10 | var helpers = generators.test;
11 | var sinon = require('sinon');
12 | var tmpdir = path.join(os.tmpdir(), 'yeoman-generators');
13 |
14 | var Environment = require('yeoman-environment');
15 |
16 | describe('Generators module', function () {
17 | before(helpers.setUpTestDirectory(tmpdir));
18 |
19 | describe('module', function () {
20 | it('initialize new Environments', function () {
21 | assert.ok(generators() instanceof Environment);
22 | assert.notEqual(generators(), generators());
23 | });
24 |
25 | it('pass arguments to the Environment constructor', function () {
26 | var args = ['model', 'Post'];
27 | var opts = { help: true };
28 | var env = generators(args, opts);
29 | assert.deepEqual(env.arguments, args);
30 | assert.deepEqual(env.options, opts);
31 | });
32 | });
33 |
34 | describe('.generators', function () {
35 | it('should have a Base object to extend from', function () {
36 | assert.ok(generators.Base);
37 | });
38 |
39 | it('should have a NamedBase object to extend from', function () {
40 | assert.ok(generators.NamedBase);
41 | });
42 | });
43 |
44 | describe('generators.Base', function () {
45 | beforeEach(function () {
46 | this.env = generators();
47 | this.generator = new generators.Base({
48 | env: this.env,
49 | resolved: 'test'
50 | });
51 | });
52 |
53 | it('is an EventEmitter', function (done) {
54 | assert.ok(this.generator instanceof events.EventEmitter);
55 | assert.ok(typeof this.generator.on === 'function');
56 | assert.ok(typeof this.generator.emit === 'function');
57 | this.generator.on('yay-o-man', done);
58 | this.generator.emit('yay-o-man');
59 | });
60 |
61 | describe('.src', function () {
62 | it('implement the file-utils interface', function () {
63 | assert.implement(this.generator.src, file.constructor.prototype);
64 | });
65 |
66 | it('generator.sourcePath() update its source base', function () {
67 | this.generator.sourceRoot('foo/src');
68 | assert.ok(this.generator.src.fromBase('bar'), 'foo/src/bar');
69 | });
70 |
71 | it('generator.destinationPath() update its destination base', function () {
72 | this.generator.destinationRoot('foo/src');
73 | assert.ok(this.generator.src.fromDestBase('bar'), 'foo/src/bar');
74 | });
75 | });
76 |
77 | describe('.dest', function () {
78 | it('implement the file-utils interface', function () {
79 | assert.implement(this.generator.dest, file.constructor.prototype);
80 | });
81 |
82 | it('generator.sourcePath() update its destination base', function () {
83 | this.generator.sourceRoot('foo/src');
84 | assert.ok(this.generator.src.fromDestBase('bar'), 'foo/src/bar');
85 | });
86 |
87 | it('generator.destinationPath() update its source base', function () {
88 | this.generator.destinationRoot('foo/src');
89 | assert.ok(this.generator.src.fromBase('bar'), 'foo/src/bar');
90 | });
91 |
92 | describe('conflict handler', function () {
93 | var destRoot = path.join(__dirname, 'fixtures');
94 | var target = path.join(destRoot, 'file-conflict.txt');
95 | var initialFileContent = fs.readFileSync(target).toString();
96 |
97 | beforeEach(function () {
98 | this.generator.destinationRoot(destRoot);
99 | assert.ok(file.exists(target));
100 | assert.textEqual(initialFileContent, 'initial content\n');
101 | });
102 |
103 | it('aborting', function () {
104 | // make sure the file exist
105 | var fileContent = this.generator.dest.read('file-conflict.txt');
106 | var checkForCollision = sinon.stub(this.generator.conflicter, 'checkForCollision');
107 |
108 | this.generator.dest.write('file-conflict.txt', 'some conficting content');
109 |
110 | var cb = checkForCollision.args[0][2];
111 | cb(null, {
112 | status: 'abort',
113 | callback: function () {}
114 | });
115 |
116 | assert.ok(checkForCollision.calledOnce);
117 | assert.ok(fileContent, this.generator.dest.read('file-conflict.txt'));
118 | });
119 |
120 | it('allowing', function () {
121 | // make sure the file exist
122 | var fileContent = this.generator.dest.read('file-conflict.txt');
123 | var checkForCollision = sinon.stub(this.generator.conflicter, 'checkForCollision');
124 |
125 | this.generator.dest.write('file-conflict.txt', 'some conficting content');
126 |
127 | var cb = checkForCollision.args[0][2];
128 | cb(null, {
129 | status: 'create',
130 | callback: function () {}
131 | });
132 |
133 | assert.ok(checkForCollision.calledOnce);
134 | assert.ok('some conflicting content', fileContent);
135 |
136 | // reset content
137 | fs.writeFileSync(target, initialFileContent);
138 | });
139 | });
140 | });
141 | });
142 |
143 | describe('generators.NamedBase', function () {
144 | before(function () {
145 | this.env = generators();
146 | this.generator = new generators.NamedBase(['namedArg'], {
147 | env: this.env,
148 | resolved: 'namedbase:test'
149 | });
150 | });
151 |
152 | it('extend Base generator', function () {
153 | assert.ok(this.generator instanceof generators.Base);
154 | });
155 |
156 | it('have a name property', function () {
157 | assert.equal(this.generator.name, 'namedArg');
158 | });
159 | });
160 | });
161 |
--------------------------------------------------------------------------------
/lib/test/run-context.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var crypto = require('crypto');
3 | var path = require('path');
4 | var os = require('os');
5 | var assert = require('assert');
6 | var _ = require('lodash');
7 | var yeoman = require('yeoman-environment');
8 | var util = require('util');
9 | var EventEmitter = require('events').EventEmitter;
10 | var helpers = require('./helpers');
11 | var TestAdapter = require('./adapter').TestAdapter;
12 |
13 | /**
14 | * This class provide a run context object to façade the complexity involved in setting
15 | * up a generator for testing
16 | * @constructor
17 | * @param {String|Function} Generator - Namespace or generator constructor. If the later
18 | * is provided, then namespace is assumed to be
19 | * 'gen:test' in all cases
20 | * @param {Object} [settings]
21 | * @param {Boolean} [settings.tmpdir=true] - Automatically run this generator in a tmp dir
22 | * @return {this}
23 | */
24 |
25 | var RunContext = module.exports = function RunContext(Generator, settings) {
26 | this._asyncHolds = 0;
27 | this.runned = false;
28 | this.inDirSet = false;
29 | this.args = [];
30 | this.options = {};
31 | this.answers = {};
32 | this.dependencies = [];
33 | this.Generator = Generator;
34 | this.settings = _.extend({ tmpdir: true }, settings);
35 |
36 | setTimeout(this._run.bind(this), 10);
37 | };
38 |
39 | util.inherits(RunContext, EventEmitter);
40 |
41 | /**
42 | * Hold the execution until the returned callback is triggered
43 | * @return {Function} Callback to notify the normal execution can resume
44 | */
45 |
46 | RunContext.prototype.async = function () {
47 | this._asyncHolds++;
48 | return function () {
49 | this._asyncHolds--;
50 | this._run();
51 | }.bind(this);
52 | };
53 |
54 | /**
55 | * Method called when the context is ready to run the generator
56 | * @private
57 | */
58 |
59 | RunContext.prototype._run = function () {
60 | if (!this.inDirSet && this.settings.tmpdir) {
61 | this.inTmpDir();
62 | }
63 |
64 | if (this._asyncHolds !== 0 || this.runned) return;
65 | this.runned = true;
66 |
67 | var namespace;
68 | this.env = yeoman.createEnv([], {}, new TestAdapter());
69 |
70 | helpers.registerDependencies(this.env, this.dependencies);
71 |
72 | if (_.isString(this.Generator)) {
73 | namespace = this.env.namespace(this.Generator);
74 | this.env.register(this.Generator);
75 | } else {
76 | namespace = 'gen:test';
77 | this.env.registerStub(this.Generator, namespace);
78 | }
79 |
80 | this.generator = this.env.create(namespace, {
81 | arguments: this.args,
82 | options: _.extend({
83 | 'skip-install': true
84 | }, this.options)
85 | });
86 |
87 | helpers.mockPrompt(this.generator, this.answers);
88 |
89 | this.generator.on('error', this.emit.bind(this, 'error'));
90 | this.generator.once('end', function () {
91 | helpers.restorePrompt(this.generator);
92 | this.emit('end');
93 | this.completed = true;
94 | }.bind(this));
95 |
96 | this.emit('ready', this.generator);
97 | this.generator.run();
98 | };
99 |
100 | /**
101 | * Clean the provided directory, then change directory into it
102 | * @param {String} dirPath - Directory path (relative to CWD). Prefer passing an absolute
103 | * file path for predictable results
104 | * @param {Function} [cb] - callback who'll receive the folder path as argument
105 | * @return {this} run context instance
106 | */
107 |
108 | RunContext.prototype.inDir = function (dirPath, cb) {
109 | this.inDirSet = true;
110 | var release = this.async();
111 | var callBackThenRelease = _.compose(release, (cb || _.noop).bind(this, path.resolve(dirPath)));
112 | helpers.testDirectory(dirPath, callBackThenRelease);
113 | return this;
114 | };
115 |
116 | /**
117 | * Cleanup a temporary directy and change the CWD into it
118 | *
119 | * This method is called automatically when creating a RunContext. Only use it if you need
120 | * to use the callback.
121 | *
122 | * @param {Function} [cb] - callback who'll receive the folder path as argument
123 | * @return {this} run context instance
124 | */
125 | RunContext.prototype.inTmpDir = function (cb) {
126 | var tmpdir = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex'));
127 | return this.inDir(tmpdir, cb);
128 | };
129 |
130 | /**
131 | * Provide arguments to the run context
132 | * @param {String|Array} args - command line arguments as Array or space separated string
133 | * @return {this}
134 | */
135 |
136 | RunContext.prototype.withArguments = function (args) {
137 | var argsArray = _.isString(args) ? args.split(' ') : args;
138 | assert(_.isArray(argsArray), 'args should be either a string separated by spaces or an array');
139 | this.args = this.args.concat(argsArray);
140 | return this;
141 | };
142 |
143 | /**
144 | * Provide options to the run context
145 | * @param {Object} options - command line options (e.g. `--opt-one=foo`)
146 | * @return {this}
147 | */
148 |
149 | RunContext.prototype.withOptions = function (options) {
150 | this.options = _.extend(this.options, options);
151 | return this;
152 | };
153 |
154 | /**
155 | * Mock the prompt with dummy answers
156 | * @param {Object} answers - Answers to the prompt questions
157 | * @return {this}
158 | */
159 |
160 | RunContext.prototype.withPrompts = function (answers) {
161 | this.answers = _.extend(this.answers, answers);
162 | return this;
163 | };
164 |
165 | /**
166 | * @alias RunContext.prototype.withPrompts
167 | * @deprecated
168 | */
169 |
170 | RunContext.prototype.withPrompt = RunContext.prototype.withPrompts;
171 |
172 | /**
173 | * Provide dependent generators
174 | * @param {Array} dependencies - paths to the generators dependencies
175 | * @return {this}
176 | * @example
177 | * var deps = ['../../common',
178 | * '../../controller',
179 | * '../../main',
180 | * [helpers.createDummyGenerator(), 'testacular:app']
181 | * ];
182 | * var angular = new RunContext('../../app');
183 | * angular.withGenerators(deps);
184 | * angular.withPrompts({
185 | * compass: true,
186 | * bootstrap: true
187 | * });
188 | * angular.onEnd(function () {
189 | * // assert something
190 | * });
191 | */
192 |
193 | RunContext.prototype.withGenerators = function (dependencies) {
194 | assert(_.isArray(dependencies), 'dependencies should be an array');
195 | this.dependencies = this.dependencies.concat(dependencies);
196 | return this;
197 | };
198 |
199 | /**
200 | * Add a callback to be called after the generator has ran
201 | * @deprecated `onEnd` is deprecated, use .on('end', onEndHandler) instead.
202 | * @param {Function} callback
203 | * @return {this}
204 | */
205 |
206 | RunContext.prototype.onEnd = function (cb) {
207 | console.log('`onEnd` is deprecated, use .on(\'end\', onEndHandler) instead.');
208 | return this.on('end', cb);
209 | };
210 |
--------------------------------------------------------------------------------
/test/storage.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, before, after, beforeEach, afterEach */
2 | 'use strict';
3 | var assert = require('assert');
4 | var FileEditor = require('mem-fs-editor');
5 | var fs = require('fs');
6 | var os = require('os');
7 | var path = require('path');
8 | var sinon = require('sinon');
9 | var env = require('yeoman-environment');
10 | var Storage = require('../lib/util/storage');
11 | var generators = require('..');
12 | var helpers = generators.test;
13 | var tmpdir = path.join(os.tmpdir(), 'yeoman-storage');
14 |
15 | function rm(path) {
16 | if (fs.existsSync(path)) {
17 | fs.unlinkSync(path);
18 | }
19 | }
20 |
21 | describe('Storage', function () {
22 | before(helpers.setUpTestDirectory(tmpdir));
23 |
24 | beforeEach(function () {
25 | this.beforeDir = process.cwd();
26 | this.storePath = path.join(tmpdir, 'new-config.json');
27 | this.memFs = env.createEnv().sharedFs;
28 | this.fs = FileEditor.create(this.memFs);
29 | this.store = new Storage('test', this.fs, this.storePath);
30 | this.store.set('foo', 'bar');
31 | this.saveSpy = sinon.spy(this.store, 'save');
32 | });
33 |
34 | afterEach(function () {
35 | rm(this.storePath);
36 | process.chdir(this.beforeDir);
37 | });
38 |
39 | describe('.constructor()', function () {
40 | it('require a name parameter', function () {
41 | assert.throws(function () { new Storage(); });
42 | });
43 |
44 | it('take a path parameter', function () {
45 | var store = new Storage('test', this.fs, path.join(__dirname, './fixtures/config.json'));
46 | assert.equal(store.get('testFramework'), 'mocha');
47 | assert.ok(store.existed);
48 | });
49 | });
50 |
51 | it('namespace each store sharing the same store file', function () {
52 | var store = new Storage('foobar', this.fs, this.storePath);
53 | store.set('foo', 'something else');
54 | assert.equal(this.store.get('foo'), 'bar');
55 | });
56 |
57 | it('defaults store path to `.yo-rc.json`', function () {
58 | process.chdir(tmpdir);
59 | var store = new Storage('yo', this.fs);
60 | store.set('foo', 'bar');
61 |
62 | var fileContent = this.fs.readJSON('.yo-rc.json');
63 | assert.equal(fileContent.yo.foo, 'bar');
64 | });
65 |
66 | describe('#get()', function () {
67 | beforeEach(function () {
68 | this.store.set('testFramework', 'mocha');
69 | this.store.set('name', 'test');
70 | });
71 |
72 | it('get values', function () {
73 | assert.equal(this.store.get('testFramework'), 'mocha');
74 | assert.equal(this.store.get('name'), 'test');
75 | });
76 | });
77 |
78 | describe('#set()', function () {
79 | it('set values', function () {
80 | this.store.set('name', 'Yeoman!');
81 | assert.equal(this.store.get('name'), 'Yeoman!');
82 | });
83 |
84 | it('set multipe values at once', function () {
85 | this.store.set({ foo: 'bar', john: 'doe' });
86 | assert.equal(this.store.get('foo'), 'bar');
87 | assert.equal(this.store.get('john'), 'doe');
88 | });
89 |
90 | it('throws when invalid JSON values are passed', function () {
91 | assert.throws(this.store.set.bind(this, 'foo', function () {}));
92 | });
93 |
94 | it('save on each changes', function () {
95 | this.store.set('foo', 'bar');
96 | assert.equal(this.saveSpy.callCount, 1);
97 | this.store.set('foo', 'oo');
98 | assert.equal(this.saveSpy.callCount, 2);
99 | });
100 |
101 | describe('@return', function () {
102 | beforeEach(function () {
103 | this.storePath = path.join(tmpdir, 'setreturn.json');
104 | this.store = new Storage('test', this.fs, this.storePath);
105 | });
106 |
107 | afterEach(function () {
108 | rm(this.storePath);
109 | });
110 |
111 | it('the saved value (with key)', function () {
112 | assert.equal(this.store.set('name', 'Yeoman!'), 'Yeoman!');
113 | });
114 |
115 | it('the saved value (without key)', function () {
116 | assert.deepEqual(
117 | this.store.set({ foo: 'bar', john: 'doe' }),
118 | { foo: 'bar', john: 'doe' }
119 | );
120 | });
121 |
122 | it('the saved value (update values)', function () {
123 | this.store.set({ foo: 'bar', john: 'doe' });
124 | assert.deepEqual(
125 | this.store.set({ foo: 'moo' }),
126 | { foo: 'moo', john: 'doe' }
127 | );
128 | });
129 | });
130 | });
131 |
132 | describe('#getAll()', function () {
133 | beforeEach(function () {
134 | this.store.set({ foo: 'bar', john: 'doe' });
135 | });
136 |
137 | it('get all values', function () {
138 | assert.deepEqual(this.store.getAll().foo, 'bar');
139 | });
140 |
141 | it('does not return a reference to the inner store', function () {
142 | this.store.getAll().foo = 'uhoh';
143 | assert.equal(this.store.getAll().foo, 'bar');
144 | });
145 | });
146 |
147 | describe('#delete()', function () {
148 | beforeEach(function () {
149 | this.store.set('name', 'test');
150 | });
151 |
152 | it('delete value', function () {
153 | this.store.delete('name');
154 | assert.equal(this.store.get('name'), undefined);
155 | });
156 | });
157 |
158 | describe('#save()', function () {
159 | beforeEach(function () {
160 | this.saveStorePath = path.join(tmpdir, 'save.json');
161 | rm(this.saveStorePath);
162 | this.store = new Storage('test', this.fs, this.saveStorePath);
163 | this.store.set('foo', 'bar');
164 | this.saveSpy = sinon.spy(this.store, 'save');
165 | });
166 |
167 | describe('when multiples instances share the same file', function () {
168 | beforeEach(function () {
169 | this.store2 = new Storage('test2', this.fs, this.saveStorePath);
170 | });
171 |
172 | it('only update modified namespace', function () {
173 | this.store2.set('bar', 'foo');
174 | this.store.set('foo', 'bar');
175 |
176 | var json = this.fs.readJSON(this.saveStorePath);
177 | assert.equal(json.test.foo, 'bar');
178 | assert.equal(json.test2.bar, 'foo');
179 | });
180 | });
181 |
182 | describe('when multiples instances share the same namespace', function () {
183 | beforeEach(function () {
184 | this.store2 = new Storage('test', this.fs, this.saveStorePath);
185 | });
186 |
187 | it('only update modified namespace', function () {
188 | this.store2.set('bar', 'foo');
189 | this.store.set('foo', 'bar');
190 |
191 | var json = this.fs.readJSON(this.saveStorePath);
192 | assert.equal(json.test.foo, 'bar');
193 | assert.equal(json.test.bar, 'foo');
194 | });
195 | });
196 | });
197 |
198 | describe('#defaults()', function () {
199 | beforeEach(function () {
200 | this.store.set('val1', 1);
201 | });
202 |
203 | it('set defaults values if not predefined', function () {
204 | this.store.defaults({ val1: 3, val2: 4 });
205 |
206 | assert.equal(this.store.get('val1'), 1);
207 | assert.equal(this.store.get('val2'), 4);
208 | });
209 |
210 | it('require an Object as argument', function () {
211 | assert.throws(this.store.defaults.bind(this.store, 'foo'));
212 | });
213 |
214 | describe('@return', function () {
215 | beforeEach(function () {
216 | this.storePath = path.join(tmpdir, 'defaultreturn.json');
217 | this.store = new Storage('test', this.fs, this.storePath);
218 | this.store.set('val1', 1);
219 | this.store.set('foo', 'bar');
220 | });
221 |
222 | afterEach(function () {
223 | rm(this.storePath);
224 | });
225 |
226 | it('the saved value when passed an empty object', function () {
227 | assert.deepEqual(this.store.defaults({}), { foo: 'bar', val1: 1 });
228 | });
229 |
230 | it('the saved value when passed the same key', function () {
231 | assert.deepEqual(
232 | this.store.defaults({ foo: 'baz' }),
233 | { foo: 'bar', val1: 1 }
234 | );
235 | });
236 |
237 | it('the saved value when passed new key', function () {
238 | assert.deepEqual(
239 | this.store.defaults({ food: 'pizza' }),
240 | { foo: 'bar', val1: 1, food: 'pizza' }
241 | );
242 | });
243 | });
244 | });
245 |
246 | });
247 |
--------------------------------------------------------------------------------
/test/helpers.js:
--------------------------------------------------------------------------------
1 | /*global it, describe, before, beforeEach, afterEach */
2 | 'use strict';
3 | var util = require('util');
4 | var path = require('path');
5 | var assert = require('assert');
6 | var sinon = require('sinon');
7 | var yeoman = require('..');
8 | var helpers = yeoman.test;
9 | var RunContext = require('../lib/test/run-context');
10 | var env = yeoman();
11 |
12 | describe('generators.test', function () {
13 | beforeEach(function () {
14 | process.chdir(path.join(__dirname, './fixtures'));
15 | var self = this;
16 | this.StubGenerator = function (args, options) {
17 | self.args = args;
18 | self.options = options;
19 | };
20 | util.inherits(this.StubGenerator, yeoman.Base);
21 | });
22 |
23 | describe('.registerDependencies()', function () {
24 | it('accepts dependency as a path', function () {
25 | helpers.registerDependencies(env, ['./custom-generator-simple']);
26 | assert(env.get('simple:app'));
27 | });
28 |
29 | it('accepts dependency as array of [, ]', function () {
30 | helpers.registerDependencies(env, [[this.StubGenerator, 'stub:app']]);
31 | assert(env.get('stub:app'));
32 | });
33 | });
34 |
35 | describe('.createGenerator()', function () {
36 | it('create a new generator', function () {
37 | var generator = helpers.createGenerator('unicorn:app', [
38 | [this.StubGenerator, 'unicorn:app']
39 | ]);
40 | assert.ok(generator instanceof this.StubGenerator);
41 | });
42 |
43 | it('pass args params to the generator', function () {
44 | helpers.createGenerator('unicorn:app', [
45 | [this.StubGenerator, 'unicorn:app']
46 | ], ['temp']);
47 | assert.deepEqual(this.args, ['temp']);
48 | });
49 |
50 | it('pass options param to the generator', function () {
51 | helpers.createGenerator('unicorn:app', [
52 | [this.StubGenerator, 'unicorn:app']
53 | ], ['temp'], { ui: 'tdd' });
54 | assert.equal(this.options.ui, 'tdd');
55 | });
56 | });
57 |
58 | describe('.decorate()', function () {
59 | beforeEach(function () {
60 | this.spy = sinon.stub().returns(1);
61 | this.spyStub = sinon.stub().returns(2);
62 | this.execSpy = sinon.stub().returns(3);
63 | this.execStubSpy = sinon.stub().returns(4);
64 | this.ctx = {
65 | exec: this.execSpy,
66 | execStub: this.execStubSpy
67 | };
68 |
69 | helpers.decorate(this.ctx, 'exec', this.spy);
70 | helpers.decorate(this.ctx, 'execStub', this.spyStub, { stub: true });
71 | this.execResult = this.ctx.exec('foo', 'bar');
72 | this.execStubResult = this.ctx.execStub();
73 | });
74 |
75 | it('wraps a method', function () {
76 | assert(this.spy.calledBefore(this.execSpy));
77 | });
78 |
79 | it('passes arguments of the original methods', function () {
80 | assert(this.spy.calledWith('foo', 'bar'));
81 | });
82 |
83 | it('skip original methods if stub: true', function () {
84 | assert(this.execStubSpy.notCalled);
85 | });
86 |
87 | it('returns original methods if stub: false', function () {
88 | assert.equal(this.execResult, 3);
89 | });
90 |
91 | it('returns stub methods if stub: true', function () {
92 | assert.equal(this.execStubResult, 2);
93 | });
94 | });
95 |
96 | describe('.stub()', function () {
97 | beforeEach(function () {
98 | this.spy = sinon.spy();
99 | this.initialExec = sinon.spy();
100 | this.ctx = {
101 | exec: this.initialExec
102 | };
103 | helpers.stub(this.ctx, 'exec', this.spy);
104 | });
105 |
106 | it('replace initial method', function () {
107 | this.ctx.exec();
108 | assert(this.initialExec.notCalled);
109 | assert(this.spy.calledOnce);
110 | });
111 | });
112 |
113 | describe('.restore()', function () {
114 | beforeEach(function () {
115 | this.initialExec = function () {};
116 | this.ctx = {
117 | exec: this.initialExec
118 | };
119 | helpers.decorate(this.ctx, 'exec', function () {});
120 | });
121 |
122 | it('restore decorated methods', function () {
123 | assert.notEqual(this.ctx.exec, this.initialExec);
124 | helpers.restore();
125 | assert.equal(this.ctx.exec, this.initialExec);
126 | });
127 | });
128 |
129 | describe('.mockPrompt()', function () {
130 | beforeEach(function () {
131 | this.generator = env.instantiate(helpers.createDummyGenerator());
132 | helpers.mockPrompt(this.generator, { answer: 'foo' });
133 | });
134 |
135 | it('uses default values', function (done) {
136 | this.generator.prompt([{ name: 'respuesta', type: 'input', default: 'bar' }], function (answers) {
137 | assert.equal(answers.respuesta, 'bar');
138 | done();
139 | });
140 | });
141 |
142 | it('uses default values when no answer is passed', function (done) {
143 | var generator = env.instantiate(helpers.createDummyGenerator());
144 | helpers.mockPrompt(generator);
145 | generator.prompt([{ name: 'respuesta', message: 'foo', type: 'input', default: 'bar' }], function (answers) {
146 | assert.equal(answers.respuesta, 'bar');
147 | done();
148 | });
149 | });
150 |
151 | it('supports `null` answer for `list` type', function (done) {
152 | var generator = env.instantiate(helpers.createDummyGenerator());
153 | helpers.mockPrompt(generator, {
154 | respuesta: null
155 | });
156 | generator.prompt([{ name: 'respuesta', message: 'foo', type: 'list', default: 'bar' }], function (answers) {
157 | assert.equal(answers.respuesta, null);
158 | done();
159 | });
160 | });
161 |
162 | it('treats `null` as no answer for `input` type', function (done) {
163 | var generator = env.instantiate(helpers.createDummyGenerator());
164 | helpers.mockPrompt(generator, {
165 | respuesta: null
166 | });
167 | generator.prompt([{ name: 'respuesta', message: 'foo', type: 'input', default: 'bar' }], function (answers) {
168 | assert.equal(answers.respuesta, 'bar');
169 | done();
170 | });
171 | });
172 |
173 | it('uses `true` as the default value for `confirm` type', function (done) {
174 | var generator = env.instantiate(helpers.createDummyGenerator());
175 | helpers.mockPrompt(generator, {});
176 | generator.prompt([{ name: 'respuesta', message: 'foo', type: 'confirm' }], function (answers) {
177 | assert.equal(answers.respuesta, true);
178 | done();
179 | });
180 | });
181 |
182 | it('supports `false` answer for `confirm` type', function (done) {
183 | var generator = env.instantiate(helpers.createDummyGenerator());
184 | helpers.mockPrompt(generator, { respuesta: false });
185 | generator.prompt([{ name: 'respuesta', message: 'foo', type: 'confirm' }], function (answers) {
186 | assert.equal(answers.respuesta, false);
187 | done();
188 | });
189 | });
190 |
191 | it('prefers mocked values over defaults', function (done) {
192 | this.generator.prompt([{ name: 'answer', type: 'input', default: 'bar' }], function (answers) {
193 | assert.equal(answers.answer, 'foo');
194 | done();
195 | });
196 | });
197 |
198 | it('can be call multiple time on the same generator', function (done) {
199 | var generator = env.instantiate(helpers.createDummyGenerator());
200 | helpers.mockPrompt(generator, { foo: 1 });
201 | helpers.mockPrompt(generator, { foo: 2 });
202 | generator.prompt({ message: 'bar', name: 'foo' }, function (answers) {
203 | assert.equal(answers.foo, 2);
204 | done();
205 | });
206 | });
207 |
208 | it('keep prompt method asynchronous', function (done) {
209 | var spy = sinon.spy();
210 | this.generator.prompt({ name: 'answer', type: 'input' }, function () {
211 | sinon.assert.called(spy);
212 | done();
213 | });
214 | spy();
215 | });
216 | });
217 |
218 | describe('.before()', function () {
219 | afterEach(function () {
220 | helpers.restore();
221 | });
222 |
223 | it('alias .setUpTestDirectory()', function () {
224 | var spy = sinon.spy(helpers, 'setUpTestDirectory');
225 | helpers.before('dir');
226 | sinon.assert.calledWith(spy, 'dir');
227 | });
228 | });
229 |
230 | describe('.run()', function () {
231 | it('return a RunContext object', function () {
232 | assert(helpers.run(helpers.createDummyGenerator()) instanceof RunContext);
233 | });
234 |
235 | it('pass settings to RunContext', function () {
236 | var runContext = helpers.run(helpers.createDummyGenerator(), { foo: 1 });
237 | assert.equal(runContext.settings.foo, 1);
238 | });
239 | });
240 | });
241 |
--------------------------------------------------------------------------------
/lib/actions/wiring.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var util = require('util');
3 | var fs = require('fs');
4 | var path = require('path');
5 | var cheerio = require('cheerio');
6 |
7 | /**
8 | * @mixin
9 | * @alias actions/wiring
10 | */
11 | var wiring = module.exports;
12 |
13 | /**
14 | * Update a file containing HTML markup with new content, either
15 | * appending, prepending or replacing content matching a particular
16 | * selector.
17 | *
18 | * The following modes are available:
19 | *
20 | * - `a` Append
21 | * - `p` Prepend
22 | * - `r` Replace
23 | * - `d` Delete
24 | *
25 | * @param {String} html
26 | * @param {String} tagName
27 | * @param {String} content
28 | * @param {String} mode
29 | */
30 |
31 | wiring.domUpdate = function domUpdate(html, tagName, content, mode) {
32 | var $ = cheerio.load(html, { decodeEntities: false });
33 |
34 | if (content !== undefined) {
35 | if (mode === 'a') {
36 | $(tagName).append(content);
37 | } else if (mode === 'p') {
38 | $(tagName).prepend(content);
39 | } else if (mode === 'r') {
40 | $(tagName).html(content);
41 | } else if (mode === 'd') {
42 | $(tagName).remove();
43 | }
44 | return $.html();
45 | } else {
46 | console.error('Please supply valid content to be updated.');
47 | }
48 | };
49 |
50 | /**
51 | * Insert specific content as the last child of each element matched
52 | * by the `tagName` selector.
53 | *
54 | * @param {String} html
55 | * @param {String} tagName
56 | * @param {String} content
57 | */
58 |
59 | wiring.append = function append(html, tagName, content) {
60 | return this.domUpdate(html, tagName, content, 'a');
61 | };
62 |
63 | /**
64 | * Insert specific content as the first child of each element matched
65 | * by the `tagName` selector.
66 | *
67 | * @param {String} html
68 | * @param {String} tagName
69 | * @param {String} content
70 | */
71 |
72 | wiring.prepend = function prepend(html, tagName, content) {
73 | return this.domUpdate(html, tagName, content, 'p');
74 | };
75 |
76 | /**
77 | * Insert specific content as the last child of each element matched
78 | * by the `tagName` selector. Writes to file.
79 | *
80 | * @param {String} path
81 | * @param {String} tagName
82 | * @param {String} content
83 | */
84 |
85 | wiring.appendToFile = function appendToFile(path, tagName, content) {
86 | var html = this.readFileAsString(path);
87 | var updatedContent = this.append(html, tagName, content);
88 | this.writeFileFromString(updatedContent, path);
89 | };
90 |
91 | /**
92 | * Insert specific content as the first child of each element matched
93 | * by the `tagName` selector. Writes to file.
94 | *
95 | * @param {String} path
96 | * @param {String} tagName
97 | * @param {String} content
98 | */
99 |
100 | wiring.prependToFile = function prependToFile(path, tagName, content) {
101 | var html = this.readFileAsString(path);
102 | var updatedContent = this.prepend(html, tagName, content);
103 | this.writeFileFromString(updatedContent, path);
104 | };
105 |
106 | /**
107 | * Generate a usemin-handler block.
108 | *
109 | * @param {String} blockType
110 | * @param {String} optimizedPath
111 | * @param {String} filesBlock
112 | * @param {String|Array} searchPath
113 | */
114 |
115 | wiring.generateBlock = function generateBlock(blockType, optimizedPath, filesBlock, searchPath) {
116 | var blockStart;
117 | var blockEnd;
118 | var blockSearchPath = '';
119 |
120 | if (searchPath !== undefined) {
121 | if (util.isArray(searchPath)) {
122 | searchPath = '{' + searchPath.join(',') + '}';
123 | }
124 | blockSearchPath = '(' + searchPath + ')';
125 | }
126 |
127 | blockStart = '\n \n';
128 | blockEnd = ' \n';
129 | return blockStart + filesBlock + blockEnd;
130 | };
131 |
132 | /**
133 | * Append files, specifying the optimized path and generating the necessary
134 | * usemin blocks to be used for the build process. In the case of scripts and
135 | * styles, boilerplate script/stylesheet tags are written for you.
136 | *
137 | * @param {String|Object} htmlOrOptions
138 | * @param {String} fileType
139 | * @param {String} optimizedPath
140 | * @param {Array} sourceFileList
141 | * @param {Object} attrs
142 | * @param {String} searchPath
143 | */
144 |
145 | wiring.appendFiles = function appendFiles(htmlOrOptions, fileType, optimizedPath, sourceFileList, attrs, searchPath) {
146 | var blocks;
147 | var updatedContent;
148 | var html = htmlOrOptions;
149 | var files = '';
150 |
151 | if (typeof htmlOrOptions === 'object') {
152 | html = htmlOrOptions.html;
153 | fileType = htmlOrOptions.fileType;
154 | optimizedPath = htmlOrOptions.optimizedPath;
155 | sourceFileList = htmlOrOptions.sourceFileList;
156 | attrs = htmlOrOptions.attrs;
157 | searchPath = htmlOrOptions.searchPath;
158 | }
159 |
160 | attrs = this.attributes(attrs);
161 |
162 | if (fileType === 'js') {
163 | sourceFileList.forEach(function (el) {
164 | files += ' \n';
165 | });
166 | blocks = this.generateBlock('js', optimizedPath, files, searchPath);
167 | updatedContent = this.append(html, 'body', blocks);
168 | } else if (fileType === 'css') {
169 | sourceFileList.forEach(function (el) {
170 | files += ' \n';
171 | });
172 | blocks = this.generateBlock('css', optimizedPath, files, searchPath);
173 | updatedContent = this.append(html, 'head', blocks);
174 | }
175 |
176 | // cleanup trailing whitespace
177 | return updatedContent.replace(/[\t ]+$/gm, '');
178 | };
179 |
180 | /**
181 | * Computes a given Hash object of attributes into its HTML representation.
182 | *
183 | * @param {Object} attrs
184 | */
185 |
186 | wiring.attributes = function attributes(attrs) {
187 | return Object.keys(attrs || {}).map(function (key) {
188 | return key + '="' + attrs[key] + '"';
189 | }).join(' ');
190 | };
191 |
192 | /**
193 | * Scripts alias to `appendFiles`.
194 | *
195 | * @param {String} html
196 | * @param {String} optimizedPath
197 | * @param {Array} sourceFileList
198 | * @param {Object} attrs
199 | * @param {String} searchPath
200 | */
201 |
202 | wiring.appendScripts = function appendScripts(html, optimizedPath, sourceFileList, attrs, searchPath) {
203 | return this.appendFiles(html, 'js', optimizedPath, sourceFileList, attrs, searchPath);
204 | };
205 |
206 | /**
207 | * Simple script removal.
208 | *
209 | * @param {String} html
210 | * @param {String} scriptPath
211 | */
212 |
213 | wiring.removeScript = function removeScript(html, scriptPath) {
214 | return this.domUpdate(html, 'script[src$="' + scriptPath + '"]', '', 'd');
215 | };
216 |
217 | /**
218 | * Style alias to `appendFiles`.
219 | *
220 | * @param {String} html
221 | * @param {String} optimizedPath
222 | * @param {Array} sourceFileList
223 | * @param {Object} attrs
224 | * @param {String} searchPath
225 | */
226 |
227 | wiring.appendStyles = function appendStyles(html, optimizedPath, sourceFileList, attrs, searchPath) {
228 | return this.appendFiles(html, 'css', optimizedPath, sourceFileList, attrs, searchPath);
229 | };
230 |
231 | /**
232 | * Simple style removal.
233 | *
234 | * @param {String} html
235 | * @param {String} path
236 | */
237 |
238 | wiring.removeStyle = function removeStyle(html, path) {
239 | return this.domUpdate(html, 'link[href$="' + path + '"]', '', 'd');
240 | };
241 |
242 | /**
243 | * Append a directory of scripts.
244 | *
245 | * @param {String} html
246 | * @param {String} optimizedPath
247 | * @param {String} sourceScriptDir
248 | * @param {Object} attrs
249 | */
250 |
251 | wiring.appendScriptsDir = function appendScriptsDir(html, optimizedPath, sourceScriptDir, attrs) {
252 | var sourceScriptList = fs.readdirSync(path.resolve(sourceScriptDir));
253 | return this.appendFiles(html, 'js', optimizedPath, sourceScriptList, attrs);
254 | };
255 |
256 | /**
257 | * Append a directory of stylesheets.
258 | *
259 | * @param {String} html
260 | * @param {String} optimizedPath
261 | * @param {String} sourceStyleDir
262 | * @param {Object} attrs
263 | */
264 |
265 | wiring.appendStylesDir = function appendStylesDir(html, optimizedPath, sourceStyleDir, attrs) {
266 | var sourceStyleList = fs.readdirSync(path.resolve(sourceStyleDir));
267 | return this.appendFiles(html, 'css', optimizedPath, sourceStyleList, attrs);
268 | };
269 |
270 | /**
271 | * Read in the contents of a resolved file path as a string.
272 | *
273 | * @param {String} filePath
274 | */
275 |
276 | wiring.readFileAsString = function readFileAsString(filePath) {
277 | return fs.readFileSync(path.resolve(filePath), 'utf8');
278 | };
279 |
280 | /**
281 | * Write the content of a string to a resolved file path.
282 | *
283 | * @param {String} html
284 | * @param {String} filePath
285 | */
286 |
287 | wiring.writeFileFromString = function writeFileFromString(html, filePath) {
288 | fs.writeFileSync(path.resolve(filePath), html, 'utf8');
289 | };
290 |
--------------------------------------------------------------------------------
/lib/test/helpers.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Collection of unit test helpers. (mostly related to Mocha syntax)
3 | * @module test/helpers
4 | */
5 |
6 | 'use strict';
7 | var fs = require('fs');
8 | var path = require('path');
9 | var _ = require('lodash');
10 | var rimraf = require('rimraf');
11 | var mkdirp = require('mkdirp');
12 | var chalk = require('chalk');
13 | var yeoman = require('yeoman-environment');
14 | var assert = require('yeoman-assert');
15 | var generators = require('../..');
16 | var RunContext = require('../test/run-context');
17 | var adapter = require('../test/adapter');
18 |
19 | exports.decorated = [];
20 |
21 | exports.deprecate = function (message) {
22 | console.log(chalk.yellow('(!) ') + message);
23 | };
24 |
25 | /**
26 | * Create a function that will clean up the test directory,
27 | * cd into it, and create a dummy gruntfile inside. Intended for use
28 | * as a callback for the mocha `before` hook.
29 | *
30 | * @param {String} dir - path to the test directory
31 | * @returns {Function} mocha callback
32 | */
33 |
34 | exports.setUpTestDirectory = function before(dir) {
35 | return function (done) {
36 | exports.testDirectory(dir, function () {
37 | exports.gruntfile({ dummy: true }, done);
38 | });
39 | };
40 | };
41 |
42 | /**
43 | * Create a function that will clean up the test directory,
44 | * cd into it, and create a dummy gruntfile inside. Intended for use
45 | * as a callback for the mocha `before` hook.
46 | *
47 | * @deprecated use helpers.setUpDirectory instead
48 | * @param {String} dir - path to the test directory
49 | * @returns {Function} mocha callback
50 | */
51 |
52 | exports.before = function (dir) {
53 | this.deprecate('before is deprecated. Use setUpTestDirectory instead');
54 | return exports.setUpTestDirectory(dir);
55 | };
56 |
57 | /**
58 | * Wrap a method with custom functionality.
59 | *
60 | * @deprecated use sinon.js instead
61 | * @param {Object} context - context to find the original method
62 | * @param {String} method - name of the method to wrap
63 | * @param {Function} replacement - executes before the original method
64 | * @param {Object} options - config settings
65 | */
66 |
67 | exports.decorate = function decorate(context, method, replacement, options) {
68 | this.deprecate('generators.test.decorate() is deprecated. Prefer using sinon module.');
69 | options = options || {};
70 | replacement = replacement || function () {};
71 |
72 | var naturalMethod = context[method];
73 |
74 | exports.decorated.push({
75 | context: context,
76 | method: method,
77 | naturalMethod: naturalMethod
78 | });
79 |
80 | context[method] = function () {
81 | var rep = replacement.apply(context, arguments);
82 |
83 | if (!options.stub) {
84 | return naturalMethod.apply(context, arguments);
85 | }
86 |
87 | return rep;
88 | };
89 | };
90 |
91 | /**
92 | * Override a method with custom functionality.
93 | *
94 | * @deprecated use sinon.js instead
95 | * @param {Object} context - context to find the original method
96 | * @param {String} method - name of the method to wrap
97 | * @param {Function} replacement - executes before the original method
98 | */
99 | exports.stub = function stub(context, method, replacement) {
100 | this.deprecate('generators.test.stub() is deprecated. Prefer using sinon module.');
101 | exports.decorate(context, method, replacement, { stub: true });
102 | };
103 |
104 | /**
105 | * Restore the original behavior of all decorated and stubbed methods
106 | * @deprecated use sinon.js instead
107 | */
108 | exports.restore = function restore() {
109 | this.deprecate('generators.test.restore() is deprecated. Prefer using sinon module.');
110 | exports.decorated.forEach(function (dec) {
111 | dec.context[dec.method] = dec.naturalMethod;
112 | });
113 | };
114 |
115 | /**
116 | *
117 | * Generates a new Gruntfile.js in the current working directory based on
118 | * options hash passed in.
119 | *
120 | * @param {Object} options - Grunt configuration
121 | * @param {Function} done - callback to call on completion
122 | * @example
123 | * before(helpers.gruntfile({
124 | * foo: {
125 | * bar: ''
126 | * }
127 | * }));
128 | *
129 | */
130 |
131 | exports.gruntfile = function (options, done) {
132 | var config = 'grunt.initConfig(' + JSON.stringify(options, null, 2) + ');';
133 | config = config.split('\n').map(function (line) {
134 | return ' ' + line;
135 | }).join('\n');
136 |
137 | var out = [
138 | 'module.exports = function (grunt) {',
139 | config,
140 | '};'
141 | ];
142 |
143 | fs.writeFile('Gruntfile.js', out.join('\n'), done);
144 | };
145 |
146 | // Façade assert module for backward compatibility
147 | exports.assertFile = assert.file;
148 | exports.assertNoFile = assert.noFile;
149 | exports.assertFiles = assert.files;
150 | exports.assertFileContent = assert.fileContent;
151 | exports.assertNoFileContent = assert.noFileContent;
152 | exports.assertTextEqual = assert.textEqual;
153 | exports.assertImplement = assert.implement;
154 |
155 | /**
156 | * Clean-up the test directory and cd into it.
157 | * Call given callback after entering the test directory.
158 | * @param {String} dir - path to the test directory
159 | * @param {Function} cb - callback executed after setting working directory to dir
160 | * @example
161 | * testDirectory(path.join(__dirname, './temp'), function () {
162 | * fs.writeFileSync('testfile', 'Roses are red.');
163 | * );
164 | */
165 |
166 | exports.testDirectory = function (dir, cb) {
167 | if (!dir) {
168 | throw new Error('Missing directory');
169 | }
170 |
171 | dir = path.resolve(dir);
172 |
173 | // Make sure we're not deleting CWD by moving to top level folder. As we `cd` in the
174 | // test dir after cleaning up, this shouldn't be perceivable.
175 | process.chdir('/');
176 |
177 | rimraf(dir, function (err) {
178 | if (err) {
179 | return cb(err);
180 | }
181 | mkdirp.sync(dir);
182 | process.chdir(dir);
183 | cb();
184 | });
185 | };
186 |
187 | /**
188 | * Answer prompt questions for the passed-in generator
189 | * @param {Generator} generator - a Yeoman generator
190 | * @param {Object} answers - an object where keys are the
191 | * generators prompt names and values are the answers to
192 | * the prompt questions
193 | * @example
194 | * mockPrompt(angular, {'bootstrap': 'Y', 'compassBoostrap': 'Y'});
195 | */
196 |
197 | exports.mockPrompt = function (generator, answers) {
198 | var promptModule = generator.env.adapter.prompt;
199 | answers = answers || {};
200 |
201 | var DummyPrompt = adapter.DummyPrompt;
202 | Object.keys(promptModule.prompts).forEach(function (name) {
203 | promptModule.registerPrompt(name, DummyPrompt.bind(DummyPrompt, answers));
204 | });
205 | };
206 |
207 | /**
208 | * Restore defaults prompts on a generator.
209 | * @param {Generator} generator
210 | */
211 | exports.restorePrompt = function (generator) {
212 | generator.env.adapter.prompt.restoreDefaultPrompts();
213 | };
214 |
215 | /**
216 | * Create a simple, dummy generator
217 | */
218 |
219 | exports.createDummyGenerator = function () {
220 | return generators.Base.extend({
221 | test: function () {
222 | this.shouldRun = true;
223 | }
224 | });
225 | };
226 |
227 | /**
228 | * Create a generator, using the given dependencies and controller arguments
229 | * Dependecies can be path (autodiscovery) or an array [, ]
230 | *
231 | * @param {String} name - the name of the generator
232 | * @param {Array} dependencies - paths to the generators dependencies
233 | * @param {Array|String} args - arguments to the generator;
234 | * if String, will be split on spaces to create an Array
235 | * @param {Object} options - configuration for the generator
236 | * @example
237 | * var deps = ['../../app',
238 | * '../../common',
239 | * '../../controller',
240 | * '../../main',
241 | * [createDummyGenerator(), 'testacular:app']
242 | * ];
243 | * var angular = createGenerator('angular:app', deps);
244 | */
245 |
246 | exports.createGenerator = function (name, dependencies, args, options) {
247 | var env = yeoman.createEnv();
248 | this.registerDependencies(env, dependencies);
249 |
250 | return env.create(name, { arguments: args, options: options });
251 | };
252 |
253 | /**
254 | * Register a list of dependent generators into the provided env.
255 | * Dependecies can be path (autodiscovery) or an array [, ]
256 | *
257 | * @param {Array} dependencies - paths to the generators dependencies
258 | */
259 |
260 | exports.registerDependencies = function (env, dependencies) {
261 | dependencies.forEach(function (dependency) {
262 | if (_.isArray(dependency)) {
263 | env.registerStub.apply(env, dependency);
264 | } else {
265 | env.register(dependency);
266 | }
267 | });
268 | };
269 |
270 | /**
271 | * Run the provided Generator
272 | * @param {String|Function} Generator - Generator constructor or namespace
273 | * @return {RunContext}
274 | */
275 |
276 | exports.run = function (Generator, settings) {
277 | return new RunContext(Generator, settings);
278 | };
279 |
--------------------------------------------------------------------------------
/test/wiring.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, it */
2 | 'use strict';
3 | var path = require('path');
4 | var fs = require('fs');
5 | var wiring = require('../lib/actions/wiring');
6 | var yeoman = require('..');
7 | var assert = yeoman.assert;
8 |
9 | describe('generators.Base (actions/wiring)', function () {
10 | before(function () {
11 | this.fixtures = path.join(__dirname, 'fixtures');
12 | });
13 |
14 | it('generate a simple block', function () {
15 | var res = wiring.generateBlock('js', 'main.js', [
16 | 'path/file1.js',
17 | 'path/file2.js'
18 | ]);
19 |
20 | assert.equal(res.trim(), '\npath/file1.js,path/file2.js ');
21 | });
22 |
23 | it('generate a simple block with search path', function () {
24 | var res = wiring.generateBlock('js', 'main.js', [
25 | 'path/file1.js',
26 | 'path/file2.js'
27 | ], '.tmp/');
28 |
29 | assert.equal(res.trim(), '\npath/file1.js,path/file2.js ');
30 | });
31 |
32 | it('generate block with multiple search paths', function () {
33 | var res = wiring.generateBlock('js', 'main.js', [
34 | 'path/file1.js',
35 | 'path/file2.js'
36 | ], ['.tmp/', 'dist/']);
37 |
38 | assert.equal(res.trim(), '\npath/file1.js,path/file2.js ');
39 | });
40 |
41 | it('append js files to an html string', function () {
42 | var html = '';
43 | var res = wiring.appendFiles(html, 'js', 'out/file.js', ['in/file1.js', 'in/file2.js']);
44 | var fixture = fs.readFileSync(path.join(this.fixtures, 'js_block.html'),
45 | 'utf-8').trim();
46 |
47 | assert.textEqual(res, fixture);
48 | });
49 |
50 | it('appendFiles work the same using the object syntax', function () {
51 | var html = '';
52 | var res = wiring.appendFiles(html, 'js', 'out/file.js', ['in/file1.js', 'in/file2.js']);
53 | var res2 = wiring.appendFiles({
54 | html: html,
55 | fileType: 'js',
56 | optimizedPath: 'out/file.js',
57 | sourceFileList: ['in/file1.js', 'in/file2.js']
58 | });
59 |
60 | assert.equal(res, res2);
61 | });
62 |
63 | it('append content in the right place', function () {
64 | var html = '';
65 | var expected = '';
66 | assert.equal(wiring.append(html, 'section', 'TEST'), expected);
67 | assert.equal(wiring.domUpdate(html, 'section', 'TEST', 'a'), expected);
68 | });
69 |
70 | it('prepend content in the right place', function () {
71 | var html = '';
72 | var expected = '';
73 | assert.equal(wiring.prepend(html, 'section', 'TEST'), expected);
74 | assert.equal(wiring.domUpdate(html, 'section', 'TEST', 'p'), expected);
75 | });
76 |
77 | it('replace content correctly', function () {
78 | var html = '';
79 | var expected = '';
80 | assert.equal(wiring.domUpdate(html, 'section', 'TEST', 'r'), expected);
81 | });
82 |
83 | it('delete content correctly', function () {
84 | var html = '';
85 | var expected = '';
86 | assert.equal(wiring.domUpdate(html, 'section', 'TEST', 'd'), expected);
87 | });
88 |
89 | it('append to files in the right place', function () {
90 | var html = '';
91 | var expected = '';
92 | var filepath = path.join(this.fixtures, 'append-prepend-to-file.html');
93 |
94 | fs.writeFileSync(filepath, html, 'utf-8');
95 |
96 | wiring.appendToFile(filepath, 'section', 'TEST');
97 |
98 | var actual = fs.readFileSync(filepath, 'utf-8').trim();
99 |
100 | assert.equal(actual, expected);
101 |
102 | fs.writeFileSync(filepath, html, 'utf-8');
103 | });
104 |
105 | it('prepend to files in the right place', function () {
106 | var html = '';
107 | var expected = '';
108 | var filepath = path.join(this.fixtures, 'append-prepend-to-file.html');
109 |
110 | fs.writeFileSync(filepath, html, 'utf-8');
111 |
112 | wiring.prependToFile(filepath, 'section', 'TEST');
113 |
114 | var actual = fs.readFileSync(filepath, 'utf-8').trim();
115 |
116 | assert.equal(actual, expected);
117 |
118 | fs.writeFileSync(filepath, html, 'utf-8');
119 | });
120 |
121 | it('handle with non-ascii characters well', function () {
122 | var html = '';
123 | var expected = 'Hello World!你好世界!ハローワールド!안녕 하세요 세계!
';
124 | assert.equal(wiring.domUpdate(html, 'p', 'Hello World!你好世界!ハローワールド!안녕 하세요 세계!', 'p'), expected);
125 | });
126 |
127 | describe('#appendFiles()', function () {
128 | it('work the same using the object syntax', function () {
129 | var html = '';
130 | var res = wiring.appendFiles(html, 'js', 'out/file.js', ['in/file1.js', 'in/file2.js']);
131 | var res2 = wiring.appendFiles({
132 | html: html,
133 | fileType: 'js',
134 | optimizedPath: 'out/file.js',
135 | sourceFileList: ['in/file1.js', 'in/file2.js']
136 | });
137 |
138 | assert.equal(res, res2);
139 | });
140 |
141 | it('work with css file', function () {
142 | var html = '';
143 | var res = wiring.appendFiles(html, 'css', 'out/file.css', ['in/file1.css', 'in/file2.css']);
144 | var fixture = fs.readFileSync(path.join(this.fixtures, 'css_block.html'), 'utf-8').trim();
145 |
146 | assert.textEqual(res, fixture);
147 | });
148 |
149 | it('work with attributes params', function () {
150 | var html = '';
151 | var res = wiring.appendFiles({
152 | html: html,
153 | fileType: 'js',
154 | optimizedPath: 'out/file.js',
155 | sourceFileList: ['in/file1.js', 'in/file2.js'],
156 | attrs: {
157 | 'data-test': 'my-attr'
158 | }
159 | });
160 | var fixture = fs.readFileSync(path.join(this.fixtures, 'js_block_with_attr.html'), 'utf-8').trim();
161 |
162 | assert.textEqual(res, fixture);
163 | });
164 | });
165 |
166 | describe('#appendScripts()', function () {
167 | it('append scripts', function () {
168 | var html = '';
169 | var res = wiring.appendScripts(html, 'out/file.js', ['in/file1.js', 'in/file2.js']);
170 | var fixture = fs.readFileSync(path.join(this.fixtures, 'js_block.html'), 'utf-8').trim();
171 | assert.textEqual(res, fixture);
172 | });
173 | });
174 |
175 | describe('#removeScript()', function () {
176 | it('remove script', function () {
177 | var withScript = '';
178 | var html = '';
179 |
180 | var res = wiring.removeScript(withScript, 'file1.js');
181 | assert.textEqual(res, html);
182 | });
183 | });
184 |
185 | describe('#appendStyles()', function () {
186 | it('append styles', function () {
187 | var html = '';
188 | var res = wiring.appendStyles(html, 'out/file.css', ['in/file1.css', 'in/file2.css']);
189 | var fixture = fs.readFileSync(path.join(this.fixtures, 'css_block.html'), 'utf-8').trim();
190 |
191 | assert.textEqual(res, fixture);
192 | });
193 | });
194 |
195 | describe('#removeStyle()', function () {
196 | it('remove style', function () {
197 | var withStyle = '';
198 | var html = '';
199 |
200 | var res = wiring.removeStyle(withStyle, 'file1.css');
201 |
202 | assert.textEqual(res, html);
203 | });
204 | });
205 |
206 | describe('#appendScriptsDir()', function () {
207 | it('append scripts directory', function () {
208 | var html = '';
209 | var res = wiring.appendScriptsDir(html, 'out/file.js', path.join(__dirname, 'fixtures/dir-fixtures'));
210 | var fixture = fs.readFileSync(path.join(this.fixtures, 'js_block_dir.html'), 'utf-8').trim();
211 |
212 | assert.textEqual(res, fixture);
213 | });
214 | });
215 |
216 | describe('#appendStylesDir()', function () {
217 | it('append styles directory', function () {
218 | var html = '';
219 | var res = wiring.appendStylesDir(html, 'out/file.css', path.join(__dirname, 'fixtures/dir-css-fixtures'));
220 | var fixture = fs.readFileSync(path.join(this.fixtures, 'css_block_dir.html'), 'utf-8').trim();
221 |
222 | assert.textEqual(res, fixture);
223 | });
224 | });
225 | });
226 |
--------------------------------------------------------------------------------
/test/remote.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, before, beforeEach */
2 | 'use strict';
3 | var os = require('os');
4 | var path = require('path');
5 | var fs = require('fs');
6 | var assert = require('assert');
7 | var yeoman = require('yeoman-environment');
8 | var nock = require('nock');
9 | var generators = require('..');
10 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
11 | var tmpdir = path.join(os.tmpdir(), 'yeoman-remote');
12 |
13 | describe('generators.Base#remote()', function () {
14 | before(generators.test.setUpTestDirectory(tmpdir));
15 |
16 | beforeEach(function () {
17 | this.env = yeoman.createEnv([], {}, new TestAdapter());
18 | this.env.registerStub(generators.test.createDummyGenerator(), 'dummy');
19 | this.dummy = this.env.create('dummy');
20 | nock('https://github.com')
21 | .get('/yeoman/generator/archive/master.tar.gz')
22 | .replyWithFile(200, path.join(__dirname, 'fixtures/testRemoteFile.tar.gz'));
23 | });
24 |
25 | it('remotely fetch a package on github', function (done) {
26 | this.dummy.remote('yeoman', 'generator', done);
27 | });
28 |
29 | it('cache the result internally into a `_cache` folder', function (done) {
30 | this.dummy.remote('yeoman', 'generator', function () {
31 | fs.stat(path.join(this.dummy.cacheRoot(), 'yeoman/generator/master'), done);
32 | }.bind(this));
33 | });
34 |
35 | it('invoke `cb` with a remote object to interract with the downloaded package', function (done) {
36 | this.dummy.remote('yeoman', 'generator', function (err, remote) {
37 | if (err) return done(err);
38 |
39 | assert.implement(remote, ['copy', 'template', 'directory']);
40 | done();
41 | });
42 | });
43 |
44 | describe('github', function () {
45 | describe('callback argument remote#copy', function () {
46 | beforeEach(function (done) {
47 | this.dummy.remote('yeoman', 'generator', 'master', function (err, remote) {
48 | this.dummy.foo = 'foo';
49 | remote.copy('test/fixtures/template.jst', 'remote-githug/template.js');
50 | this.dummy._writeFiles(done);
51 | }.bind(this), true);
52 | });
53 |
54 | it('copy a file from a remote resource', function () {
55 | var data = fs.readFileSync('remote-githug/template.js');
56 | assert.equal(data, 'var foo = \'foo\';\n');
57 | });
58 | });
59 |
60 | describe('callback argument remote#bulkCopy', function () {
61 | beforeEach(function (done) {
62 | this.dummy.remote('yeoman', 'generator', 'master', function (err, remote) {
63 | remote.bulkCopy('test/fixtures/foo-template.js', 'remote-githug/foo-template.js');
64 | this.dummy._writeFiles(done);
65 | }.bind(this), true);
66 | });
67 |
68 | it('copy a file from a remote resource', function (done) {
69 | fs.stat('remote-githug/foo-template.js', done);
70 | });
71 |
72 | it('doesn\'t process templates on bulkCopy', function () {
73 | var data = fs.readFileSync('remote-githug/foo-template.js');
74 | assert.equal(data, 'var <%= foo %> = \'<%= foo %>\';\n');
75 | });
76 | });
77 |
78 | describe('callback argument remote#directory', function () {
79 | beforeEach(function (done) {
80 | this.dummy.remote('yeoman', 'generator', 'master', function (err, remote) {
81 | remote.directory('test/generators', 'remote-githug/generators');
82 | this.dummy._writeFiles(done);
83 | }.bind(this), true);
84 | });
85 |
86 | it('copy a directory from a remote resource', function (done) {
87 | fs.stat('remote-githug/generators/test-angular.js', done);
88 | });
89 | });
90 |
91 | describe('callback argument remote#bulkDirectory', function () {
92 | beforeEach(function (done) {
93 | this.dummy.remote('yeoman', 'generator', 'master', function (err, remote) {
94 | remote.bulkDirectory('test/fixtures', 'remote-githug/fixtures');
95 | this.dummy.conflicter.force = true;
96 | this.dummy.conflicter.resolve(done);
97 | }.bind(this), true);
98 | });
99 |
100 | it('copy a directory from a remote resource', function (done) {
101 | fs.stat('remote-githug/fixtures/foo.js', done);
102 | });
103 |
104 | it('doesn\'t process templates on bulkDirectory', function () {
105 | var data = fs.readFileSync('remote-githug/fixtures/foo-template.js');
106 | assert.equal(data, 'var <%= foo %> = \'<%= foo %>\';\n');
107 | });
108 | });
109 |
110 | describe('callback argument remote fileUtils Environment instances', function () {
111 | beforeEach(function (done) {
112 | this.cachePath = path.join(this.dummy.cacheRoot(), 'yeoman/generator/master');
113 | this.dummy.remote('yeoman', 'generator', 'master', function (err, remote) {
114 | this.remoteArg = remote;
115 | done();
116 | }.bind(this));
117 | });
118 |
119 | it('.src is scoped to cachePath', function () {
120 | assert.equal(this.remoteArg.src.fromBase('.'), this.cachePath);
121 | assert.equal(this.remoteArg.src.fromDestBase('.'), this.dummy.destinationRoot());
122 | });
123 |
124 | it('.dest is scoped to destinationRoot', function () {
125 | assert.equal(this.remoteArg.dest.fromBase('.'), this.dummy.destinationRoot());
126 | assert.equal(this.remoteArg.dest.fromDestBase('.'), this.cachePath);
127 | });
128 | });
129 | });
130 |
131 | describe('absolute', function () {
132 | describe('callback argument remote#copy', function () {
133 | beforeEach(function (done) {
134 | this.dummy.foo = 'foo';
135 | this.dummy.remote('https://github.com/yeoman/generator/archive/master.tar.gz', function (err, remote) {
136 | remote.copy('test/fixtures/template.jst', 'remote-absolute/template.js');
137 | this.dummy._writeFiles(done);
138 | }.bind(this), true);
139 | });
140 |
141 | it('copy a file from a remote resource', function () {
142 | var data = fs.readFileSync('remote-absolute/template.js');
143 | assert.equal(data, 'var foo = \'foo\';\n');
144 | });
145 | });
146 |
147 | describe('callback argument remote#bulkCopy', function () {
148 | beforeEach(function (done) {
149 | this.dummy.remote('https://github.com/yeoman/generator/archive/master.tar.gz', function (err, remote) {
150 | remote.bulkCopy('test/fixtures/foo-template.js', 'remote-absolute/foo-template.js');
151 | this.dummy.conflicter.force = true;
152 | this.dummy.conflicter.resolve(done);
153 | }.bind(this), true);
154 | });
155 |
156 | it('copy a file from a remote resource', function (done) {
157 | fs.stat('remote-absolute/foo-template.js', done);
158 | });
159 |
160 | it('doesn\'t process templates on bulkCopy', function () {
161 | var data = fs.readFileSync('remote-absolute/foo-template.js');
162 | assert.equal(data, 'var <%= foo %> = \'<%= foo %>\';\n');
163 | });
164 | });
165 |
166 | describe('callback argument remote#directory', function () {
167 | beforeEach(function (done) {
168 | this.dummy.remote('https://github.com/yeoman/generator/archive/master.tar.gz', function (err, remote) {
169 | remote.directory('test/generators', 'remote-absolute/generators');
170 | this.dummy._writeFiles(done);
171 | }.bind(this), true);
172 | });
173 |
174 | it('copy a directory from a remote resource', function (done) {
175 | fs.stat('remote-absolute/generators/test-angular.js', done);
176 | });
177 | });
178 |
179 | describe('callback argument remote#bulkDirectory', function () {
180 | beforeEach(function (done) {
181 | this.dummy.remote('https://github.com/yeoman/generator/archive/master.tar.gz', function (err, remote) {
182 | remote.bulkDirectory('test/fixtures', 'remote-absolute/fixtures');
183 | this.dummy.conflicter.force = true;
184 | this.dummy.conflicter.resolve(done);
185 | }.bind(this), true);
186 | });
187 |
188 | it('copy a directory from a remote resource', function (done) {
189 | fs.stat('remote-absolute/fixtures/foo.js', done);
190 | });
191 |
192 | it('doesn\'t process templates on bulkDirectory', function () {
193 | var data = fs.readFileSync('remote-absolute/fixtures/foo-template.js');
194 | assert.equal(data, 'var <%= foo %> = \'<%= foo %>\';\n');
195 | });
196 | });
197 |
198 | describe('callback argument remote fileUtils Environment instances', function () {
199 | beforeEach(function (done) {
200 | this.cachePath = path.join(this.dummy.cacheRoot(), 'httpsgithubcomyeomangeneratorarchivemastertargz');
201 | this.dummy.remote('https://github.com/yeoman/generator/archive/master.tar.gz', function (err, remote) {
202 | this.remoteArg = remote;
203 | done();
204 | }.bind(this));
205 | });
206 |
207 | it('.src is scoped to cachePath', function () {
208 | assert.equal(this.remoteArg.src.fromBase('.'), this.cachePath);
209 | assert.equal(this.remoteArg.src.fromDestBase('.'), this.dummy.destinationRoot());
210 | });
211 |
212 | it('.dest is scoped to destinationRoot', function () {
213 | assert.equal(this.remoteArg.dest.fromBase('.'), this.dummy.destinationRoot());
214 | assert.equal(this.remoteArg.dest.fromDestBase('.'), this.cachePath);
215 | });
216 | });
217 | });
218 | });
219 |
--------------------------------------------------------------------------------
/test/prompt-suggestion.js:
--------------------------------------------------------------------------------
1 | /*global describe, it, before, after, beforeEach, afterEach */
2 | 'use strict';
3 | var path = require('path');
4 | var assert = require('assert');
5 | var os = require('os');
6 | var Storage = require('../lib/util/storage');
7 | var promptSuggestion = require('../lib/util/prompt-suggestion');
8 | var rimraf = require('rimraf');
9 | var inquirer = require('inquirer');
10 | var env = require('yeoman-environment');
11 | var FileEditor = require('mem-fs-editor');
12 |
13 | describe('PromptSuggestion', function () {
14 | beforeEach(function () {
15 | this.memFs = env.createEnv().sharedFs;
16 | this.fs = FileEditor.create(this.memFs);
17 | this.storePath = path.join(os.tmpdir(), 'suggestion-config.json');
18 | this.store = new Storage('suggestion', this.fs, this.storePath);
19 | this.store.set('promptValues', { respuesta: 'foo' });
20 | });
21 |
22 | afterEach(function (done) {
23 | rimraf(this.storePath, done);
24 | });
25 |
26 | describe('.prefillQuestions()', function () {
27 | it('require a store parameter', function () {
28 | assert.throws(promptSuggestion.prefillQuestions.bind(null));
29 | });
30 |
31 | it('require a questions parameter', function () {
32 | assert.throws(promptSuggestion.prefillQuestions.bind(this.store));
33 | });
34 |
35 | it('take a questions parameter', function () {
36 | promptSuggestion.prefillQuestions(this.store, []);
37 | });
38 |
39 | it('take a question object', function () {
40 | var question = {
41 | name: 'respuesta',
42 | default: 'bar',
43 | store: true
44 | };
45 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
46 |
47 | assert.equal(result.default, 'foo');
48 | });
49 |
50 | it('take a question array', function () {
51 | var question = [{
52 | name: 'respuesta',
53 | default: 'bar',
54 | store: true
55 | }];
56 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
57 |
58 | assert.equal(result.default, 'foo');
59 | });
60 |
61 | it('don\'t override default when store is set to false', function () {
62 | var question = {
63 | name: 'respuesta',
64 | default: 'bar',
65 | store: false
66 | };
67 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
68 | assert.equal(result.default, 'bar');
69 | });
70 |
71 | it('override default when store is set to true', function () {
72 | var question = {
73 | name: 'respuesta',
74 | default: 'bar',
75 | store: true
76 | };
77 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
78 | assert.equal(result.default, 'foo');
79 | });
80 |
81 | it('keep inquirer objects', function () {
82 | var question = {
83 | type: 'checkbox',
84 | name: 'respuesta',
85 | default: ['bar'],
86 | store: true,
87 | choices: [new inquirer.Separator('spacer')]
88 | };
89 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
90 | assert.ok(result.choices[0] instanceof inquirer.Separator);
91 | });
92 |
93 | describe('take a checkbox', function () {
94 | beforeEach(function () {
95 | this.store.set('promptValues', {
96 | respuesta: ['foo']
97 | });
98 | });
99 |
100 | it('override default from an array with objects', function () {
101 | var question = {
102 | type: 'checkbox',
103 | name: 'respuesta',
104 | default: ['bar'],
105 | store: true,
106 | choices: [{
107 | value: 'foo',
108 | name: 'foo'
109 | }, new inquirer.Separator('spacer'), {
110 | value: 'bar',
111 | name: 'bar'
112 | }, {
113 | value: 'baz',
114 | name: 'baz'
115 | }]
116 | };
117 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
118 |
119 | result.choices.forEach(function (choice) {
120 | assert.equal(choice.checked, false);
121 | });
122 | assert.deepEqual(result.default, ['foo']);
123 | });
124 |
125 | it('override default from an array with strings', function () {
126 | var question = {
127 | type: 'checkbox',
128 | name: 'respuesta',
129 | default: ['bar'],
130 | store: true,
131 | choices: ['foo', new inquirer.Separator('spacer'), 'bar', 'baz']
132 | };
133 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
134 |
135 | assert.deepEqual(result.default, ['foo']);
136 | });
137 |
138 | describe('with multiple defaults', function () {
139 | beforeEach(function () {
140 | this.store.set('promptValues', {
141 | respuesta: ['foo', 'bar']
142 | });
143 | });
144 |
145 | it('from an array with objects', function () {
146 | var question = {
147 | type: 'checkbox',
148 | name: 'respuesta',
149 | default: ['bar'],
150 | store: true,
151 | choices: [{
152 | value: 'foo',
153 | name: 'foo'
154 | }, new inquirer.Separator('spacer'), {
155 | value: 'bar',
156 | name: 'bar'
157 | }, {
158 | value: 'baz',
159 | name: 'baz'
160 | }]
161 | };
162 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
163 |
164 | result.choices.forEach(function (choice) {
165 | assert.equal(choice.checked, false);
166 | });
167 | assert.deepEqual(result.default, ['foo', 'bar']);
168 | });
169 |
170 | it('from an array with strings', function () {
171 | var question = {
172 | type: 'checkbox',
173 | name: 'respuesta',
174 | default: ['bar'],
175 | store: true,
176 | choices: ['foo', new inquirer.Separator('spacer'), 'bar', 'baz']
177 | };
178 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
179 |
180 | assert.deepEqual(result.default, ['foo', 'bar']);
181 | });
182 | });
183 | });
184 |
185 | describe('take a rawlist / expand', function () {
186 | beforeEach(function () {
187 | this.store.set('promptValues', {
188 | respuesta: 'bar'
189 | });
190 | });
191 |
192 | it('override default arrayWithObjects', function () {
193 | var question = {
194 | type: 'rawlist',
195 | name: 'respuesta',
196 | default: 0,
197 | store: true,
198 | choices: [{
199 | value: 'foo',
200 | name: 'foo'
201 | }, new inquirer.Separator('spacer'), {
202 | value: 'bar',
203 | name: 'bar'
204 | }, {
205 | value: 'baz',
206 | name: 'baz'
207 | }]
208 | };
209 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
210 |
211 | assert.equal(result.default, 2);
212 | });
213 |
214 | it('override default arrayWithObjects', function () {
215 | var question = {
216 | type: 'rawlist',
217 | name: 'respuesta',
218 | default: 0,
219 | store: true,
220 | choices: ['foo', new inquirer.Separator('spacer'), 'bar', 'baz']
221 | };
222 | var result = promptSuggestion.prefillQuestions(this.store, question)[0];
223 |
224 | assert.equal(result.default, 2);
225 | });
226 | });
227 | });
228 |
229 | describe('.storeAnswers()', function () {
230 | beforeEach(function () {
231 | this.store.set('promptValues', { respuesta: 'foo' });
232 | });
233 |
234 | it('require a store parameter', function () {
235 | assert.throws(promptSuggestion.storeAnswers.bind(null));
236 | });
237 |
238 | it('require a question parameter', function () {
239 | assert.throws(promptSuggestion.storeAnswers.bind(this.store));
240 | });
241 |
242 | it('require a answer parameter', function () {
243 | assert.throws(promptSuggestion.storeAnswers.bind(this.store, []));
244 | });
245 |
246 | it('take a answer parameter', function () {
247 | promptSuggestion.storeAnswers(this.store, [], {});
248 | });
249 |
250 | it('store answer in global store', function () {
251 | var question = {
252 | name: 'respuesta',
253 | default: 'bar',
254 | store: true
255 | };
256 | var mockAnswers = {
257 | respuesta: 'baz'
258 | };
259 | promptSuggestion.prefillQuestions(this.store, question);
260 | promptSuggestion.storeAnswers(this.store, question, mockAnswers);
261 |
262 | assert.equal(this.store.get('promptValues').respuesta, 'baz');
263 | });
264 |
265 | it('don\'t store answer in global store', function () {
266 | var question = {
267 | name: 'respuesta',
268 | default: 'bar',
269 | store: false
270 | };
271 | var mockAnswers = {
272 | respuesta: 'baz'
273 | };
274 | promptSuggestion.prefillQuestions(this.store, question);
275 | promptSuggestion.storeAnswers(this.store, question, mockAnswers);
276 | assert.equal(this.store.get('promptValues').respuesta, 'foo');
277 | });
278 |
279 | it('store answer from rawlist type', function () {
280 | var question = {
281 | type: 'rawlist',
282 | name: 'respuesta',
283 | default: 0,
284 | store: true,
285 | choices: ['foo', new inquirer.Separator('spacer'), 'bar', 'baz']
286 | };
287 | var mockAnswers = {
288 | respuesta: 'baz'
289 | };
290 | promptSuggestion.prefillQuestions(this.store, question);
291 | promptSuggestion.storeAnswers(this.store, question, mockAnswers);
292 | assert.equal(this.store.get('promptValues').respuesta, 'baz');
293 | });
294 | });
295 | });
296 |
--------------------------------------------------------------------------------
/lib/actions/actions.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | var fs = require('fs');
3 | var path = require('path');
4 | var mkdirp = require('mkdirp');
5 | var isBinaryFile = require('isbinaryfile');
6 | var chalk = require('chalk');
7 | var xdgBasedir = require('xdg-basedir');
8 |
9 | /**
10 | * @mixin
11 | * @alias actions/actions
12 | */
13 | var actions = module.exports;
14 |
15 | /**
16 | * Stores and return the cache root for this class. The cache root is used to
17 | * `git clone` repositories from github by `.remote()` for example.
18 | */
19 |
20 | actions.cacheRoot = function cacheRoot() {
21 | return path.join(xdgBasedir.cache, 'yeoman');
22 | };
23 |
24 | // Copy helper for two versions of copy action
25 | actions._prepCopy = function _prepCopy(source, destination, process) {
26 | var body;
27 | destination = destination || source;
28 |
29 | if (typeof destination === 'function') {
30 | process = destination;
31 | destination = source;
32 | }
33 |
34 | source = this.isPathAbsolute(source) ? source : path.join(this.sourceRoot(), source);
35 | destination = this.isPathAbsolute(destination) ? destination : path.join(this.destinationRoot(), destination);
36 |
37 | var encoding = null;
38 | var binary = isBinaryFile(source);
39 | if (!binary) {
40 | encoding = 'utf8';
41 | }
42 |
43 | body = fs.readFileSync(source, encoding);
44 |
45 | if (typeof process === 'function' && !binary) {
46 | body = process(body, source, destination, {
47 | encoding: encoding
48 | });
49 | }
50 |
51 | return {
52 | body: body,
53 | encoding: encoding,
54 | destination: destination,
55 | source: source
56 | };
57 | };
58 |
59 | /**
60 | * Make some of the file API aware of our source/destination root paths.
61 | * `copy`, `template` (only when could be applied/required by legacy code),
62 | * `write` and alike consider.
63 | *
64 | * @param {String} source Source file to copy from. Relative to this.sourceRoot()
65 | * @param {String} destination Destination file to write to. Relative to this.destinationRoot()
66 | * @param {Function} process
67 | */
68 |
69 | actions.copy = function copy(source, destination, process) {
70 | var file = this._prepCopy(source, destination, process);
71 | try {
72 | file.body = this.engine(file.body, this);
73 | } catch (err) {
74 | // this happens in some cases when trying to copy a JS file like lodash/underscore
75 | // (conflicting the templating engine)
76 | }
77 |
78 | this.fs.copy(file.source, file.destination, {
79 | process: function () {
80 | return file.body;
81 | }
82 | });
83 |
84 | return this;
85 | };
86 |
87 | /**
88 | * Bulk copy
89 | * https://github.com/yeoman/generator/pull/359
90 | * https://github.com/yeoman/generator/issues/350
91 | *
92 | * A copy method skipping templating and conflict checking. It will allow copying
93 | * a large amount of files without causing too much recursion errors. You should
94 | * never use this method, unless there's no other solution.
95 | *
96 | * @param {String} source Source file to copy from. Relative to this.sourceRoot()
97 | * @param {String} destination Destination file to write to. Relative to this.destinationRoot()
98 | * @param {Function} process
99 | */
100 |
101 | actions.bulkCopy = function bulkCopy(source, destination, process) {
102 |
103 | var file = this._prepCopy(source, destination, process);
104 |
105 | mkdirp.sync(path.dirname(file.destination));
106 | fs.writeFileSync(file.destination, file.body);
107 |
108 | // synchronize stats and modification times from the original file.
109 | var stats = fs.statSync(file.source);
110 | try {
111 | fs.chmodSync(file.destination, stats.mode);
112 | fs.utimesSync(file.destination, stats.atime, stats.mtime);
113 | } catch (err) {
114 | this.log.error('Error setting permissions of "' + chalk.bold(file.destination) + '" file: ' + err);
115 | }
116 |
117 | this.log.create(file.destination);
118 | return this;
119 | };
120 |
121 | /**
122 | * A simple method to read the content of the a file borrowed from Grunt:
123 | * https://github.com/gruntjs/grunt/blob/master/lib/grunt/file.js
124 | *
125 | * Discussion and future plans:
126 | * https://github.com/yeoman/generator/pull/220
127 | *
128 | * The encoding is `utf8` by default, to read binary files, pass the proper
129 | * encoding or null. Non absolute path are prefixed by the source root.
130 | *
131 | * @param {String} filepath
132 | * @param {String} [encoding="utf-8"] Character encoding.
133 | */
134 |
135 | actions.read = function read(filepath, encoding) {
136 | if (!this.isPathAbsolute(filepath)) {
137 | filepath = path.join(this.sourceRoot(), filepath);
138 | }
139 |
140 | var contents = this.fs.read(filepath, { raw: true });
141 | return contents.toString(encoding || 'utf8');
142 | };
143 |
144 | /**
145 | * Writes a chunk of data to a given `filepath`, checking for collision prior
146 | * to the file write.
147 | *
148 | * @param {String} filepath
149 | * @param {String} content
150 | * @param {Object} writeFile An object containing options for the file writing, as shown here: http://nodejs.org/api/fs.html#fs_fs_writefile_filename_data_options_callback
151 | */
152 |
153 | actions.write = function write(filepath, content, writeFile) {
154 | this.fs.write(filepath, content);
155 | return this;
156 | };
157 |
158 | /**
159 | * Gets a template at the relative source, executes it and makes a copy
160 | * at the relative destination. If the destination is not given it's assumed
161 | * to be equal to the source relative to destination.
162 | *
163 | * Use configured engine to render the provided `source` template at the given
164 | * `destination`. The `destination` path its a template itself and supports variable
165 | * interpolation. `data` is an optional hash to pass to the template, if undefined,
166 | * executes the template in the generator instance context.
167 | *
168 | * use options to pass parameters to engine (like _.templateSettings)
169 | *
170 | * @param {String} source Source file to read from. Relative to this.sourceRoot()
171 | * @param {String} destination Destination file to write to. Relative to this.destinationRoot().
172 | * @param {Object} data Hash to pass to the template. Leave undefined to use the generator instance context.
173 | * @param {Object} options
174 | */
175 |
176 | actions.template = function template(source, destination, data, options) {
177 | if (!destination || !this.isPathAbsolute(destination)) {
178 | destination = path.join(
179 | this.destinationRoot(),
180 | this.engine(destination || source, data || this, options)
181 | );
182 | }
183 |
184 | if (!this.isPathAbsolute(source)) {
185 | source = path.join(
186 | this.sourceRoot(),
187 | this.engine(source, data || this, options)
188 | );
189 | }
190 |
191 | var body = this.engine(this.fs.read(source), data || this, options);
192 |
193 | // Using copy to keep the file mode of the previous file
194 | this.fs.copy(source, destination, {
195 | process: function () {
196 | return body;
197 | }
198 | });
199 | return this;
200 | };
201 |
202 | /**
203 | * The engine method is the function used whenever a template needs to be rendered.
204 | *
205 | * It uses the configured engine (default: underscore) to render the `body`
206 | * template with the provided `data`.
207 | *
208 | * use options to pass paramters to engine (like _.templateSettings)
209 | *
210 | * @param {String} body
211 | * @param {Object} data
212 | * @param {Object} options
213 | */
214 |
215 | actions.engine = function engine(body, data, options) {
216 | if (!this._engine) {
217 | throw new Error('Trying to render template without valid engine.');
218 | }
219 |
220 | return this._engine.detect && this._engine.detect(body) ?
221 | this._engine(body, data, options) :
222 | body;
223 | };
224 |
225 | // Shared directory method
226 | actions._directory = function _directory(source, destination, process, bulk) {
227 | // Only add sourceRoot if the path is not absolute
228 | var root = this.isPathAbsolute(source) ? source : path.join(this.sourceRoot(), source);
229 | var files = this.expandFiles('**', { dot: true, cwd: root });
230 |
231 | destination = destination || source;
232 |
233 | if (typeof destination === 'function') {
234 | process = destination;
235 | destination = source;
236 | }
237 |
238 | var cp = this.copy;
239 | if (bulk) {
240 | cp = this.bulkCopy;
241 | }
242 |
243 | // get the path relative to the template root, and copy to the relative destination
244 | for (var i in files) {
245 | var dest = path.join(destination, files[i]);
246 | cp.call(this, path.join(root, files[i]), dest, process);
247 | }
248 |
249 | return this;
250 | };
251 |
252 | /**
253 | * Copies recursively the files from source directory to root directory.
254 | *
255 | * @param {String} source Source directory to copy from. Relative to this.sourceRoot()
256 | * @param {String} destination Directory to copy the source files into. Relative to this.destinationRoot().
257 | * @param {Function} process
258 | */
259 |
260 | actions.directory = function directory(source, destination, process) {
261 | return this._directory(source, destination, process);
262 | };
263 |
264 | /**
265 | * Copies recursively the files from source directory to root directory.
266 | *
267 | * A copy method skiping templating and conflict checking. It will allow copying
268 | * a large amount of files without causing too much recursion errors. You should
269 | * never use this method, unless there's no other solution.
270 | *
271 | * @param {String} source Source directory to copy from. Relative to this.sourceRoot()
272 | * @param {String} destination Directory to copy the source files into.Relative to this.destinationRoot().
273 | * @param {Function} process
274 | */
275 |
276 | actions.bulkDirectory = function directory(source, destination, process) {
277 | // Join the source here because the conflicter will not run
278 | // until next tick, which resets the source root on remote
279 | // bulkCopy operations
280 | source = path.join(this.sourceRoot(), source);
281 | this.conflicter.checkForCollision(destination, null, function (err, status) {
282 | // create or force means file write, identical or skip prevent the
283 | // actual write.
284 | if (/force|create/.test(status)) {
285 | this._directory(source, destination, process, true);
286 | }
287 |
288 | }.bind(this));
289 | return this;
290 | };
291 |
--------------------------------------------------------------------------------
/test/run-context.js:
--------------------------------------------------------------------------------
1 | /*global it, describe, before, beforeEach, afterEach */
2 | 'use strict';
3 | var os = require('os');
4 | var fs = require('fs');
5 | var path = require('path');
6 | var assert = require('assert');
7 | var sinon = require('sinon');
8 | var inquirer = require('inquirer');
9 | var yo = require('..');
10 | var helpers = yo.test;
11 | var tmpdir = path.join(os.tmpdir(), 'yeoman-run-context');
12 |
13 | var RunContext = require('../lib/test/run-context');
14 |
15 | describe('RunContext', function () {
16 | beforeEach(function () {
17 | process.chdir(__dirname);
18 | this.defaultInput = inquirer.prompts.input;
19 | var Dummy = this.Dummy = helpers.createDummyGenerator();
20 | this.execSpy = sinon.spy();
21 | Dummy.prototype.exec = this.execSpy;
22 | this.ctx = new RunContext(Dummy);
23 | });
24 |
25 | afterEach(function (done) {
26 | process.chdir(__dirname);
27 | if (this.ctx.completed) return done();
28 | this.ctx.on('end', done);
29 | });
30 |
31 | describe('constructor', function () {
32 | it('accept path parameter', function (done) {
33 | var ctx = new RunContext(path.join(__dirname, './fixtures/custom-generator-simple'));
34 | ctx
35 | .on('ready', function () {
36 | assert(ctx.env.get('simple:app'));
37 | })
38 | .on('end', done);
39 | });
40 |
41 | it('propagate generator error events', function (done) {
42 | var error = new Error();
43 | var Dummy = helpers.createDummyGenerator();
44 | var execSpy = sinon.stub().throws(error);
45 | Dummy.prototype.exec = execSpy;
46 | var ctx = new RunContext(Dummy);
47 | ctx.on('error', function (err) {
48 | sinon.assert.calledOnce(execSpy);
49 | assert.equal(err, error);
50 | done();
51 | });
52 | });
53 |
54 | it('accept generator constructor parameter (and assign gen:test as namespace)', function (done) {
55 | this.ctx.on('ready', function () {
56 | assert(this.ctx.env.get('gen:test'));
57 | done();
58 | }.bind(this));
59 | });
60 |
61 | it('run the generator asynchronously', function (done) {
62 | assert(this.execSpy.notCalled);
63 | this.ctx.on('end', function () {
64 | sinon.assert.calledOnce(this.execSpy);
65 | done();
66 | }.bind(this));
67 | });
68 |
69 | it('reset mocked prompt after running', function (done) {
70 | this.ctx.on('end', function () {
71 | assert.equal(this.defaultInput, inquirer.prompts.input);
72 | done();
73 | }.bind(this));
74 | });
75 |
76 | it('automatically run in a random tmpdir', function (done) {
77 | this.ctx.on('end', function () {
78 | assert.notEqual(process.cwd(), __dirname);
79 | assert.equal(fs.realpathSync(os.tmpdir()), path.dirname(process.cwd()));
80 | done();
81 | }.bind(this));
82 | });
83 |
84 | it('allows an option to not automatically run in tmpdir', function (done) {
85 | var cwd = process.cwd();
86 | this.ctx.settings.tmpdir = false;
87 | this.ctx.on('end', function (err) {
88 | assert.equal(cwd, process.cwd());
89 | done();
90 | });
91 | });
92 |
93 | it('accepts settings', function () {
94 | var Dummy = helpers.createDummyGenerator();
95 | var ctx = new RunContext(Dummy, { tmpdir: false });
96 | assert.equal(ctx.settings.tmpdir, false);
97 | });
98 |
99 | it('only run a generator once', function (done) {
100 | this.ctx.on('end', function () {
101 | sinon.assert.calledOnce(this.execSpy);
102 | done();
103 | }.bind(this));
104 | this.ctx._run();
105 | this.ctx._run();
106 | });
107 | });
108 |
109 | describe('error handling', function () {
110 |
111 | function removeListeners(host, handlerName) {
112 | if (!host) return;
113 | // store the original handlers for the host
114 | var originalHandlers = host.listeners(handlerName);
115 | // remove the current handlers for the host
116 | host.removeAllListeners(handlerName);
117 | return originalHandlers;
118 | }
119 |
120 | function setListeners(host, handlerName, handlers) {
121 | if (!host) return;
122 | handlers.forEach(host.on.bind(host, handlerName));
123 | }
124 |
125 | function processError(host, handlerName, cb) {
126 | if (!host) return;
127 | host.once(handlerName, cb);
128 | }
129 |
130 | beforeEach(function () {
131 | this.originalHandlersProcess = removeListeners(process, 'uncaughtException');
132 | this.originalHandlersProcessDomain = removeListeners(process.domain, 'error');
133 | });
134 |
135 | afterEach(function () {
136 | setListeners(process, 'uncaughtException', this.originalHandlersProcess);
137 | setListeners(process.domain, 'error', this.originalHandlersProcessDomain);
138 | });
139 |
140 | it('throw an error when no listener is present', function (done) {
141 | var error = new Error('dummy exception');
142 | var execSpy = sinon.stub().throws(error);
143 |
144 | var errorHandler = function (err) {
145 | sinon.assert.calledOnce(execSpy);
146 | assert.equal(err, error);
147 | done();
148 | };
149 |
150 | // tests can be run via 2 commands : 'gulp test' or 'mocha'
151 | // in 'mocha' case the error has to be caught using process.on('uncaughtException')
152 | // in 'gulp' case the error has to be caught using process.domain.on('error')
153 | // as we don't know in which case we are, we set the error handler for both
154 | processError(process, 'uncaughtException', errorHandler);
155 | processError(process.domain, 'error', errorHandler);
156 |
157 | var Dummy = helpers.createDummyGenerator();
158 | Dummy.prototype.exec = execSpy;
159 |
160 | setImmediate(function () {
161 | new RunContext(Dummy);
162 | });
163 |
164 | });
165 |
166 | });
167 |
168 | describe('#inDir()', function () {
169 | beforeEach(function () {
170 | process.chdir(__dirname);
171 | this.tmp = tmpdir;
172 | });
173 |
174 | it('call helpers.testDirectory()', function () {
175 | sinon.spy(helpers, 'testDirectory');
176 | this.ctx.inDir(this.tmp);
177 | assert(helpers.testDirectory.withArgs(this.tmp).calledOnce);
178 | helpers.testDirectory.restore();
179 | });
180 |
181 | it('is chainable', function () {
182 | assert.equal(this.ctx.inDir(this.tmp), this.ctx);
183 | });
184 |
185 | it('accepts optional `cb` to be invoked with resolved `dir`', function (done) {
186 | var ctx = new RunContext(this.Dummy);
187 | var cb = sinon.spy(function () {
188 | sinon.assert.calledOnce(cb);
189 | sinon.assert.calledOn(cb, ctx);
190 | sinon.assert.calledWith(cb, path.resolve(this.tmp));
191 | }.bind(this));
192 | ctx.inDir(this.tmp, cb).on('end', done);
193 | });
194 |
195 | it('optional `cb` can use `this.async()` to delay execution', function (done) {
196 | var ctx = new RunContext(this.Dummy);
197 | var delayed = false;
198 | var cb = sinon.spy(function () {
199 | var release = this.async();
200 | setTimeout(function () {
201 | delayed = true;
202 | release();
203 | }.bind(this), 1);
204 | });
205 | ctx.inDir(this.tmp, cb)
206 | .on('ready', function () {
207 | assert(delayed);
208 | })
209 | .on('end', done);
210 | });
211 | });
212 |
213 | describe('#inTmpDir', function () {
214 | it('call helpers.testDirectory()', function () {
215 | sinon.spy(helpers, 'testDirectory');
216 | this.ctx.inTmpDir();
217 | sinon.assert.calledOnce(helpers.testDirectory);
218 | helpers.testDirectory.restore();
219 | });
220 |
221 | it('is chainable', function () {
222 | assert.equal(this.ctx.inTmpDir(), this.ctx);
223 | });
224 |
225 | it('accepts optional `cb` to be invoked with resolved `dir`', function (done) {
226 | var ctx = this.ctx;
227 | var cb = sinon.spy(function (dir) {
228 | assert.equal(this, ctx);
229 | assert(dir.indexOf(os.tmpdir()) > -1);
230 | });
231 | this.ctx.inTmpDir(cb).on('end', done);
232 | });
233 | });
234 |
235 | describe('#withArguments()', function () {
236 | it('provide arguments to the generator when passed as Array', function (done) {
237 | this.ctx.withArguments(['one', 'two']);
238 | this.ctx.on('end', function () {
239 | assert.deepEqual(this.execSpy.firstCall.thisValue.arguments, ['one', 'two']);
240 | done();
241 | }.bind(this));
242 | });
243 |
244 | it('provide arguments to the generator when passed as String', function (done) {
245 | this.ctx.withArguments('foo bar');
246 | this.ctx.on('end', function () {
247 | assert.deepEqual(this.execSpy.firstCall.thisValue.arguments, ['foo', 'bar']);
248 | done();
249 | }.bind(this));
250 | });
251 |
252 | it('throws when arguments passed is neither a String or an Array', function () {
253 | assert.throws(this.ctx.withArguments.bind(this.ctx, { foo: 'bar' }));
254 | });
255 |
256 | it('is chainable', function (done) {
257 | this.ctx.withArguments('foo').withArguments('bar');
258 | this.ctx.on('end', function () {
259 | assert.deepEqual(this.execSpy.firstCall.thisValue.arguments, ['foo', 'bar']);
260 | done();
261 | }.bind(this));
262 | });
263 | });
264 |
265 | describe('#withOptions()', function () {
266 | it('provide options to the generator', function (done) {
267 | this.ctx.withOptions({ foo: 'bar' });
268 | this.ctx.on('end', function () {
269 | assert.equal(this.execSpy.firstCall.thisValue.options.foo, 'bar');
270 | done();
271 | }.bind(this));
272 | });
273 |
274 | it('set skip-install by default', function (done) {
275 | this.ctx.on('end', function () {
276 | assert.equal(this.execSpy.firstCall.thisValue.options['skip-install'], true);
277 | done();
278 | }.bind(this));
279 | });
280 |
281 | it('allow skip-install to be overriden', function (done) {
282 | this.ctx.withOptions({ 'skip-install': false });
283 | this.ctx.on('end', function () {
284 | assert.equal(this.execSpy.firstCall.thisValue.options['skip-install'], false);
285 | done();
286 | }.bind(this));
287 | });
288 |
289 | it('is chainable', function (done) {
290 | this.ctx.withOptions({ foo: 'bar' }).withOptions({ john: 'doe' });
291 | this.ctx.on('end', function () {
292 | var options = this.execSpy.firstCall.thisValue.options;
293 | assert.equal(options.foo, 'bar');
294 | assert.equal(options.john, 'doe');
295 | done();
296 | }.bind(this));
297 | });
298 | });
299 |
300 | describe('#withPrompts()', function () {
301 | it('is call automatically', function (done) {
302 | this.Dummy.prototype.askFor = function () {
303 | this.prompt({
304 | name: 'yeoman',
305 | type: 'input',
306 | message: 'Hey!',
307 | default: 'pass'
308 | }, function (answers) {
309 | assert.equal(answers.yeoman, 'pass');
310 | });
311 | };
312 | this.ctx.on('end', done);
313 | });
314 |
315 | it('mock the prompt', function (done) {
316 | this.Dummy.prototype.askFor = function () {
317 | this.prompt({
318 | name: 'yeoman',
319 | type: 'input',
320 | message: 'Hey!'
321 | }, function (answers) {
322 | assert.equal(answers.yeoman, 'yes please');
323 | });
324 | };
325 | this.ctx
326 | .withPrompts({ yeoman: 'yes please' })
327 | .on('end', done);
328 | });
329 |
330 | it('is chainable', function (done) {
331 | this.Dummy.prototype.askFor = function () {
332 | var cb = this.async();
333 | this.prompt([{
334 | name: 'yeoman',
335 | type: 'input',
336 | message: 'Hey!'
337 | }, {
338 | name: 'yo',
339 | type: 'input',
340 | message: 'Yo!'
341 | }], function (answers) {
342 | assert.equal(answers.yeoman, 'yes please');
343 | assert.equal(answers.yo, 'yo man');
344 | cb();
345 | });
346 | };
347 | this.ctx
348 | .withPrompts({ yeoman: 'yes please' })
349 | .withPrompts({ yo: 'yo man' })
350 | .on('end', done);
351 | });
352 | });
353 |
354 | describe('#withGenerators()', function () {
355 | it('register paths', function (done) {
356 | this.ctx.withGenerators([
357 | path.join(__dirname, './fixtures/custom-generator-simple')
358 | ]).on('ready', function () {
359 | assert(this.ctx.env.get('simple:app'));
360 | done();
361 | }.bind(this));
362 | });
363 |
364 | it('register mocked generator', function (done) {
365 | this.ctx.withGenerators([
366 | [helpers.createDummyGenerator(), 'dummy:gen']
367 | ]).on('ready', function () {
368 | assert(this.ctx.env.get('dummy:gen'));
369 | done();
370 | }.bind(this));
371 | });
372 |
373 | it('allow multiple calls', function (done) {
374 | this.ctx.withGenerators([
375 | path.join(__dirname, './fixtures/custom-generator-simple')
376 | ]).withGenerators([
377 | [helpers.createDummyGenerator(), 'dummy:gen']
378 | ]).on('ready', function () {
379 | assert(this.ctx.env.get('dummy:gen'));
380 | assert(this.ctx.env.get('simple:app'));
381 | done();
382 | }.bind(this));
383 | });
384 | });
385 | });
386 |
--------------------------------------------------------------------------------
/test/actions.js:
--------------------------------------------------------------------------------
1 | /*global describe, before, after, it, afterEach, beforeEach */
2 | 'use strict';
3 | var fs = require('fs');
4 | var os = require('os');
5 | var path = require('path');
6 | var sinon = require('sinon');
7 | var generators = require('..');
8 | var helpers = generators.test;
9 | var assert = generators.assert;
10 | var TestAdapter = require('../lib/test/adapter').TestAdapter;
11 | var tmpdir = path.join(os.tmpdir(), 'yeoman-actions');
12 |
13 | describe('generators.Base (actions/actions)', function () {
14 | before(helpers.setUpTestDirectory(tmpdir));
15 |
16 | beforeEach(function () {
17 | var env = this.env = generators([], {}, new TestAdapter());
18 | env.registerStub(helpers.createDummyGenerator(), 'dummy');
19 | this.dummy = env.create('dummy');
20 |
21 | this.fixtures = path.join(__dirname, 'fixtures');
22 | this.dummy.sourceRoot(this.fixtures);
23 | this.dummy.foo = 'bar';
24 | });
25 |
26 | describe('#sourceRoot()', function () {
27 | it('updates the "_sourceRoot" property when root is given', function () {
28 | this.dummy.sourceRoot(this.fixtures);
29 | assert.equal(this.dummy._sourceRoot, this.fixtures);
30 | });
31 |
32 | it('returns the updated or current value of "_sourceRoot"', function () {
33 | assert.equal(this.dummy.sourceRoot(), this.fixtures);
34 | });
35 | });
36 |
37 | describe('#destinationRoot()', function () {
38 | it('updates the "_destinationRoot" property when root is given', function () {
39 | this.dummy.destinationRoot('.');
40 | assert.equal(this.dummy._destinationRoot, process.cwd());
41 | });
42 |
43 | it('returns the updated or current value of "_destinationRoot"', function () {
44 | assert.equal(this.dummy.destinationRoot(), process.cwd());
45 | });
46 | });
47 |
48 | describe('#cacheRoot()', function () {
49 | it('returns the cache root where yeoman stores all temp files', function () {
50 | assert(/yeoman$/.test(this.dummy.cacheRoot()));
51 | });
52 | });
53 |
54 | describe('#copy()', function () {
55 | before(function (done) {
56 | this.dummy.copy(path.join(__dirname, 'fixtures/foo.js'), 'write/to/bar.js');
57 | this.dummy.copy('foo.js', 'write/to/foo.js');
58 | this.dummy.copy('foo-copy.js');
59 | this.dummy.copy('yeoman-logo.png');
60 | this.dummy.copy(path.join(__dirname, 'fixtures/lodash-copy.js'), 'write/to/lodash.js');
61 | this.dummy.copy('foo-process.js', 'write/to/foo-process.js', function (contents) {
62 | contents = contents.replace('foo', 'bar');
63 | contents = contents.replace('\r\n', '\n');
64 |
65 | return contents;
66 | });
67 |
68 | var oldDestRoot = this.dummy.destinationRoot();
69 | this.dummy.destinationRoot('write/to');
70 | this.dummy.copy('foo.js', 'foo-destRoot.js');
71 | this.dummy.destinationRoot(oldDestRoot);
72 | this.dummy._writeFiles(done);
73 | });
74 |
75 | it('copy source files relative to the "sourceRoot" value', function (done) {
76 | fs.stat('write/to/foo.js', done);
77 | });
78 |
79 | it('copy to destination files relative to the "destinationRoot" value', function (done) {
80 | fs.stat('write/to/foo-destRoot.js', done);
81 | });
82 |
83 | it('allow absolute path, and prevent the relative paths join', function (done) {
84 | fs.stat('write/to/bar.js', done);
85 | });
86 |
87 | it('allow to copy without using the templating (conficting with lodash/underscore)', function (done) {
88 | fs.stat('write/to/lodash.js', done);
89 | });
90 |
91 | it('defaults the destination to the source filepath value', function (done) {
92 | fs.stat('foo-copy.js', done);
93 | });
94 |
95 | it('retains executable mode on copied files', function (done) {
96 | // Don't run on windows
97 | if (process.platform === 'win32') return done();
98 |
99 | fs.stat('write/to/bar.js', function (err, stats) {
100 | if (err) throw err;
101 | assert(stats.mode & 1 === 1, 'File be executable.');
102 | done();
103 | });
104 | });
105 |
106 | it('process source contents via function', function (done) {
107 | fs.readFile('write/to/foo-process.js', function (err, data) {
108 | if (err) throw err;
109 | assert.textEqual(String(data), 'var bar = \'foo\';\n');
110 | done();
111 | });
112 | });
113 | });
114 |
115 | describe('#bulkCopy()', function () {
116 | before(function () {
117 | this.dummy.bulkCopy(path.join(__dirname, 'fixtures/foo.js'), 'write/to/foo.js');
118 | this.dummy.bulkCopy(path.join(__dirname, 'fixtures/foo-template.js'), 'write/to/noProcess.js');
119 | });
120 |
121 | it('copy a file', function (done) {
122 | fs.readFile('write/to/foo.js', function (err, data) {
123 | if (err) throw err;
124 | assert.textEqual(String(data), 'var foo = \'foo\';\n');
125 | done();
126 | });
127 | });
128 |
129 | it('does not run conflicter or template engine', function () {
130 | var data = fs.readFileSync('write/to/noProcess.js');
131 | assert.textEqual(String(data), 'var <%= foo %> = \'<%= foo %>\';\n<%%= extra %>\n');
132 |
133 | this.dummy.bulkCopy(path.join(__dirname, 'fixtures/foo.js'), 'write/to/noProcess.js');
134 | var data2 = fs.readFileSync('write/to/noProcess.js');
135 | assert.textEqual(String(data2), 'var foo = \'foo\';\n');
136 | });
137 | });
138 |
139 | describe('#read()', function () {
140 | it('read files relative to the "sourceRoot" value', function () {
141 | var body = this.dummy.read('foo.js');
142 | assert.textEqual(body, 'var foo = \'foo\';' + '\n');
143 | });
144 |
145 | it('allow absolute path, and prevent the relative paths join', function () {
146 | var body = this.dummy.read(path.join(__dirname, 'fixtures/foo.js'));
147 | assert.textEqual(body, 'var foo = \'foo\';' + '\n');
148 | });
149 | });
150 |
151 | describe('#write()', function () {
152 | before(function (done) {
153 | this.body = 'var bar = \'bar\';' + '\n';
154 | this.dummy.write('write/to/foobar.js', this.body);
155 | this.dummy._writeFiles(done);
156 | });
157 |
158 | it('writes the specified files relative to the "destinationRoot" value', function () {
159 | var body = this.body;
160 | var actual = fs.readFileSync('write/to/foobar.js', 'utf8');
161 | assert.ok(actual, body);
162 | });
163 | });
164 |
165 | describe('#template()', function () {
166 | describe('without options', function () {
167 | before(function (done) {
168 | // Create file with weird permission for testing
169 | var permFileName = this.fixtures + '/perm-test.js';
170 | fs.writeFileSync(permFileName, 'var foo;', { mode: parseInt(733, 8) });
171 |
172 | this.dummy.foo = 'fooooooo';
173 | this.dummy.template('perm-test.js', 'write/to/perm-test.js');
174 | this.dummy.template('foo-template.js', 'write/to/from-template.js');
175 | this.dummy.template('foo-template.js');
176 | this.dummy.template('<%=foo%>-file.js');
177 | this.dummy.template('foo-template.js', 'write/to/<%=foo%>-directory/from-template.js', {
178 | foo: 'bar'
179 | });
180 | this.dummy.template('foo-template.js', 'write/to/from-template-bar.js', {
181 | foo: 'bar'
182 | });
183 | this.dummy.template('template-tags.jst', 'write/to/template-tags.js', {
184 | foo: 'bar',
185 | bar: 'foo'
186 | });
187 | this.dummy._writeFiles(done);
188 | });
189 |
190 | after(function () {
191 | fs.unlinkSync(this.fixtures + '/perm-test.js');
192 | });
193 |
194 | it('copy and process source file to destination', function (done) {
195 | fs.stat('write/to/from-template.js', done);
196 | });
197 |
198 | it('defaults the destination to the source filepath value, relative to "destinationRoot" value', function () {
199 | var body = fs.readFileSync('foo-template.js', 'utf8');
200 | assert.textEqual(body, 'var fooooooo = \'fooooooo\';\n<%= extra %>\n');
201 | });
202 |
203 | it('process underscore templates with the passed-in data', function () {
204 | var body = fs.readFileSync('write/to/from-template-bar.js', 'utf8');
205 | assert.textEqual(body, 'var bar = \'bar\';\n<%= extra %>\n');
206 | });
207 |
208 | it('process underscore templates with the actual generator instance, when no data is given', function () {
209 | var body = fs.readFileSync('write/to/from-template.js', 'utf8');
210 | assert.textEqual(body, 'var fooooooo = \'fooooooo\';\n<%= extra %>\n');
211 | });
212 |
213 | it('parses `${}` tags', function () {
214 | var body = fs.readFileSync('write/to/template-tags.js', 'utf8');
215 | assert.textEqual(body, 'foo = bar\n');
216 | });
217 |
218 | it('process underscode templates in destination filename', function () {
219 | var body = fs.readFileSync('fooooooo-file.js', 'utf8');
220 | assert.textEqual(body, 'var fooooooo = \'fooooooo\';\n');
221 | });
222 |
223 | it('process underscore templates in destination path', function () {
224 | var body = fs.readFileSync('write/to/bar-directory/from-template.js', 'utf8');
225 | assert.textEqual(body, 'var bar = \'bar\';\n<%= extra %>\n');
226 | });
227 |
228 | it('keep file mode', function () {
229 | var originFileStat = fs.statSync(this.fixtures + '/perm-test.js');
230 | var bodyStat = fs.statSync('write/to/perm-test.js');
231 | assert.equal(originFileStat.mode, bodyStat.mode);
232 | });
233 |
234 | });
235 |
236 | describe('with options', function () {
237 | beforeEach(function (done) {
238 | this.src = 'template-setting.xml';
239 | this.dest = 'write/to/template-setting.xml';
240 | this.dummy.template(this.src, this.dest, { foo: 'bar' }, {
241 | evaluate: /\{\{([\s\S]+?)\}\}/g,
242 | interpolate: /\{\{=([\s\S]+?)\}\}/g,
243 | escape: /\{\{-([\s\S]+?)\}\}/g
244 | });
245 | this.dummy._writeFiles(done);
246 | });
247 |
248 | it('uses tags specified in option', function () {
249 | var body = fs.readFileSync(this.dest, 'utf8');
250 | assert.textEqual(body, 'bar <%= foo %>;\n');
251 | });
252 | });
253 |
254 | describe('with custom tags', function () {
255 | beforeEach(function (done) {
256 | this.src = 'custom-template-setting.xml';
257 | this.dest = 'write/to/custom-template-setting.xml';
258 | this.spy = sinon.spy();
259 |
260 | var oldEngineOptions = this.dummy.options.engine.options;
261 |
262 | this.dummy.options.engine.options = {
263 | detecter: /\{\{?[^\}]+\}\}/,
264 | matcher: /\{\{\{([^\}]+)\}\}/g,
265 | start: '{{',
266 | end: '}}'
267 | };
268 |
269 | this.dummy.template(this.src, this.dest, {
270 | foo: 'bar',
271 | spy: this.spy
272 | }, {
273 | evaluate: /\{\{([\s\S]+?)\}\}/g,
274 | interpolate: /\{\{=([\s\S]+?)\}\}/g,
275 | escape: /\{\{-([\s\S]+?)\}\}/g
276 | });
277 |
278 | this.dummy.options.engine.options = oldEngineOptions;
279 | this.dummy._writeFiles(done);
280 | });
281 |
282 | it('uses tags specified in option and engine', function () {
283 | var body = fs.readFileSync(this.dest, 'utf8');
284 | assert.textEqual(body, 'bar\n');
285 | sinon.assert.calledOnce(this.spy);
286 | });
287 | });
288 | });
289 |
290 | describe('#directory()', function () {
291 | before(function (done) {
292 | this.dummy.directory('./dir-fixtures', 'directory');
293 | this.dummy.directory('./dir-fixtures');
294 | this.dummy.directory('./dir-fixtures', 'directory-processed', function (contents, source) {
295 | if (source.indexOf('foo-process.js') !== -1) {
296 | contents = contents.replace('foo', 'bar');
297 | contents = contents.replace('\r\n', '\n');
298 | }
299 |
300 | return contents;
301 | });
302 | this.dummy._writeFiles(done);
303 | });
304 |
305 | it('copy and process source files to destination', function (done) {
306 | fs.stat('directory/foo-template.js', function (err) {
307 | if (err) {
308 | return done(err);
309 | }
310 | fs.stat('directory/foo.js', done);
311 | });
312 | });
313 |
314 | it('defaults the destination to the source filepath value, relative to "destinationRoot" value', function (done) {
315 | fs.stat('dir-fixtures/foo-template.js', function (err) {
316 | if (err) {
317 | return done(err);
318 | }
319 | fs.stat('dir-fixtures/foo.js', done);
320 | });
321 | });
322 |
323 | it('process underscore templates with the actual generator instance', function () {
324 | var body = fs.readFileSync('directory/foo-template.js', 'utf8');
325 | var foo = this.dummy.foo;
326 | assert.textEqual(body, 'var ' + foo + ' = \'' + foo + '\';\n');
327 | });
328 |
329 | it('process source contents via function', function () {
330 | var body = fs.readFileSync('directory-processed/foo-process.js', 'utf8');
331 | assert.textEqual(body, 'var bar = \'foo\';\n');
332 | });
333 |
334 | });
335 |
336 | describe('#bulkDirectory()', function () {
337 | before(function (done) {
338 | this.dummy.sourceRoot(this.fixtures);
339 | this.dummy.destinationRoot('.');
340 | this.dummy.conflicter.force = true;
341 | // Create temp bulk operation files
342 | // These cannot just be in the repo or the other directory tests fail
343 | require('mkdirp').sync(this.fixtures + '/bulk-operation');
344 | for (var i = 0; i < 1000; i++) {
345 | fs.writeFileSync(this.fixtures + '/bulk-operation/' + i + '.js', i);
346 | }
347 |
348 | // Copy files without processing
349 | this.dummy.bulkDirectory('bulk-operation', 'bulk-operation');
350 | this.dummy.conflicter.resolve(done);
351 | });
352 |
353 | after(function () {
354 | // Now remove them
355 | for (var i = 0; i < 1000; i++) {
356 | fs.unlinkSync(this.fixtures + '/bulk-operation/' + i + '.js');
357 | }
358 | fs.rmdirSync(this.fixtures + '/bulk-operation');
359 | });
360 |
361 | it('bulk copy one thousand files', function (done) {
362 | fs.readFile('bulk-operation/999.js', function (err, data) {
363 | if (err) throw err;
364 | assert.equal(data, '999');
365 | done();
366 | });
367 | });
368 |
369 | it('check for conflict if directory already exists', function (done) {
370 | this.dummy.conflicter.force = true;
371 | this.dummy.bulkDirectory('bulk-operation', 'bulk-operation');
372 | this.dummy.conflicter.resolve(done);
373 | });
374 | });
375 |
376 | describe('#expandFiles()', function () {
377 | before(function (done) {
378 | this.dummy.copy('foo.js', 'write/abc/abc.js');
379 | this.dummy._writeFiles(done);
380 | });
381 | it('returns expand files', function () {
382 | var files = this.dummy.expandFiles('write/abc/**');
383 | assert.deepEqual(files, ['write/abc/abc.js']);
384 | });
385 | it('returns expand files', function () {
386 | var files = this.dummy.expandFiles('abc/**', { cwd: './write' });
387 | assert.deepEqual(files, ['abc/abc.js']);
388 | });
389 | });
390 | });
391 |
--------------------------------------------------------------------------------