From 4259d66b5bd40c9180e9947f2ce1c1cee1a73265 Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Wed, 4 Sep 2019 01:06:16 -0700 Subject: [PATCH] feat(deploy): add detailed checks and logging of configuration If deploying using a folder from the configurations repo, numerous checks and more logging will be performed given information about the configurations --- bin/mastarm-deploy | 226 +++++++++++++++++++++++++++++++-------------- package.json | 3 + yarn.lock | 119 ++++++++++++++++-------- 3 files changed, 241 insertions(+), 107 deletions(-) diff --git a/bin/mastarm-deploy b/bin/mastarm-deploy index 37c987a..24fc3bc 100755 --- a/bin/mastarm-deploy +++ b/bin/mastarm-deploy @@ -3,11 +3,14 @@ const path = require('path') const commander = require('commander') +const execa = require('execa') +const origin = require('git-remote-origin-url') +const repoInfo = require('git-repo-info') const commit = require('this-commit')() const username = require('username') const build = require('../lib/build') -const {readFile} = require('../lib/fs-promise') +const {readFile, writeFile} = require('../lib/fs-promise') const loadConfig = require('../lib/load-config') const logger = require('../lib/logger') const pkg = require('../lib/pkg') @@ -28,86 +31,157 @@ commander .option('--s3bucket', 'S3 Bucket to push to.') .parse(process.argv) -const url = pkg.repository.url.replace('.git', '') -const tag = `<${url}/commit/${commit}|${pkg.name}@${commit.slice(0, 6)}>` -const config = loadConfig(process.cwd(), commander.config, commander.env) -const get = util.makeGetFn([commander, config.settings]) +// each of these variables are also used in the logToMsTeams function and +// these need to be defined after potentially decoding a sops-encoded file +let cloudfront, config, env, minify, s3bucket, tag, url -if (config.env.SLACK_WEBHOOK && config.env.SLACK_WEBHOOK.length > 0) { - logger.logToSlack({ - channel: config.env.SLACK_CHANNEL || '#devops', - webhook: config.env.SLACK_WEBHOOK - }) -} +async function deploy () { + // get information about the config directory being used + const repoUrl = await origin(commander.config) + let configCommit, configRemoteUrl + const configRepoRoot = repoInfo(commander.config).root + const configDir = path + .resolve(commander.config) + .replace(configRepoRoot, '') -const files = util.parseEntries([...commander.args, ...(get('entries') || [])]) -util.assertEntriesExist(files) -const sourceFiles = files.map(f => f[0]) -const outfiles = [...files.map(f => f[1]), ...files.map(f => `${f[1]}.map`)] - -const env = get('env') || 'development' -const minify = get('minify') -const buildOpts = { - config, - env, - files, - minify -} -const cloudfront = get('cloudfront') -const s3bucket = get('s3bucket') + // check if the config directory being used is a configurations repo + if (repoUrl.endsWith('/configurations.git')) { + // run some extra checks to make sure configurations repo is up to date + // modified from https://stackoverflow.com/a/3278427/269834 + configRemoteUrl = repoUrl.replace('.git', '') + const { stdout: local } = await execa('git', ['-C', configRepoRoot, 'rev-parse', '@']) + const { stdout: remote } = await execa('git', ['-C', configRepoRoot, 'rev-parse', '@{u}']) + const { stdout: base } = await execa('git', ['-C', configRepoRoot, 'merge-base', '@', '@{u}']) + let configurationsOk = false + if (local === remote) { + // Up-to-date + console.log('Configurations up-to-date') + configurationsOk = true + } else if (local === base) { + console.error('Configurations out of sync: Need to pull') + } else if (remote === base) { + console.error('Configurations out of sync: Need to push') + } else { + console.error('Configurations out of sync: Diverged') + } + + // make sure there are no changes to the configurations + const { stdout: status } = await execa( + 'git', + [ + '-C', + configRepoRoot, + 'status', + '-s' + ] + ) + if (status === '') { + console.log('No changes to configurations repo') + } else { + console.error('Configurations out of sync: local changes exist that are not yet committed') + configurationsOk = false + } + + if (!configurationsOk) process.exit(1) + + configCommit = local + + // decrypt env file using sops to make sure old file is overwritten with + // data from encoded sops file + const configPath = path.resolve(commander.config) + console.log('decrypting env file with sops') + const {stdout} = await execa( + 'sops', + [ + '-d', + path.join(configPath, 'env.enc.yml') + ] + ) + await writeFile(path.join(configPath, 'env.yml'), stdout) + // at this point, we can be certain that the local configurations repo + // directory matches what has been committed and pushed to the remote repo + } + + url = pkg.repository.url.replace('.git', '') + tag = `<${url}/commit/${commit}|${pkg.name}@${commit.slice(0, 6)}>` + config = loadConfig(process.cwd(), commander.config, commander.env) + const get = util.makeGetFn([commander, config.settings]) + + if (config.env.SLACK_WEBHOOK && config.env.SLACK_WEBHOOK.length > 0) { + logger.logToSlack({ + channel: config.env.SLACK_CHANNEL || '#devops', + webhook: config.env.SLACK_WEBHOOK + }) + } + + const files = util.parseEntries([...commander.args, ...(get('entries') || [])]) + util.assertEntriesExist(files) + const sourceFiles = files.map(f => f[0]) + const outfiles = [...files.map(f => f[1]), ...files.map(f => `${f[1]}.map`)] -const pushToS3 = createPushToS3({ - cloudfront, - s3bucket -}) + env = get('env') || 'development' + minify = get('minify') + const buildOpts = { + config, + env, + files, + minify + } + cloudfront = get('cloudfront') + s3bucket = get('s3bucket') -logger - .log( + const pushToS3 = createPushToS3({ + cloudfront, + s3bucket + }) + + await logger.log( `:construction: *deploying: ${tag} by <@${username.sync()}>* -:vertical_traffic_light: *mastarm:* v${mastarmVersion} -:cloud: *cloudfront:* ${cloudfront} -:hash: *commit:* ${commit} -:seedling: *env:* ${env} -:compression: *minify:* ${minify} -:package: *s3bucket:* ${s3bucket} -:hammer_and_wrench: *building:* ${sourceFiles.join(', ')}` + :vertical_traffic_light: *mastarm:* v${mastarmVersion} + :cloud: *cloudfront:* ${cloudfront} + :hash: *commit:* ${commit} + :seedling: *env:* ${env} + :compression: *minify:* ${minify} + :package: *s3bucket:* ${s3bucket} + :hammer_and_wrench: *building:* ${sourceFiles.join(', ')}` ) - .then(() => - build(buildOpts) - .then(() => - logger.log(`:rocket: *uploading:* ${sourceFiles.length * 2} file(s)`) - ) - .then(() => - Promise.all( - outfiles.map(outfile => - readFile(outfile).then(body => pushToS3({body, outfile})) - ) - ) - ) - .then(() => - logger - .log( - `:tada: :confetti_ball: :tada: *deploy ${tag} complete* :tada: :confetti_ball: :tada:` - ) - .then(() => logToMsTeams()) - .then(() => process.exit(0)) - ) - .catch(err => - logger - .log( - `:rotating_light: *${tag} error deploying ${tag} ${err.message || err}*` - ) - .then(() => logToMsTeams(err)) - .then(() => process.exit(1)) + + try { + await build(buildOpts) + await logger.log(`:rocket: *uploading:* ${sourceFiles.length * 2} file(s)`) + await Promise.all( + outfiles.map(outfile => + readFile(outfile).then(body => pushToS3({body, outfile})) ) - ) + ) + await logger.log( + `:tada: :confetti_ball: :tada: *deploy ${tag} complete* :tada: :confetti_ball: :tada:` + ) + await logToMsTeams({ configCommit, configDir, configRemoteUrl }) + process.exit(0) + } catch (error) { + await logger.log( + `:rotating_light: *${tag} error deploying ${tag} ${error.message || error}*` + ) + await logToMsTeams({ configCommit, configDir, configRemoteUrl, error }) + process.exit(1) + } +} + +deploy() /** * Sends a card to MS Teams with information about the deployment + * @param {[string]} configCommit hash of the commit in the configurations + * repo (if it exists) + * @param {[string]} configDir partial path to specific config directory used + * to deploy + * @param {[string]} configRemoteUrl base url for the configurations repo + * (if it exists) * @param {[Error]} error the error, if one occurred. A falsy value indicates * success */ -function logToMsTeams (error) { +function logToMsTeams ({ configCommit, configDir, configRemoteUrl, error }) { if (!config.env.MS_TEAMS_WEBHOOK) return Promise.resolve() const potentialAction = [{ @@ -120,8 +194,24 @@ function logToMsTeams (error) { } ] }] + if (configCommit && configRemoteUrl) { + potentialAction.push({ + '@type': 'OpenUri', + name: `View Config Commit on Github`, + targets: [ + { + os: 'default', + uri: `${configRemoteUrl}/tree/${configCommit}/${configDir}` + } + ] + }) + } const text = `📄 *commit:* ${pkg.name}@${commit.slice(0, 6)}\n 👤 *deployed by:* ${username.sync()}\n + ${configCommit + ? `🎛️ *config:* configurations@${configCommit.slice(0, 6)}\n + 📂 *config folder:* ${configDir}\n` // improper indenting here needed to properly format on MS Teams + : '🎛️ *config:* unknown configuration data!\n'} 🚦 *mastarm:* v${mastarmVersion}\n ☁️ *cloudfront:* ${cloudfront}\n 🌱 *env:* ${env}\n diff --git a/package.json b/package.json index b5c5389..27931bc 100644 --- a/package.json +++ b/package.json @@ -84,9 +84,12 @@ "eslint-plugin-promise": "^4.0.1", "eslint-plugin-react": "^7.12.4", "eslint-plugin-standard": "^4.0.0", + "execa": "^2.0.4", "exorcist": "^1.0.1", "flow-bin": "0.84.0", "flow-runtime": "^0.17.0", + "git-remote-origin-url": "^3.0.0", + "git-repo-info": "^2.1.0", "glob": "^7.1.3", "isomorphic-fetch": "^2.2.1", "jest": "^24.1.0", diff --git a/yarn.lock b/yarn.lock index 85370a2..be1b9b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3137,7 +3137,7 @@ debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -3660,9 +3660,9 @@ eslint-plugin-import@^2.16.0: resolve "^1.11.0" eslint-plugin-jest@^22.3.0: - version "22.14.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.14.0.tgz#f9b09837f665cfe360b55c08866904255294cc16" - integrity sha512-Xtc9ZTtxdYFC7vu0PHxDeQ9lOMQ8gjwMmSQq/ni83TdflgL3eVh/qg3t99I7gcDxpeXfcp+lHu9C0vN3QAhATw== + version "22.16.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.16.0.tgz#30c4e0e9dc331beb2e7369b70dd1363690c1ce05" + integrity sha512-eBtSCDhO1k7g3sULX/fuRK+upFQ7s548rrBtxDyM1fSoY7dTWp/wICjrJcDZKVsW7tsFfH22SG+ZaxG5BZodIg== dependencies: "@typescript-eslint/experimental-utils" "^1.13.0" @@ -3914,6 +3914,21 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/execa/-/execa-2.0.4.tgz#2f5cc589c81db316628627004ea4e37b93391d8e" + integrity sha512-VcQfhuGD51vQUQtKIq2fjGDLDbL6N1DTQVpYzxZ7LPIXw3HqTuIz6uxRmpV1qf8i31LHf2kjiaGI+GdHwRgbnQ== + dependencies: + cross-spawn "^6.0.5" + get-stream "^5.0.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^3.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4445,6 +4460,25 @@ git-log-parser@^1.2.0: through2 "~2.0.0" traverse "~0.6.6" +git-remote-origin-url@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/git-remote-origin-url/-/git-remote-origin-url-3.0.0.tgz#7561ea24bc16e89b39f96d4302fffb0cd990a1d8" + integrity sha512-KXFRTWj4F39zzmeTo4DGDGiZ9dPDsh+HPZiPRQgW0OpnhcmEPSdf3AgLzLqJuuo8t8UGSKKcvK8nmnejWuq3UQ== + dependencies: + gitconfiglocal "^2.1.0" + +git-repo-info@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/git-repo-info/-/git-repo-info-2.1.0.tgz#13d1f753c75bc2994432e65a71e35377ff563813" + integrity sha512-+kigfDB7j3W80f74BoOUX+lKOmf4pR3/i2Ww6baKTCPe2hD4FRdjhV3s4P5Dy0Tak1uY1891QhKoYNtnyX2VvA== + +gitconfiglocal@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/gitconfiglocal/-/gitconfiglocal-2.1.0.tgz#07c28685c55cc5338b27b5acbcfe34aeb92e43d1" + integrity sha512-qoerOEliJn3z+Zyn1HW2F6eoYJqKwS6MgC9cztTLUB/xLWX8gD/6T60pKn4+t/d6tP7JlybI7Z3z+I572CR/Vg== + dependencies: + ini "^1.3.2" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -4865,7 +4899,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -4918,7 +4952,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== @@ -5327,6 +5361,11 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0, is-stream@~1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-subset@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" @@ -6261,11 +6300,6 @@ lockfile@^1.0.4: dependencies: signal-exit "^3.0.2" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -6274,33 +6308,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= - -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" @@ -6366,11 +6378,6 @@ lodash.merge@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.restparam@*: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" @@ -6676,6 +6683,11 @@ merge-stream@^1.0.1: dependencies: readable-stream "^2.0.1" +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" @@ -6774,7 +6786,7 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0: +mimic-fn@^2.0.0, mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -7233,6 +7245,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npm-run-path@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5" + integrity sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg== + dependencies: + path-key "^3.0.0" + npm-user-validate@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951" @@ -7515,6 +7534,13 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -7632,6 +7658,11 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + p-is-promise@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" @@ -7892,6 +7923,11 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= +path-key@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" + integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -10205,6 +10241,11 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-indent@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"