diff --git a/.travis.yml b/.travis.yml index f7b2923..fd4e926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,17 @@ node_js: addons: hosts: - local.generatortest.com -install: +before_install: + - rvm use 1.9.3 + - gem install bundler - npm install -g bower before_script: + - echo "Running from ${PWD}" + - echo "Install NPM dependencies" - npm install - - ./test/bin/generate - - sudo mv "${PWD}/test/temp" /vagrant - - sudo /vagrant/bin/provision + - echo "Mock test project" + - "${PWD}/test/bin/mock" + - echo "Provision server" + - sudo "${PWD}/test/temp/bin/provision" + - echo "Copy mock project to /vagrant for end-user tests" + - sudo cp -R "${PWD}/test/temp" /vagrant diff --git a/README.md b/README.md index 7143724..ce6f0b9 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ If you get EMFILE issues, try running: `$ ulimit -n 4096`. ### Deployment -Install [Capistrano v2.15.*][5] & [Ansible][7]: +Install [Capistrano v2.15.*][5] via [Bundler][1] & [Ansible][7]: - $ sudo gem install capistrano:2.15 capistrano-ext colored + $ sudo bundle install $ sudo easy_install pip $ sudo pip install ansible @@ -92,7 +92,7 @@ access the project's *Settings -> Deploy Keys* in Github and add `provisioning/f Next, assuming the server has been provisioned, deploy your code on Github: - $ cap production deploy + $ bundle exec cap production deploy The latest code is now live: @@ -104,7 +104,7 @@ If you deploy to `staging`, the name of the current branch (e.g. `my-feature`) i In the rare event the changes weren't supposed to go live, you can rollback to the previous release: - $ cap production deploy:rollback + $ bundle exec cap production deploy:rollback **Note that deployments use the project's *Github repository* as the source, not your local machine!** @@ -118,15 +118,15 @@ a database or uploaded images. You can **overwrite the remote database** with your local VM's: - $ cap production genesis:up:db + $ bundle exec cap production genesis:up:db You can sync your local files to the remote filesystem: - $ cap production genesis:up:files + $ bundle exec cap production genesis:up:files Or, you can perform both actions together: - $ cap production genesis:up + $ bundle exec cap production genesis:up Once a site is live, you *rarely* need to sync anything up to the remote server. If anything, you usually sync changes *down*. @@ -137,9 +137,9 @@ you usually sync changes *down*. Suppose you have a live site that you need to work on locally. Like the previous section, you can sync down the database, the files (e.g. uploaded images), or both: - $ cap production genesis:down:db - $ cap production genesis:down:files - $ cap production genesis:down + $ bundle exec cap production genesis:down:db + $ bundle exec cap production genesis:down:files + $ bundle exec cap production genesis:down ## Provisioning @@ -152,11 +152,11 @@ The following environments are expected to exist and resolve via DNS to simplify If you're deploying to a new machine (e.g. production.mysite.com), you first need to provision it: - $ cap production genesis:provision + $ bundle exec cap production genesis:provision If there is an error, you may be prompted to re-run the command with an explicit username/password: - $ cap production genesis:provision -S user=myuser -S password=mypassword + $ bundle exec cap production genesis:provision -S user=myuser -S password=mypassword *From that point on, tasks will use a private key (`provisioning/files/ssh/id_rsa`).* @@ -167,14 +167,14 @@ migrate the old server to a new server: $ vagrant up # Provision the new server - $ cap production provision - $ cap production deploy + $ bundle exec cap production provision + $ bundle exec cap production deploy # Download the old site to local - $ cap old genesis:down + $ bundle exec cap old genesis:down # Upload the old site to production - $ cap production genesis:up + $ bundle exec cap production genesis:up Now you can switch DNS for http://www.mysite.com/ to point to http://production.mysite.com/'s IP! @@ -183,7 +183,7 @@ Now you can switch DNS for http://www.mysite.com/ to point to http://production. Most of the functionality regarding remote servers are handled by custom [Capistrano][5] tasks, which you can see by running: - $ cap -T genesis + $ bundle exec cap -T genesis cap genesis:down # Downloads both remote database & syncs remote files into Vagrant cap genesis:down:db # Downloads remote database into Vagrant cap genesis:down:files # Downloads remote files to Vagrant @@ -201,7 +201,7 @@ which you can see by running: Now run any one of those commands against an environemnt: - $ cap local genesis:restart + $ bundle exec cap local genesis:restart ## Troubleshooting @@ -209,7 +209,7 @@ Now run any one of those commands against an environemnt: If you're seeing this: - $ cap staging genesis:ssh + $ bundle exec cap staging genesis:ssh deploy@staging.example.com's password: Then the `deploy` user's ssh keys on your remote server *do not match* the keys in your local repository. @@ -218,17 +218,17 @@ You should first ensure that your local repository is up to date, thereby ensuri $ git checkout master $ git pull origin master - $ cap staging genesis:ssh + $ bundle exec cap staging genesis:ssh If the problem persists, this means that the keys on your remote server are out of date or otherwise incorrect, and you must re-provision by specifying a username and password: - $ cap staging genesis:provision -S user=userWithRootOrSudoAccess -S password=usersHopefullyStrongPassword + $ bundle exec cap staging genesis:provision -S user=userWithRootOrSudoAccess -S password=usersHopefullyStrongPassword ### SSH - Host key mismatch If you're seeing this: - $ cap staging genesis:ssh + $ bundle exec cap staging genesis:ssh @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @@ -402,7 +402,7 @@ sudo /Library/StartupItems/VirtualBox/VirtualBox restart [7]: http://www.ansibleworks.com/ [8]: https://www.virtualbox.org/ [9]: http://nodejs.org/ - +[10]: http://bundler.io/ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/genesis/wordpress/trend.png)](https://bitdeli.com/free "Bitdeli Badge") diff --git a/generator/app/index.js b/generator/app/index.js index 99ec63e..ec1c553 100644 --- a/generator/app/index.js +++ b/generator/app/index.js @@ -385,4 +385,26 @@ WordpressGenerator.prototype.setupDeployment = function() { this.template('deployment/stages/production.rb', 'deployment/stages/production.rb'); }; +WordpressGenerator.prototype.installGems = function() { + var done = this.async(); + var installer = 'bundle'; + + this.log.info(chalk.green('Installing Gems...')); + + this.emit(installer + 'Install'); + + this + .spawnCommand(installer, ['install'], done) + .on('error', done) + .on('exit', this.emit.bind(this, installer + 'Install:end')) + .on('exit', function (err) { + if (err === 127) { + this.log.error('Could not run bundler. Please install with `sudo ' + installer + ' install`.'); + } + + done(err); + }.bind(this)) + ; +}; + module.exports = WordpressGenerator; diff --git a/package.json b/package.json index 5506048..4caf639 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "zombie": "~2.0.0-alpha24" }, "scripts": { - "test": "mocha --reporter spec" + "test": "./test/bin/test" }, "repository": { "type": "git", diff --git a/test/README.md b/test/README.md index 50a2de3..5c0821e 100644 --- a/test/README.md +++ b/test/README.md @@ -13,7 +13,7 @@ $ npm install Generate test project scaffolding: ```shell -$ ./test/bin/generate +$ ./test/bin/mock ``` ### Testing Provisioning @@ -24,12 +24,20 @@ Start test project server: $ (cd test/temp && vagrant up) ``` -Tests will be ran against: +### End-User Testing -> http://local.generatortest.com +Install Gems: -### End-User Testing +```shell +$ (cd test/temp && sudo bundle install) +``` + +Run tests: ```shell $ npm test ``` + +Tests will be ran against: + +> http://local.generatortest.com diff --git a/test/bin/generate b/test/bin/mock similarity index 57% rename from test/bin/generate rename to test/bin/mock index 8c48666..ef5ab4d 100755 --- a/test/bin/generate +++ b/test/bin/mock @@ -1,6 +1,6 @@ #!/usr/bin/env node -var Generator = require('../support/generator'); +var Generator = require('../support/mock-generator'); var generator = new Generator(); generator.run(); diff --git a/test/bin/test b/test/bin/test new file mode 100755 index 0000000..7058d1b --- /dev/null +++ b/test/bin/test @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +var fs = require('fs'); +var Mocha = require('mocha'); +var mocha = new Mocha(); +var testDir = __dirname + '/../'; + +// Setup environment first +mocha.addFile(testDir + '/support/bootstrap.js'); + +// Add all other tests +fs.readdirSync(testDir).filter(function(file) { + return '.js' === file.substr(-3); +}).forEach(function(file) { + mocha.addFile(testDir + file); +}); + +mocha + .reporter('spec') + .ui('bdd') + .run(process.exit) +; diff --git a/test/cap.genesis.deploy.js b/test/cap.genesis.deploy.js new file mode 100644 index 0000000..102661c --- /dev/null +++ b/test/cap.genesis.deploy.js @@ -0,0 +1,17 @@ +'use strict'; + +var assert = require('assert'); +var exec = require('child_process').exec; + +describe('bundle exec cap local deploy', function(done) { + this.timeout(0); + + it('should not fail', function(done) { + exec('bundle exec cap local deploy', { + cwd: __dirname + '/temp' + }, function(err, stdout, stderr) { + assert.ifError(err); + done(); + }); + }); +}); diff --git a/test/generator.js b/test/generator.js index 33fcb71..a4a8f9d 100644 --- a/test/generator.js +++ b/test/generator.js @@ -1,31 +1,29 @@ 'use strict'; var assert = require('assert'); -var Browser = require('zombie'); var fs = require('fs'); var path = require('path'); -describe('Genesis WordPress', function () { - describe('generator', function() { - it('should create required files', function(done) { - [ - 'bin/provision', - 'deployment/deploy.rb', - 'provisioning/provision.yml', - 'provisioning/files/ssh/id_rsa', - 'provisioning/files/ssh/id_rsa.pub', - 'web/wp-config.php', - 'bower.json', - 'Capfile', - 'README.md', - 'Vagrantfile', - ].forEach(function(file) { - var filePath = path.join(__dirname, 'temp', file); +describe('Generator', function() { + it('should create required files', function(done) { + [ + 'bin/provision', + 'deployment/deploy.rb', + 'provisioning/provision.yml', + 'provisioning/files/ssh/id_rsa', + 'provisioning/files/ssh/id_rsa.pub', + 'web/wp-config.php', + 'bower.json', + 'Capfile', + 'Gemfile', + 'README.md', + 'Vagrantfile', + ].forEach(function(file) { + var filePath = path.join(__dirname, 'temp', file); - assert.ok(fs.existsSync(filePath), 'File not created: ' + filePath); - }); - - done(); + assert.ok(fs.existsSync(filePath), 'File not created: ' + filePath); }); + + done(); }); }); diff --git a/test/site.install.js b/test/site.install.js deleted file mode 100644 index f839a13..0000000 --- a/test/site.install.js +++ /dev/null @@ -1,47 +0,0 @@ -'use strict'; - -var assert = require('assert'); -var Browser = require('zombie'); -var fs = require('fs'); -var path = require('path'); - -describe('Genesis WordPress', function () { - describe('site', function() { - it('may not be installed', function(done) { - var browser = new Browser(); - - this.timeout(0); - - browser - .visit('http://local.generatortest.com/wp-admin/install.php') - .then(function() { - if (browser.button('Install WordPress')) { - browser - .fill('Site Title', 'Genesis WordPress Test') - .fill('Username', 'test') - .fill('admin_password', 'test') - .fill('admin_password2', 'test') - .fill('Your E-mail', 'test@example.com') - .uncheck('blog_public') - ; - - return browser.pressButton('Install WordPress'); - } - }) - .then(done, done) - ; - }); - - it('should be installed', function(done) { - var browser = new Browser(); - - browser - .visit('http://local.generatortest.com/wp-admin/install.php') - .then(function() { - assert.equal('Log In', browser.text('a.button')); - }) - .then(done, done) - ; - }) - }); -}); diff --git a/test/support/bootstrap.js b/test/support/bootstrap.js new file mode 100644 index 0000000..29db6f5 --- /dev/null +++ b/test/support/bootstrap.js @@ -0,0 +1,45 @@ +'use strict'; + +var assert = require('assert'); +var Browser = require('zombie'); +var fs = require('fs'); +var path = require('path'); + +describe('Mock site', function() { + it('may not be installed', function(done) { + var browser = new Browser(); + + this.timeout(0); + + browser + .visit('http://local.generatortest.com/wp-admin/install.php') + .then(function() { + if (browser.button('Install WordPress')) { + browser + .fill('Site Title', 'Genesis WordPress Test') + .fill('Username', 'test') + .fill('admin_password', 'test') + .fill('admin_password2', 'test') + .fill('Your E-mail', 'test@example.com') + .uncheck('blog_public') + ; + + return browser.pressButton('Install WordPress'); + } + }) + .then(done, done) + ; + }); + + it('should be installed', function(done) { + var browser = new Browser(); + + browser + .visit('http://local.generatortest.com/wp-admin/install.php') + .then(function() { + assert.equal('Log In', browser.text('a.button')); + }) + .then(done, done) + ; + }) +}); diff --git a/test/support/generator.js b/test/support/generator.js deleted file mode 100644 index bccc13e..0000000 --- a/test/support/generator.js +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env node - -var hooker = require('hooker'); -var path = require('path'); -var helpers = require('yeoman-generator').test; - -var Generator = function() {}; - -Generator.prototype.create = function() { - this.app = helpers.createGenerator('genesis-wordpress:app', [ - [require('../../generator/app'), 'genesis-wordpress:app'] - ]); -}; - -Generator.prototype.prompts = function() { - hooker.hook(this.app, 'prompt', function(prompts, done) { - var answers = { - name: 'GeneratorTest.com', - domain: 'generatortest.com', - ip: '192.168.137.137', - DB_NAME: 'generator_test', - DB_USER: 'generator_test', - DB_PASSWORD: 'generator_test' - }; - - prompts.forEach(function(prompt) { - if (answers[prompt.name]) { - return; - } - - if (prompt.default instanceof Function) { - answers[prompt.name] = prompt.default(answers); - } else { - answers[prompt.name] = prompt.default; - } - }); - - hooker.unhook(this.app, 'prompt'); - - done(answers); - - return hooker.preempt(answers); - }.bind(this)); -}; - -Generator.prototype.run = function() { - helpers.testDirectory(path.join(__dirname, '..', 'temp'), function(err) { - if (err) { - throw err; - } - - this.create(); - this.prompts(); - - this.app.run({}, function() {}); - }.bind(this)); -}; - - -module.exports = Generator; diff --git a/test/support/mock-generator.js b/test/support/mock-generator.js new file mode 100644 index 0000000..b513147 --- /dev/null +++ b/test/support/mock-generator.js @@ -0,0 +1,117 @@ +var fs = require('fs-extra'); +var hooker = require('hooker'); +var helpers = require('yeoman-generator').test; +var path = require('path'); + +var MockGenerator = function(outputDir) { + this.outputDir = __dirname + '/../temp'; +}; + +MockGenerator.prototype.create = function() { + this.app = helpers.createGenerator('genesis-wordpress:app', [ + [require('../../generator/app'), 'genesis-wordpress:app'] + ]); + + this.app.options['skip-install'] = true; +}; + +MockGenerator.prototype.prepare = function() { + this.privatePath = this.outputDir + '/provisioning/files/ssh/id_rsa'; + this.privateKey = fs.existsSync(this.privatePath) ? fs.readFileSync(this.privatePath) : null; + this.publicPath = this.outputDir + '/provisioning/files/ssh/id_rsa.pub'; + this.publicKey = fs.existsSync(this.publicPath) ? fs.readFileSync(this.publicPath) : null; +}; + +MockGenerator.prototype.prompts = function() { + hooker.hook(this.app, 'prompt', function(prompts, done) { + var answers = { + name: 'GeneratorTest.com', + domain: 'generatortest.com', + ip: '192.168.137.137', + DB_NAME: 'generator_test', + DB_USER: 'generator_test', + DB_PASSWORD: 'generator_test' + }; + + prompts.forEach(function(prompt) { + if (answers[prompt.name]) { + return; + } + + if (prompt.default instanceof Function) { + answers[prompt.name] = prompt.default(answers); + } else { + answers[prompt.name] = prompt.default; + } + }); + + hooker.unhook(this.app, 'prompt'); + + done(answers); + + return hooker.preempt(answers); + }.bind(this)); +}; + +MockGenerator.prototype.run = function() { + helpers.testDirectory(this.outputDir, function(err) { + if (err) { + throw err; + } + + this.create(); + this.prepare(); + this.prompts(); + + this.app.run({}, this.finalize.bind(this)); + }.bind(this)); +}; + +MockGenerator.prototype.finalize = function() { + fs.appendFileSync(this.outputDir + '/deployment/deploy.rb', [ + '', + '# Use local repository for testing', + 'set :deploy_via, :copy', + 'set :repository, "."', + 'set :local_repository, "."', + 'set :copy_remote_dir, "/var/www/#{domain}/#{branch}"', + '' + ].join('\n')); + + if (this.privateKey) { + fs.writeFileSync(this.privatePath, this.privateKey); + } + + if (this.publicKey) { + fs.writeFileSync(this.publicPath, this.publicKey); + } + + var vagrantFile = fs.readFileSync(this.outputDir + '/Vagrantfile', 'utf8') + .replace( + new RegExp('(# Remount)'), + [ + '# Mount library for testing', + ' box.vm.synced_folder "../../", "/wordpress", :nfs => true', + '', + ' $1' + ].join('\n') + ) + ; + + fs.writeFileSync(this.outputDir + '/Vagrantfile', vagrantFile); + + // Copy current library into bower location, except for test + fs.copy( + path.resolve(this.outputDir, '../../'), + this.outputDir + '/bower_components/genesis-wordpress', + function(path) { + return !path.match('test'); + }, function(err) { + if (err) { + return console.error(err); + } + } + ); +}; + +module.exports = MockGenerator;