├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── package.json ├── tasks ├── lib │ └── util.js └── wordpressdeploy.js └── test ├── deployments_test.js └── expected ├── custom_options └── default_options /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | backups 5 | test_db.json -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true, 13 | "es5": true 14 | } 15 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-wordpress-deploy 3 | * https://github.com/webrain/grunt-wordpress-deploy 4 | * 5 | * Copyright (c) 2013 Webrain 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | grunt.initConfig({ 14 | jshint: { 15 | all: [ 16 | 'Gruntfile.js', 17 | 'tasks/*.js', 18 | ], 19 | options: { 20 | jshintrc: '.jshintrc', 21 | }, 22 | }, 23 | 24 | clean: { 25 | tests: ['tmp'], 26 | }, 27 | 28 | wordpressdeploy: { 29 | options: { 30 | backups_dir: 'backups_dir/', 31 | rsync_args: ['--verbose', '--progress', '-rlpt', '--compress', '--omit-dir-times'], 32 | exclusions: ['.git', 'tmp/*', 'backups_dir/', 'wp-config.php', 'composer.json', 'composer.lock'] 33 | }, 34 | local: { 35 | title: 'local', 36 | database: 'db_local', 37 | user: 'user_local', 38 | pass: 'pass_local', 39 | host: 'host_local', 40 | url: 'url_local', 41 | path: 'path_local' 42 | }, 43 | production: { 44 | title: 'staging', 45 | database: 'db_staging', 46 | user: 'user_staging', 47 | pass: 'pass_staging', 48 | host: 'host_staging', 49 | url: 'url_staging', 50 | path: 'path_staging', 51 | ssh_host: 'ssh_staging' 52 | } 53 | }, 54 | 55 | nodeunit: { 56 | tasks: ['test/*_test.js'] 57 | }, 58 | }); 59 | 60 | grunt.loadTasks('tasks'); 61 | 62 | grunt.loadNpmTasks('grunt-contrib-jshint'); 63 | grunt.loadNpmTasks('grunt-contrib-clean'); 64 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 65 | 66 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this 67 | // plugin's task(s), then test the result. 68 | grunt.registerTask('test', ['clean', 'nodeunit']); 69 | 70 | // By default, lint and run all tests. 71 | grunt.registerTask('default', ['jshint', 'test']); 72 | }; 73 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 David Smith 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grunt Wordpress Deployments 2 | 3 | The project is searching for a new maintainer. 4 | If anybody is interested please let us know (@ingo86 or @davidthou). 5 | Thanks. 6 | 7 | # Instructions 8 | 9 | Deploy a Wordpress instance without pain using Grunt. 10 | 11 | This plugin leverages on Grunt to push and pull a Wordpress instance into the predefined locations. 12 | Here's a tour of the features: 13 | 14 | * Multiple environments support: you can define different environments such as `development`, `staging`, `production` and so on, with different access credentials, paths and domains. 15 | * Adapt the Wordpress database to the destination domain: It replaces all the instances of the source environment domain with the destination environment domain, even into serialized data. 16 | * Push and pull files with rsync. 17 | * Completely based on Javascript, leverages only on some common system tools to perform the tasks (`mysql`, `mysqldump`, `ssh`). 18 | 19 | ## Requirements 20 | 21 | This plugin requires: 22 | 23 | * Grunt `~0.4.1` 24 | * `ssh` 25 | * `rsync` 26 | * `mysqldump` 27 | 28 | To be able to use this plugin it's important to have access to the remote machine through `ssh`, with ssh key authentication to avoid password entering during the tasks. As this is a different topic we will not cover it here but you might like to start by reading [Github's own advice](https://help.github.com/articles/generating-ssh-keys). 29 | 30 | ## Getting started 31 | 32 | This is a [Grunt](http://gruntjs.com/) plugin, so it requires Grunt. It's really easy to install, as explained into the [Getting Started](http://gruntjs.com/getting-started) guide. Please read the guide to understand how does this works. 33 | 34 | When Grunt is installed on your machine, you can install this plugin with the following command: 35 | 36 | ```shell 37 | npm install grunt-wordpress-deploy --save-dev 38 | ``` 39 | 40 | Once the plugin has been installed, it may be enabled and configured into your Gruntfile.js. Please follow the example Gruntfile to configure your environments. 41 | 42 | ```js 43 | module.exports = function(grunt) { 44 | "use strict"; 45 | 46 | grunt.initConfig({ 47 | wordpressdeploy: { 48 | options: { 49 | backup_dir: "backups/", 50 | rsync_args: ['--verbose', '--progress', '-rlpt', '--compress', '--omit-dir-times', '--delete'], 51 | exclusions: ['Gruntfile.js', '.git/', 'tmp/*', 'backups/', 'wp-config.php', 'composer.json', 'composer.lock', 'README.md', '.gitignore', 'package.json', 'node_modules'] 52 | }, 53 | local: { 54 | "title": "local", 55 | "database": "database_name", 56 | "user": "database_username", 57 | "pass": "database_password", 58 | "host": "database_host", 59 | "url": "http://local_url", 60 | "path": "/local_path" 61 | }, 62 | staging: { 63 | "title": "staging", 64 | "database": "database_name", 65 | "user": "database_username", 66 | "pass": "database_password", 67 | "host": "database_host", 68 | "url": "http://staging_url", 69 | "path": "/staging_path", 70 | "ssh_host": "user@staging_host" 71 | }, 72 | your_environment: { 73 | ... 74 | } 75 | }, 76 | }); 77 | 78 | // Load tasks 79 | grunt.loadNpmTasks('grunt-wordpress-deploy'); 80 | 81 | // Register tasks 82 | grunt.registerTask('default', [ 83 | 'wordpressdeploy' 84 | ]); 85 | }; 86 | ``` 87 | 88 | In the example above we define two environments, one is mandatory and is always called `local`, another is optional and can be called the way you want. In this case we have defined a second environment called `staging`. 89 | 90 | ## Available tasks 91 | 92 | The plugin defines a serie of tasks. Here's a brief overview: 93 | 94 | * `grunt push_db --target=environment_name`: Push the local database to the specified environment. 95 | * `grunt pull_db --target=environment_name`: Pull the database on the specified environment into the local environment. 96 | * `grunt push_files --target=environment_name`: Push the local files to the specified environment, using rsync. 97 | * `grunt pull_files --target=environment_name`: Pull the files from the specified environment to the local environment, using rsync. 98 | 99 | ### Push_db 100 | 101 | Example execution: `grunt push_db --target=staging` 102 | 103 | The `push_db` command moves your local database to a remote database location, specified by the target environment. What happens under the hood is the following: 104 | 105 | - Dump the local database 106 | - Adapt the local dump to the remote environment executing a search and replace to change the instances of the local domain with the instances of the remote domain, taking care of serialized data 107 | - Backups the database on the target environment 108 | - Imports the local adapted dump into the remote database 109 | 110 | 111 | ### Pull_db 112 | 113 | Example execution: `grunt pull_db --target=staging` 114 | 115 | The `pull_db` command moves your target environment database to the local database. What happens under the hood is the following: 116 | 117 | - Dump the remote database 118 | - Adapt the remote dump to the local environment executing a search and replace to change the instances of the remote domain with the instances of the local domain, taking care of serialized data 119 | - Backups the database on the local environment 120 | - Imports the remote adapted dump into the local database 121 | 122 | ### Push_files 123 | 124 | Example execution: `grunt push_files --target=staging` 125 | 126 | The `push_files` command moves your local environment files to the target environment using rsync. 127 | 128 | This operation is not reversible. 129 | 130 | Into `Gruntfile.js` is possible to set which options rsync will use, and which files should be exluded from the synchronization. 131 | More details in the configuration section below. 132 | 133 | ```js 134 | grunt.initConfig({ 135 | wordpressdeploy: { 136 | options: { 137 | backup_dir: "backups/", 138 | rsync_args: ['--verbose', '--progress', '-rlpt', '--compress', '--omit-dir-times', '--delete'], 139 | exclusions: ['Gruntfile.js', '.git/', 'tmp/*', 'backups/', 'wp-config.php', 'composer.json', 'composer.lock', 'README.md', '.gitignore', 'package.json', 'node_modules'] 140 | }, 141 | local: { 142 | ... 143 | ``` 144 | 145 | ### Pull_files 146 | 147 | Example execution: `grunt pull_files --target=staging` 148 | 149 | The `pull_files` command moves your target environment files to the local environment using rsync. 150 | 151 | This operation is not reversible. 152 | 153 | Into `Gruntfile.js` is possible to set which options rsync will use, and which files should be exluded from the synchronization. 154 | 155 | 156 | ### Configuration 157 | 158 | Each target expects a series of configuration options to be provided to enable the task to function correctly. These are detailed below: 159 | 160 | #### title 161 | Type: `String` 162 | 163 | Description: A proper case name for the target. Used to describe the target to humans in console output whilst the task is running. 164 | 165 | #### database 166 | Type: `String` 167 | 168 | Description: the name of the database for this target. 169 | 170 | #### user 171 | Type: `String` 172 | 173 | Description: the database user with permissions to access and modify the database 174 | 175 | #### pass 176 | Type: `String` 177 | 178 | Description: the password for the database user (above) 179 | 180 | #### host 181 | Type: `String` 182 | 183 | Description: the hostname for the location in which the database resides. 184 | 185 | #### url 186 | Type: `String` 187 | 188 | Description: the string to search and replace within the database before it is moved to the target location. This is designed for use with the awful Wordpress implementation which stores [the site url into the database](http://codex.wordpress.org/Changing_The_Site_URL) and is required to be updated upon migration to a new environment. 189 | 190 | #### path 191 | Type: `String` 192 | 193 | Description: the path of the the installation files on the filesystem. Used by rsync to update the correct folder on synchronization. 194 | 195 | #### ssh_host 196 | Type: `String` 197 | 198 | Description: ssh connection string in the format `SSH_USER@SSH_HOST`. The task assumes you have ssh keys setup which allow you to remote into your server without requiring the input of a password. 199 | 200 | ### Options 201 | 202 | #### options.backups_dir 203 | Type: `String` 204 | 205 | Default value: `backups` 206 | 207 | A string value that represents the directory path (*relative* to your Grunt file) to which you want your database backups for source and target to be saved prior to modifications. 208 | 209 | You may wish to have your backups reside outside the current working directory of your Gruntfile. In which case simply provide the relative path eg: ````../../backups````. 210 | 211 | #### options.rsync_args 212 | Type: `Array` 213 | 214 | Default value: `['--verbose', '--progress', '-rlpt', '--compress', '--omit-dir-times', '--delete']` 215 | 216 | An array representing all parameters passed to the rsync command in order to perform the synchronization operation. The defult value in this example is fine for common usages of this plugin. 217 | 218 | 219 | #### options.exclusions 220 | Type: `Array` 221 | 222 | Default value: `['Gruntfile.js', '.git/', 'tmp/*', 'backups/', 'wp-config.php', 'composer.json', 'composer.lock', 'README.md', '.gitignore', 'package.json', 'node_modules']` 223 | 224 | An array representing all excluded files and directories from the synchronization process. 225 | 226 | ## History 227 | 228 | This plugin is an almost complete rewrite of the [Grunt-Deployments Plugin](https://github.com/getdave/grunt-deployments). 229 | Credits to the original developer for the work on the original plugin. 230 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-wordpress-deploy", 3 | "description": "Deploy Wordpress without pain using Grunt.", 4 | "version": "0.0.6", 5 | "homepage": "https://github.com/webrain/grunt-wordpress-deploy", 6 | "author": { 7 | "name": "Dario Ghilardi", 8 | "email": "darioghilardi@webrain.it", 9 | "url": "http://www.webrain.it" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:webrain/grunt-wordpress-deploy.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/webrain/grunt-wordpress-deploy/issues" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/webrain/grunt-wordpress-deploy/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "main": "Gruntfile.js", 25 | "engines": { 26 | "node": ">= 0.8.0" 27 | }, 28 | "scripts": { 29 | "test": "grunt test" 30 | }, 31 | "dependencies": { 32 | "shelljs": "~0.1.4", 33 | "line-reader": "~0.2.3" 34 | }, 35 | "devDependencies": { 36 | "grunt-contrib-jshint": "~0.1.1", 37 | "grunt-contrib-clean": "~0.4.0", 38 | "grunt-contrib-nodeunit": "~0.2.2", 39 | "grunt-shell": "~0.6.0", 40 | "grunt": "~0.4.1" 41 | }, 42 | "peerDependencies": { 43 | "grunt": "~0.4.1" 44 | }, 45 | "keywords": [ 46 | "gruntplugin", 47 | "deploy", 48 | "wordpress" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /tasks/lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.init = function (grunt) { 4 | var shell = require('shelljs'); 5 | var lineReader = require("line-reader"); 6 | 7 | var exports = {}; 8 | 9 | exports.db_dump = function(config, output_paths) { 10 | grunt.file.mkdir(output_paths.dir); 11 | 12 | var cmd = exports.mysqldump_cmd(config); 13 | 14 | var output = shell.exec(cmd, {silent: true}).output; 15 | 16 | grunt.file.write(output_paths.file, output); 17 | grunt.log.oklns("Database DUMP succesfully exported to: " + output_paths.file); 18 | }; 19 | 20 | exports.db_import = function(config, src) { 21 | shell.exec(exports.mysql_cmd(config, src)); 22 | grunt.log.oklns("Database imported succesfully"); 23 | }; 24 | 25 | exports.rsync_push = function(config) { 26 | grunt.log.oklns("Syncing data from '" + config.from + "' to '" + config.to + "' with rsync."); 27 | 28 | var cmd = exports.rsync_push_cmd(config); 29 | grunt.log.writeln(cmd); 30 | 31 | shell.exec(cmd); 32 | 33 | grunt.log.oklns("Sync completed successfully."); 34 | }; 35 | 36 | exports.rsync_pull = function(config) { 37 | grunt.log.oklns("Syncing data from '" + config.from + "' to '" + config.to + "' with rsync."); 38 | 39 | var cmd = exports.rsync_pull_cmd(config); 40 | shell.exec(cmd); 41 | 42 | grunt.log.oklns("Sync completed successfully."); 43 | }; 44 | 45 | exports.generate_backup_paths = function(target, task_options) { 46 | 47 | var backups_dir = task_options['backups_dir'] || "backups"; 48 | 49 | var directory = grunt.template.process(tpls.backup_path, { 50 | data: { 51 | backups_dir: backups_dir, 52 | env: target, 53 | date: grunt.template.today('yyyymmdd'), 54 | time: grunt.template.today('HH-MM-ss'), 55 | } 56 | }); 57 | 58 | var filepath = directory + '/db_backup.sql'; 59 | 60 | return { 61 | dir: directory, 62 | file: filepath 63 | }; 64 | }; 65 | 66 | exports.compose_rsync_options = function(options) { 67 | var args = options.join(' '); 68 | 69 | return args; 70 | }; 71 | 72 | exports.compose_rsync_exclusions = function(options) { 73 | var exclusions = ''; 74 | var i = 0; 75 | 76 | for(i = 0;i < options.length; i++) { 77 | exclusions += "--exclude '" + options[i] + "' "; 78 | } 79 | 80 | exclusions = exclusions.trim(); 81 | 82 | return exclusions; 83 | }; 84 | 85 | exports.db_adapt = function(old_url, new_url, file) { 86 | grunt.log.oklns("Adapt the database: set the correct urls for the destination in the database."); 87 | var content = grunt.file.read(file); 88 | 89 | var output = exports.replace_urls(old_url, new_url, content); 90 | 91 | grunt.file.write(file, output); 92 | }; 93 | 94 | exports.replace_urls = function(search, replace, content) { 95 | content = exports.replace_urls_in_serialized(search, replace, content); 96 | content = exports.replace_urls_in_string(search, replace, content); 97 | 98 | return content; 99 | }; 100 | 101 | exports.replace_urls_in_serialized = function(search, replace, string) { 102 | var length_delta = search.length - replace.length; 103 | var search_regexp = new RegExp(search, 'g'); 104 | 105 | // Replace for serialized data 106 | var matches, length, delimiter, old_serialized_data, target_string, new_url, occurences; 107 | var regexp = /s:(\d+):([\\]*['"])(.*?)\2;/g; 108 | 109 | while (matches = regexp.exec(string)) { 110 | old_serialized_data = matches[0]; 111 | target_string = matches[3]; 112 | 113 | // If the string contains the url make the substitution 114 | if (target_string.indexOf(search) !== -1) { 115 | occurences = target_string.match(search_regexp).length; 116 | length = matches[1]; 117 | delimiter = matches[2]; 118 | 119 | // Replace the url 120 | new_url = target_string.replace(search_regexp, replace); 121 | length -= length_delta * occurences; 122 | 123 | // Compose the new serialized data 124 | var new_serialized_data = 's:' + length + ':' + delimiter + new_url + delimiter + ';'; 125 | 126 | // Replace the new serialized data into the dump 127 | string = string.replace(old_serialized_data, new_serialized_data); 128 | } 129 | } 130 | 131 | return string; 132 | }; 133 | 134 | exports.replace_urls_in_string = function (search, replace, string) { 135 | var regexp = new RegExp('(?!' + replace + ')(' + search + ')', 'g'); 136 | return string.replace(regexp, replace); 137 | }; 138 | 139 | /* Commands generators */ 140 | exports.mysqldump_cmd = function(config) { 141 | var cmd = grunt.template.process(tpls.mysqldump, { 142 | data: { 143 | user: config.user, 144 | pass: config.pass, 145 | database: config.database, 146 | host: config.host 147 | } 148 | }); 149 | 150 | if (typeof config.ssh_host === "undefined") { 151 | grunt.log.oklns("Creating DUMP of local database"); 152 | } else { 153 | grunt.log.oklns("Creating DUMP of remote database"); 154 | var tpl_ssh = grunt.template.process(tpls.ssh, { 155 | data: { 156 | host: config.ssh_host 157 | } 158 | }); 159 | cmd = tpl_ssh + " '" + cmd + "'"; 160 | } 161 | 162 | return cmd; 163 | }; 164 | 165 | exports.mysql_cmd = function(config, src) { 166 | var cmd = grunt.template.process(tpls.mysql, { 167 | data: { 168 | host: config.host, 169 | user: config.user, 170 | pass: config.pass, 171 | database: config.database, 172 | path: src 173 | } 174 | }); 175 | 176 | if (typeof config.ssh_host === "undefined") { 177 | grunt.log.oklns("Importing DUMP into local database"); 178 | cmd = cmd + " < " + src; 179 | } else { 180 | var tpl_ssh = grunt.template.process(tpls.ssh, { 181 | data: { 182 | host: config.ssh_host 183 | } 184 | }); 185 | 186 | grunt.log.oklns("Importing DUMP into remote database"); 187 | cmd = tpl_ssh + " '" + cmd + "' < " + src; 188 | } 189 | 190 | return cmd; 191 | }; 192 | 193 | exports.rsync_push_cmd = function(config) { 194 | var cmd = grunt.template.process(tpls.rsync_push, { 195 | data: { 196 | rsync_args: config.rsync_args, 197 | ssh_host: config.ssh_host, 198 | from: config.from, 199 | to: config.to, 200 | exclusions: config.exclusions 201 | } 202 | }); 203 | 204 | return cmd; 205 | }; 206 | 207 | exports.rsync_pull_cmd = function(config) { 208 | var cmd = grunt.template.process(tpls.rsync_pull, { 209 | data: { 210 | rsync_args: config.rsync_args, 211 | ssh_host: config.ssh_host, 212 | from: config.from, 213 | to: config.to, 214 | exclusions: config.exclusions 215 | } 216 | }); 217 | 218 | return cmd; 219 | }; 220 | 221 | var tpls = { 222 | backup_path: "<%= backups_dir %>/<%= env %>/<%= date %>/<%= time %>", 223 | mysqldump: "mysqldump -h <%= host %> -u<%= user %> -p<%= pass %> <%= database %>", 224 | mysql: "mysql -h <%= host %> -u <%= user %> -p<%= pass %> <%= database %>", 225 | rsync_push: "rsync <%= rsync_args %> --delete -e 'ssh <%= ssh_host %>' <%= exclusions %> <%= from %> :<%= to %>", 226 | rsync_pull: "rsync <%= rsync_args %> -e 'ssh <%= ssh_host %>' <%= exclusions %> :<%= from %> <%= to %>", 227 | ssh: "ssh <%= host %>", 228 | }; 229 | 230 | return exports; 231 | }; 232 | -------------------------------------------------------------------------------- /tasks/wordpressdeploy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-wordpress-deploy 3 | * https://github.com/webrain/grunt-wordpress-deploy 4 | * 5 | * Copyright (c) 2013 Webrain 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var grunt = require('grunt'); 12 | var util = require('../tasks/lib/util.js').init(grunt); 13 | 14 | module.exports = function(grunt) { 15 | 16 | /** 17 | * DB PUSH 18 | * pushes local database to remote database 19 | */ 20 | grunt.registerTask('push_db', 'Push to Database', function() { 21 | 22 | var task_options = grunt.config.get('wordpressdeploy')['options']; 23 | 24 | var target = grunt.option('target') || task_options['target']; 25 | 26 | if ( typeof target === "undefined" || typeof grunt.config.get('wordpressdeploy')[target] === "undefined") { 27 | grunt.fail.warn("Invalid target specified. Did you pass the wrong argument? Please check your task configuration.", 6); 28 | } 29 | 30 | // Grab the options 31 | var target_options = grunt.config.get('wordpressdeploy')[target]; 32 | var local_options = grunt.config.get('wordpressdeploy').local; 33 | 34 | // Generate required backup directories and paths 35 | var local_backup_paths = util.generate_backup_paths("local", task_options); 36 | var target_backup_paths = util.generate_backup_paths(target, task_options); 37 | 38 | grunt.log.subhead("Pushing database from 'Local' to '" + target_options.title + "'"); 39 | 40 | // Dump local DB 41 | util.db_dump(local_options, local_backup_paths); 42 | 43 | // Search and Replace database refs 44 | util.db_adapt(local_options.url, target_options.url, local_backup_paths.file); 45 | 46 | // Dump target DB 47 | util.db_dump(target_options, target_backup_paths); 48 | 49 | // Import dump to target DB 50 | util.db_import(target_options, local_backup_paths.file); 51 | 52 | grunt.log.subhead("Operations completed"); 53 | }); 54 | 55 | /** 56 | * DB PULL 57 | * pulls remote database into local database 58 | */ 59 | grunt.registerTask('pull_db', 'Pull from Database', function() { 60 | 61 | var task_options = grunt.config.get('wordpressdeploy')['options']; 62 | var target = grunt.option('target') || task_options['target']; 63 | 64 | if ( typeof target === "undefined" || typeof grunt.config.get('wordpressdeploy')[target] === "undefined") { 65 | grunt.fail.warn("Invalid target provided. I cannot pull a database from nowhere! Please checked your configuration and provide a valid target.", 6); 66 | } 67 | 68 | // Grab the options 69 | var target_options = grunt.config.get('wordpressdeploy')[target]; 70 | var local_options = grunt.config.get('wordpressdeploy').local; 71 | 72 | // Generate required backup directories and paths 73 | var local_backup_paths = util.generate_backup_paths("local", task_options); 74 | var target_backup_paths = util.generate_backup_paths(target, task_options); 75 | 76 | // Start execution 77 | grunt.log.subhead("Pulling database from '" + target_options.title + "' into Local"); 78 | 79 | // Dump Target DB 80 | util.db_dump(target_options, target_backup_paths ); 81 | 82 | util.db_adapt(target_options.url,local_options.url,target_backup_paths.file); 83 | 84 | // Backup Local DB 85 | util.db_dump(local_options, local_backup_paths); 86 | 87 | // Import dump into Local 88 | util.db_import(local_options,target_backup_paths.file); 89 | 90 | grunt.log.subhead("Operations completed"); 91 | }); 92 | 93 | /** 94 | * Push files 95 | * Sync all local files with the remote location 96 | */ 97 | grunt.registerTask("push_files", "Transfer files to a remote host with rsync.", function () { 98 | 99 | var task_options = grunt.config.get('wordpressdeploy')['options']; 100 | var target = grunt.option('target') || task_options['target']; 101 | 102 | if ( typeof target === "undefined" || typeof grunt.config.get('wordpressdeploy')[target] === "undefined") { 103 | grunt.fail.warn("Invalid target provided. I cannot push files from nowhere! Please checked your configuration and provide a valid target.", 6); 104 | } 105 | 106 | // Grab the options 107 | var target_options = grunt.config.get('wordpressdeploy')[target]; 108 | var local_options = grunt.config.get('wordpressdeploy').local; 109 | var rsync_args = util.compose_rsync_options(task_options.rsync_args); 110 | var exclusions = util.compose_rsync_exclusions(task_options.exclusions); 111 | 112 | var config = { 113 | rsync_args: task_options.rsync_args.join(' '), 114 | ssh_host: target_options.ssh_host, 115 | from: local_options.path, 116 | to: target_options.path, 117 | exclusions: exclusions 118 | }; 119 | 120 | util.rsync_push(config); 121 | }); 122 | 123 | /** 124 | * Pull files 125 | * Sync all target files with the local location 126 | */ 127 | grunt.registerTask("pull_files", "Transfer files to a remote host with rsync.", function () { 128 | 129 | var task_options = grunt.config.get('wordpressdeploy')['options']; 130 | var target = grunt.option('target') || task_options['target']; 131 | 132 | if ( typeof target === "undefined" || typeof grunt.config.get('wordpressdeploy')[target] === "undefined") { 133 | grunt.fail.warn("Invalid target provided. I cannot push files from nowhere! Please checked your configuration and provide a valid target.", 6); 134 | } 135 | 136 | // Grab the options 137 | var target_options = grunt.config.get('wordpressdeploy')[target]; 138 | var local_options = grunt.config.get('wordpressdeploy').local; 139 | var rsync_args = util.compose_rsync_options(task_options.rsync_args); 140 | var exclusions = util.compose_rsync_exclusions(task_options.exclusions); 141 | 142 | var config = { 143 | rsync_args: rsync_args, 144 | ssh_host: target_options.ssh_host, 145 | from: target_options.path, 146 | to: local_options.path, 147 | exclusions: exclusions 148 | }; 149 | 150 | util.rsync_pull(config); 151 | }); 152 | }; 153 | -------------------------------------------------------------------------------- /test/deployments_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var grunt = require('grunt'); 3 | var util = require('../tasks/lib/util.js').init(grunt); 4 | 5 | module.exports = { 6 | replace_urls: function(test) { 7 | test.expect(3); 8 | 9 | var search = 'http://loremipsum'; 10 | var replace = 'http://www.loremipsum.com'; 11 | 12 | var string1 = ''; 13 | test.equal( 14 | util.replace_urls(search, replace, string1), 15 | '', 16 | "Replacing a blank line will return the blank line." 17 | ); 18 | 19 | var string2 = '{s:19:"payment_success_url";s:37:"http://loremipsum/payment-successful/";}http://loremipsum/hb'; 20 | test.equal( 21 | util.replace_urls(search, replace, string2), 22 | '{s:19:"payment_success_url";s:45:"http://www.loremipsum.com/payment-successful/";}http://www.loremipsum.com/hb', 23 | "Replacing a mixed string, serialized or not." 24 | ); 25 | 26 | search = 'http://loremipsum'; 27 | replace = 'http://loremipsum.loremipsum.com'; 28 | 29 | var string3 = '{s:19:"payment_success_url";s:37:"http://loremipsum/payment-successful/";}http://loremipsum.loremipsum.com/hb'; 30 | test.equal( 31 | util.replace_urls(search, replace, string3), 32 | '{s:19:"payment_success_url";s:52:"http://loremipsum.loremipsum.com/payment-successful/";}http://loremipsum.loremipsum.com/hb', 33 | "Replacing a mixed string, serialized or not, with the source url contained into the replace url." 34 | ); 35 | 36 | test.done(); 37 | }, 38 | 39 | replace_urls_in_serialized: function(test) { 40 | test.expect(4); 41 | 42 | var search = 'http://loremipsum'; 43 | var replace = 'http://www.loremipsum.com'; 44 | 45 | var string1 = '{s:19:"payment_success_url";s:37:"http---loremaaaam/payment-successful/";}'; 46 | test.equal( 47 | util.replace_urls_in_serialized(search, replace, string1), 48 | '{s:19:"payment_success_url";s:37:"http---loremaaaam/payment-successful/";}', 49 | "Don't replace as this serialized data has no url inside." 50 | ); 51 | 52 | var string2 = '{s:19:"payment_success_url";s:37:"http://loremipsum/payment-successful/";}'; 53 | test.equal( 54 | util.replace_urls_in_serialized(search, replace, string2), 55 | '{s:19:"payment_success_url";s:45:"http://www.loremipsum.com/payment-successful/";}', 56 | "Replace a single url into serialized data." 57 | ); 58 | 59 | var string3 = '{s:19:"payment_success_url";s:37:"http://loremipsum/payment-successful/";s:16:"payment_fail_url";s:33:"http://loremipsum/payment-failed/";s:13:"currency_unit"}'; 60 | test.equal( 61 | util.replace_urls_in_serialized(search, replace, string3), 62 | '{s:19:"payment_success_url";s:45:"http://www.loremipsum.com/payment-successful/";s:16:"payment_fail_url";s:41:"http://www.loremipsum.com/payment-failed/";s:13:"currency_unit"}', 63 | "Replace multiple urls into serialized data." 64 | ); 65 | 66 | var string4 = '{s:19:"payment_success_url";s:74:"http://loremipsum/payment-successful/ and http://loremipsum/error-message/";s:16:"payment_fail_url";s:33:"http://loremipsum/payment-failed/";s:13:"currency_unit"}'; 67 | test.equal( 68 | util.replace_urls_in_serialized(search, replace, string4), 69 | '{s:19:"payment_success_url";s:90:"http://www.loremipsum.com/payment-successful/ and http://www.loremipsum.com/error-message/";s:16:"payment_fail_url";s:41:"http://www.loremipsum.com/payment-failed/";s:13:"currency_unit"}', 70 | "Replace multiple urls in a single serialized object into serialized data." 71 | ); 72 | 73 | test.done(); 74 | }, 75 | 76 | replace_urls_in_string: function(test) { 77 | test.expect(3); 78 | 79 | var search = 'http://loremipsum'; 80 | var replace = 'http://www.loremipsum.com'; 81 | 82 | var string1 = 'loremiremipsum'; 83 | test.equal( 84 | util.replace_urls_in_string(search, replace, string1), 85 | 'loremiremipsum', 86 | "No url found so no replace." 87 | ); 88 | 89 | var string2 = 'http://loremipsum'; 90 | test.equal( 91 | util.replace_urls_in_string(search, replace, string2), 92 | 'http://www.loremipsum.com', 93 | "Replace an url into a string." 94 | ); 95 | 96 | var string3 = 'dfvdsfsdhttp://loremipsumdbshf jshdbfghahttp://loremipsum/bhs/sdf'; 97 | test.equal( 98 | util.replace_urls_in_string(search, replace, string3), 99 | 'dfvdsfsdhttp://www.loremipsum.comdbshf jshdbfghahttp://www.loremipsum.com/bhs/sdf', 100 | "Replace multiple urls into a complex string." 101 | ); 102 | 103 | test.done(); 104 | }, 105 | 106 | mysqldump_cmd: function(test) { 107 | test.expect(2); 108 | 109 | var config = { 110 | user: 'john', 111 | pass: 'pass', 112 | database: 'test', 113 | host: 'localhost' 114 | }; 115 | 116 | var cmd1 = util.mysqldump_cmd(config); 117 | test.equal(cmd1, "mysqldump -h localhost -ujohn -ppass test", 'Local mysqldump command.'); 118 | 119 | config.ssh_host = '127.0.0.1'; 120 | 121 | var cmd2 = util.mysqldump_cmd(config); 122 | test.equal(cmd2, "ssh 127.0.0.1 'mysqldump -h localhost -ujohn -ppass test'", 'SSH remote mysqldump command.'); 123 | test.done(); 124 | }, 125 | 126 | mysql_cmd: function(test) { 127 | test.expect(2); 128 | 129 | var config = { 130 | host: 'localhost', 131 | user: 'john', 132 | pass: 'pass', 133 | database: 'test', 134 | }; 135 | 136 | var src = '/aaa/bbb'; 137 | 138 | var cmd1 = util.mysql_cmd(config, src); 139 | test.equal(cmd1, "mysql -h localhost -u john -ppass test < /aaa/bbb", 'Local Mysql import command.'); 140 | 141 | config.ssh_host = '127.0.0.1'; 142 | 143 | var cmd2 = util.mysql_cmd(config, src); 144 | test.equal(cmd2, "ssh 127.0.0.1 'mysql -h localhost -u john -ppass test' < /aaa/bbb", 'Remote Mysql import command.'); 145 | test.done(); 146 | }, 147 | 148 | rsync_push_cmd: function(test) { 149 | test.expect(1); 150 | 151 | var config = { 152 | ssh_host: '127.0.0.1', 153 | from: '/htdocs/test', 154 | to: '/var/www/test', 155 | rsync_args: '--verbose --progress', 156 | exclusions: "--exclude '.git/' --exclude 'composer.json'" 157 | }; 158 | 159 | var cmd1 = util.rsync_push_cmd(config); 160 | test.equal(cmd1, "rsync --verbose --progress --delete -e 'ssh 127.0.0.1' --exclude '.git/' --exclude 'composer.json' /htdocs/test :/var/www/test", 'Push files to remote host with rsync.'); 161 | 162 | test.done(); 163 | }, 164 | 165 | rsync_pull_cmd: function(test) { 166 | test.expect(1); 167 | 168 | var config = { 169 | ssh_host: '127.0.0.1', 170 | from: '/var/www/test', 171 | to: '/htdocs/test', 172 | rsync_args: '--verbose --progress', 173 | exclusions: "--exclude '.git/' --exclude 'composer.json'" 174 | }; 175 | 176 | var cmd1 = util.rsync_pull_cmd(config); 177 | test.equal(cmd1, "rsync --verbose --progress -e 'ssh 127.0.0.1' --exclude '.git/' --exclude 'composer.json' :/var/www/test /htdocs/test", 'Pull files from remote host with rsync.'); 178 | 179 | test.done(); 180 | }, 181 | 182 | generate_backup_paths: function(test) { 183 | test.expect(1); 184 | 185 | var target = 'production'; 186 | var task_options = { 187 | backups_dir: 'backups_dir' 188 | }; 189 | var today = grunt.template.today('yyyymmdd'); 190 | var now = grunt.template.today('HH-MM-ss'); 191 | 192 | var actual = util.generate_backup_paths(target, task_options); 193 | 194 | var expected = { 195 | dir: 'backups_dir/production/' + today + '/' + now, 196 | file: 'backups_dir/production/' + today + '/' + now + '/db_backup.sql' 197 | }; 198 | 199 | test.deepEqual(actual, expected, 'Generate backup paths'); 200 | 201 | test.done(); 202 | }, 203 | 204 | compose_rsync_options: function(test) { 205 | test.expect(1); 206 | 207 | var options = ['--verbose', '--progress']; 208 | var string1 = util.compose_rsync_options(options); 209 | 210 | test.equal(string1, '--verbose --progress', "Compose a valid option string from array."); 211 | 212 | test.done(); 213 | }, 214 | 215 | compose_rsync_exclusions: function(test) { 216 | test.expect(1); 217 | 218 | var exclusions = ['.git/', 'composer.json']; 219 | var string1 = util.compose_rsync_exclusions(exclusions); 220 | 221 | test.equal(string1, "--exclude '.git/' --exclude 'composer.json'", "Compose a the exclusions string from array."); 222 | 223 | test.done(); 224 | } 225 | }; 226 | -------------------------------------------------------------------------------- /test/expected/custom_options: -------------------------------------------------------------------------------- 1 | Testing: 1 2 3 !!! -------------------------------------------------------------------------------- /test/expected/default_options: -------------------------------------------------------------------------------- 1 | Testing, 1 2 3. --------------------------------------------------------------------------------