deploy.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. 'use strict';
  2. // eslint-disable-next-line camelcase
  3. var child_process = require('child_process');
  4. var format = require('util').format;
  5. var path = require('path');
  6. var series = require('run-series');
  7. var tv4 = require('tv4');
  8. var schema = {
  9. type: 'object',
  10. properties: {
  11. user: { type: 'string', minLength: 1 },
  12. host: { type: ['string', 'array'] },
  13. repo: { type: 'string' },
  14. path: { type: 'string' },
  15. ref: { type: 'string' },
  16. fetch: { type: 'string' },
  17. },
  18. required: ['host', 'repo', 'path', 'ref'],
  19. };
  20. /**
  21. * Spawn a modified version of visionmedia/deploy
  22. * @private
  23. * @param {object} config config to be piped to deploy
  24. * @param {array} args custom deploy command-line arguments
  25. * @param {DeployCallback} cb done callback
  26. */
  27. function spawn(config, args, cb) {
  28. var cmd = format('echo \'%j\' | "%s"', config, require.resolve('./deploy'));
  29. args = args || [];
  30. if (args.length > 0) {
  31. var cmdArgs = args.map(function (arg) {
  32. return format('"%s"', arg);
  33. }).join(' ');
  34. cmd = [cmd, cmdArgs].join(' ');
  35. }
  36. var proc = child_process.spawn('sh', ['-c', cmd], { stdio: 'inherit' });
  37. var error;
  38. proc.on('error', function (err) {
  39. error = err;
  40. });
  41. proc.on('close', function (code) {
  42. if (code === 0) return cb(null, args);
  43. error = error || new Error(format('Deploy failed with exit code: %s', code));
  44. error.code = code;
  45. return cb(error);
  46. });
  47. }
  48. function clone(obj) {
  49. return JSON.parse(JSON.stringify(obj));
  50. }
  51. function castArray(arg) {
  52. return Array.isArray(arg) ? arg : [arg];
  53. }
  54. /**
  55. * Deploy to a single environment
  56. * @param {object} deployConfig object containing deploy configs for all environments
  57. * @param {string} env the name of the environment to deploy to
  58. * @param {array} args custom deploy command-line arguments
  59. * @param {DeployCallback} cb done callback
  60. * @returns {boolean} return value is always `false`
  61. */
  62. function deployForEnv(deployConfig, env, args, cb) {
  63. if (!deployConfig[env]) {
  64. return cb(new Error(format('%s not defined in deploy section', env)));
  65. }
  66. var envConfig = clone(deployConfig[env]);
  67. if (envConfig.ssh_options) {
  68. envConfig.ssh_options = castArray(envConfig.ssh_options).map(function (option) {
  69. return format('-o %s', option);
  70. }).join(' ');
  71. }
  72. var result = tv4.validateResult(envConfig, schema);
  73. if (!result.valid) {
  74. return cb(result.error);
  75. }
  76. if (process.env.NODE_ENV !== 'test') {
  77. console.log('--> Deploying to %s environment', env);
  78. }
  79. if (process.platform !== 'win32') {
  80. envConfig.path = path.resolve(envConfig.path);
  81. }
  82. var hosts = castArray(envConfig.host);
  83. var jobs = hosts.map(function (host) {
  84. return function job(done) {
  85. if (process.env.NODE_ENV !== 'test') {
  86. console.log('--> on host %s', host.host ? host.host : host);
  87. }
  88. var config = clone(envConfig);
  89. config.host = host;
  90. config['post-deploy'] = prependEnv(config['post-deploy'], config.env);
  91. spawn(config, args, done);
  92. };
  93. });
  94. series(jobs, function (err, result) {
  95. result = Array.isArray(envConfig.host) ? result : result[0];
  96. cb(err, result);
  97. });
  98. return false;
  99. }
  100. function envToString(env) {
  101. env = env || {};
  102. return Object.keys(env).map(function (name) {
  103. return format('%s=%s', name.toUpperCase(), env[name]);
  104. }).join(' ');
  105. }
  106. /**
  107. * Prepend command with environment variables
  108. * @private
  109. * @param {string} cmd command
  110. * @param {object} env object containing environment variables
  111. * @returns {string} concatenated shell command
  112. */
  113. function prependEnv(cmd, env) {
  114. const envVars = envToString(env);
  115. if (!envVars) return cmd;
  116. if (!cmd) return format('export %s', envVars);
  117. return format('export %s && %s', envVars, cmd);
  118. }
  119. module.exports = {
  120. deployForEnv: deployForEnv,
  121. };
  122. /**
  123. * @callback DeployCallback
  124. * @param {Error} error deployment error
  125. * @param {array} args custom command-line arguments provided to deploy
  126. */