UNPKG

now-flow

Version:

Add deployment workflows to Zeit now

295 lines (270 loc) 9.53 kB
/** * Copyright (c) 2018, Neap Pty Ltd. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ const path = require('path') require('colors') const { writeToFile, deleteFile } = require('./utilities').files const configFileManager = require('./util/config-files') const gcpDeploy = require('./providers/gcp/deploy') const awsDeploy = require('./providers/aws/deploy') /*eslint-disable */ const getAbsPath = relativePath => path.join(process.cwd(), relativePath) const exit = msg => { if (msg) console.log(msg) process.exit() } /*eslint-enable */ const duplicate = obj => obj ? JSON.parse(JSON.stringify(obj)) : obj const getJsonFiles = (env='default') => { const nowPath = getAbsPath('now.json') const pkgPath = getAbsPath('package.json') let now, pkg try { now = require(nowPath) } catch(err) { console.log(`Error while attempting to load file ${nowPath.bold}`.italic.red) exit(err) } try { pkg = require(pkgPath) } catch(err) { console.log(`Error while attempting to load file ${pkgPath.bold}`.italic.red) exit(err) } const envConfig = (now.environment || {})[env] if (!envConfig) exit(`No environment named '${env.bold}' in ${'now.json'.bold}`.italic.red) return { pkgPath, nowPath, now, pkg } } /** * Updates the package.json file to contain the appropriate config specific * to the targetted environment. This only includes the "scripts" properties. * * @param {String} env Target environment * @return {Object} out * @return {Object} out.err { subject: ..., body: ... } * @return {Boolean} out.restore Indicates whether or not the package.json should be restored (which also implicitely should delete the backup file) * @return {String} out.path package.json location * @return {String} out.backupPath .package.backup.json location * @return {Object} out.current Original package.json (same as .package.backup.json but in memory) * @return {Object} out.new Modified package.json */ const updatePackageJson = (env='default', { pkgPath, pkg, now }) => { const pkgBackupPath = getAbsPath('.package.backup.json') const currentPkgConfig = pkg const newPkgConfig = duplicate(currentPkgConfig) const envConfig = (now.environment || {})[env] if (envConfig.scripts != undefined) { if (!currentPkgConfig.scripts) newPkgConfig.scripts = {} for (let script in envConfig.scripts) newPkgConfig.scripts[script] = envConfig.scripts[script] return createOrUpdateJsonFile(pkgBackupPath, currentPkgConfig) .catch(err => ({ err: { subject: 'Failed to create the temporary \'.package.backup.json\' file.', body: err }, restore: true, path: pkgPath, backupPath: pkgBackupPath, current: currentPkgConfig, new: newPkgConfig })) .then(() => createOrUpdateJsonFile(pkgPath, newPkgConfig)) .catch(err => ({ err: { subject: 'Failed to temporarily override the \'package.json\' file.', body: err }, restore: true, path: pkgPath, backupPath: pkgBackupPath, current: currentPkgConfig, new: newPkgConfig })) .then(() => ({ restore: true, path: pkgPath, backupPath: pkgBackupPath, current: currentPkgConfig, new: newPkgConfig })) } else return Promise.resolve({ restore: false, path: pkgPath, backupPath: pkgBackupPath, current: currentPkgConfig, new: newPkgConfig }) } /** * Updates the now.json file to contain the appropriate config specific * to the targetted environment. This includes: * - The now alias "alias" * - The current enviornment "env"."active" * - Any gcp config "gcp" * * @param {String} env Target environment * @return {Object} out * @return {Object} out.err { subject: ..., body: ... } * @return {Boolean} out.restore Indicates whether or not the now.json should be restored (which also implicitely should delete the backup file) * @return {Boolean} out.alias Indicates whether or not the alias should be created * @return {String} out.path now.json location * @return {String} out.backupPath .now.backup.json location * @return {Object} out.current Original now.json (same as .now.backup.json but in memory) * @return {Object} out.new Modified now.json */ const updateNowJson = (env='default', { nowPath, now }) => { const nowBackupPath = getAbsPath('.now.backup.json') const currentNowConfig = now const newNowConfig = duplicate(currentNowConfig) const currentEnvConfig = (now.environment || {})[env] const aliasMustBeSet = currentEnvConfig.alias != undefined && newNowConfig.alias != currentEnvConfig.alias const activeEnvMustBeSet = !currentEnvConfig.active || currentEnvConfig.active == env const hostingType = !currentEnvConfig.hostingType || currentEnvConfig.hostingType == 'localhost' ? 'localhost' : currentEnvConfig.hostingType const gcpMustBeSet = hostingType == 'gcp' && currentEnvConfig.gcp != undefined const awsMustBeSet = hostingType == 'aws' && currentEnvConfig.aws != undefined if (aliasMustBeSet || activeEnvMustBeSet || gcpMustBeSet) { if (aliasMustBeSet) newNowConfig.alias = currentEnvConfig.alias if (activeEnvMustBeSet) newNowConfig.environment.active = env if (gcpMustBeSet) newNowConfig.gcp = currentEnvConfig.gcp if (awsMustBeSet) newNowConfig.aws = currentEnvConfig.aws return createOrUpdateJsonFile(nowBackupPath, currentNowConfig) .catch(err => ({ err: { subject: 'Failed to create the temporary \'.now.backup.json\' file.', body: err }, restore: true, alias: false, path: nowPath, backupPath: nowBackupPath, current: currentNowConfig, new: newNowConfig, hostingType: hostingType })) .then(() => createOrUpdateJsonFile(nowPath, newNowConfig)) .catch(err => ({ err: { subject: 'Failed to temporarily override the \'now.json\' file.', body: err }, restore: true, alias: false, path: nowPath, backupPath: nowBackupPath, current: currentNowConfig, new: newNowConfig, hostingType: hostingType })) .then(() => ({ restore: true, alias: currentEnvConfig.alias != undefined && !gcpMustBeSet, // (1) path: nowPath, backupPath: nowBackupPath, current: currentNowConfig, new: newNowConfig, hostingType: hostingType })) } else return Promise.resolve({ restore: false, alias: currentEnvConfig.alias != undefined && !gcpMustBeSet, // (1) path: nowPath, backupPath: nowBackupPath, current: currentNowConfig, new: newNowConfig, hostingType: hostingType }) // (1) Aliasing is not supported by GCP. Also, the reason we use currentEnvConfig.alias != undefined rather than // aliasMustBeSet is because aliasMustBeSet=true only specifies that the now.json must be modified. We might // have aliasMustBeSet=false but still need to alias the deployment. } const createOrUpdateJsonFile = (filePath, jsonObj={}) => { const str = JSON.stringify(jsonObj, null, '\t') return writeToFile(filePath, str) } const restoreFiles = (now, pkg) => { return Promise.resolve(null) .then(() => { if (now.restore) return createOrUpdateJsonFile(now.path, now.current).then(() => deleteFile(now.backupPath)) }) .then(() => { if (pkg.restore) return createOrUpdateJsonFile(pkg.path, pkg.current).then(() => deleteFile(pkg.backupPath)) }) .catch(() => { console.log('WARNING - Issue while attempting to restore files now.json and package.json'.yellow) }) } const getConfigFiles = () => ({ config: configFileManager.readConfigFile(), authConfig: configFileManager.readAuthConfigFile(), argv: configFileManager.readLocalConfig() }) const deploy = (env='default', noalias=false) => { const configFiles = getJsonFiles(env) let nowUpdate, pkgUpdate, deployError, aliasError return updateNowJson(env, configFiles).then(out => { nowUpdate = out }) .then(() => updatePackageJson(env, configFiles).then(out => { pkgUpdate = out })) .then(() => { if (!nowUpdate.err && !pkgUpdate.err) return Promise.resolve(null) .then(() => { try { if (nowUpdate.hostingType == 'gcp') return gcpDeploy(getConfigFiles()) else if (nowUpdate.hostingType == 'aws') return awsDeploy(getConfigFiles()) else require('child_process').execSync('now', { stdio: 'inherit' }) } catch(err) { deployError = err } }) .then(() => { try { if (!deployError && !noalias && nowUpdate.alias) { require('child_process').execSync('now alias', { stdio: 'inherit' }) } } catch(err) { aliasError = err } return restoreFiles(nowUpdate, pkgUpdate) .then(() => { if (aliasError) { console.log('Error while aliasing'.red) exit(aliasError) } if (deployError) { console.log('Error while deploying'.red) exit(deployError) } if (pkgUpdate.err) { console.log('Error while updating the package.json pre-deployment'.red) console.log(pkgUpdate.err.subject.red) exit(pkgUpdate.err.body) } if (nowUpdate.err) { console.log('Error while updating the now.json pre-deployment'.red) console.log(nowUpdate.err.subject.red) exit(nowUpdate.err.body) } }) }) }) } module.exports = { deploy }