From 4cbab9c5ceda14b2bcc81683b762da15e32052ad Mon Sep 17 00:00:00 2001 From: Jinksi Date: Sun, 12 Nov 2017 14:36:06 +1000 Subject: [PATCH] Add resized-image support and add example --- functions/resize-images.js | 83 ++++++++++++++++++ package.json | 12 +-- public/admin/config.yml | 1 + src/components/PageHeader.js | 18 ++-- src/util/getImageUrl.js | 43 ++++++++++ src/views/About.js | 6 +- yarn.lock | 162 ++++++++++++++++++++++++++++++++++- 7 files changed, 313 insertions(+), 12 deletions(-) create mode 100644 functions/resize-images.js create mode 100644 src/util/getImageUrl.js diff --git a/functions/resize-images.js b/functions/resize-images.js new file mode 100644 index 0000000..165c956 --- /dev/null +++ b/functions/resize-images.js @@ -0,0 +1,83 @@ +const fs = require('fs') +const path = require('path') +const globCb = require('glob') +const util = require('util') +const sharp = require('sharp') + +const glob = util.promisify(globCb) +const readFile = util.promisify(fs.readFile) + +const options = { + inputDir: './public/images/uploads', + outputDir: './public/images/uploads/resized', + sizes: [300, 600, 1200, 1800], + imageFormats: ['jpg', 'jpeg', 'png', 'gif', 'webp'] +} + +const saveImage = ({ buffer, size, outputFile }) => { + return new Promise((resolve, reject) => { + sharp(buffer) + .resize(size) + .toFile(outputFile, err => { + if (err) { + return reject(err) + } else { + return resolve(console.log(`✅ Saved ${outputFile}`)) + } + }) + }) +} + +const saveImages = ({ buffer, filename }) => { + console.log(`🎞 Processing ${filename}`) + return Promise.all( + options.sizes.map(async size => { + const extname = path.extname(filename) + const newFilename = `${path.basename( + filename, + extname + )}.${size}${extname}` + const outputFile = `${options.outputDir}/${newFilename}` + const fileExists = await doesFileExist({ filename: outputFile }) + if (fileExists) return console.log(`â†Šī¸ ${outputFile} exists, skipping`) + return saveImage({ buffer, size, outputFile }) + }) + ) +} + +const readFiles = files => + Promise.all( + files.map(async filename => { + const buffer = await readFile(filename) + return { filename, buffer } + }) + ) + +const doesFileExist = async ({ filename }) => { + try { + await readFile(filename) + return true + } catch (e) { + return false + } +} + +const resizeImages = async () => { + console.log(`✨ Reading image files in ${options.inputDir}`) + try { + const fileGlob = `${options.inputDir}/**/**.+(${options.imageFormats.join( + '|' + )})` + const files = await glob(fileGlob) + const ignore = new RegExp( + `(${options.sizes.join('|')}).(${options.imageFormats.join('|')})$` + ) + const filesToResize = files.filter(filename => !filename.match(ignore)) + const imageFiles = await readFiles(filesToResize) + imageFiles.map(saveImages) + } catch (e) { + console.log(e) + } +} + +resizeImages() diff --git a/package.json b/package.json index 354d9af..4133595 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "gray-matter": "^3.1.1", "js-yaml": "^3.10.0", "react-scripts": "^1.0.10", + "sharp": "^0.18.4", "snazzy": "^7.0.0", "standard": "^10.0.2", "sw-precache": "^5.2.0" @@ -32,16 +33,17 @@ "styled-components": "^2.1.0" }, "scripts": { - "start": "npm run parse-content && react-scripts start", - "build": "npm run parse-content && react-scripts build && react-snapshot && npm run sw", + "start": + "npm run parse-content && npm run resize-images && react-scripts start", + "build": + "npm run parse-content && npm run resize-images && react-scripts build && react-snapshot && npm run sw", "parse-content": "node ./functions/parse-content.js", + "resize-images": "node ./functions/resize-images.js", "sw": "sw-precache --config='sw-precache-config.js'", "test": "standard | snazzy && react-scripts test --env=jsdom", "eject": "react-scripts eject" }, "reactSnapshot": { - "include": [ - "/404" - ] + "include": ["/404"] } } diff --git a/public/admin/config.yml b/public/admin/config.yml index eb19684..6e464dc 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -40,6 +40,7 @@ collections: # A list of collections the CMS should be able to edit fields: - {label: Title, name: title, widget: string} - {label: Subtitle, name: subtitle, widget: string} + - {label: Featured Image, name: featuredImage, widget: image} - {label: Section 1, name: section1, widget: markdown} - {label: Section 2, name: section2, widget: markdown} - file: "content/pages/contact.json" diff --git a/src/components/PageHeader.js b/src/components/PageHeader.js index 11bef65..30e4211 100644 --- a/src/components/PageHeader.js +++ b/src/components/PageHeader.js @@ -1,8 +1,9 @@ import React from 'react' import PropTypes from 'prop-types' import styled from 'styled-components' -import { Section, Container } from './common' +import { Section, Container, BackgroundImage } from './common' +import { getImageSrc } from '../util/getImageUrl' import { color } from '../globalStyles' const Header = styled(Section)` @@ -10,14 +11,21 @@ const Header = styled(Section)` color: ${color.primary}; background: ${color.primary}; color: white; - h2{ + + h2 { font-weight: 200; } ` -const PageHeader = ({ title, subtitle }) => ( -
- +const PageHeader = ({ title, subtitle, backgroundImage }) => ( +
+ {backgroundImage && ( + + )} +

{title}

{subtitle ?

{subtitle}

: ''}
diff --git a/src/util/getImageUrl.js b/src/util/getImageUrl.js new file mode 100644 index 0000000..8c5919b --- /dev/null +++ b/src/util/getImageUrl.js @@ -0,0 +1,43 @@ +const sizes = [300, 600, 1200, 1800] +const outputDir = '/images/uploads/' +const resizedDir = '/images/uploads/resized/' + +const getImageSrcset = path => { + if (path.indexOf('http') >= 0) { + return console.warn('Cannot get srcset for external image: ' + path) + } + const filename = path.split('.').shift() + const extname = path.split('.').pop() + const pathname = encodeURI(filename.replace(outputDir, resizedDir)) + + const srcset = sizes + .map(size => `${pathname}.${size}.${extname} ${size}w`) + .join(', ') + return srcset +} + +const getImageSrc = (path, sizeRequested) => { + if (path.indexOf('http') >= 0) return path + sizeRequested = parseInt(sizeRequested, 10) + let size + if (sizeRequested) { + // rounds up to nearest size or returns largest + size = + sizes.filter(num => num >= sizeRequested)[0] || sizes[sizes.length - 1] + } else { + // get the middle size + size = sizes[Math.ceil(sizes.length / 2)] + } + + const extname = path.split('.').pop() + const filename = path.split('.').shift() + const pathname = encodeURI(filename.replace(outputDir, resizedDir)) + return `${pathname}.${size}.${extname}` +} + +module.exports = { + getImageSrcset, + getImageSrc, + sizes, + outputDir +} diff --git a/src/views/About.js b/src/views/About.js index ac1406e..af65315 100644 --- a/src/views/About.js +++ b/src/views/About.js @@ -11,7 +11,11 @@ export default ({ page }) => ( {page.title} - +
diff --git a/yarn.lock b/yarn.lock index 39ad1bf..437bf78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1398,6 +1398,15 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +caw@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" + dependencies: + get-proxy "^2.0.0" + isurl "^1.0.0-alpha5" + tunnel-agent "^0.6.0" + url-to-options "^1.0.1" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -1438,6 +1447,10 @@ chokidar@^1.6.0, chokidar@^1.7.0: optionalDependencies: fsevents "^1.0.0" +chownr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" + ci-info@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" @@ -1524,6 +1537,12 @@ color-convert@^1.0.0, color-convert@^1.3.0, color-convert@^1.9.0: dependencies: color-name "^1.1.1" +color-convert@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + color-name@^1.0.0, color-name@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.1.tgz#4b1415304cf50028ea81643643bd82ea05803689" @@ -1534,6 +1553,13 @@ color-string@^0.3.0: dependencies: color-name "^1.0.0" +color-string@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.2.tgz#26e45814bc3c9a7cbd6751648a41434514a773a9" + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color@^0.11.0: version "0.11.4" resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" @@ -1542,6 +1568,13 @@ color@^0.11.0: color-convert "^1.3.0" color-string "^0.3.0" +color@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839" + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + colormin@^1.0.5: version "1.1.2" resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" @@ -1617,6 +1650,13 @@ concat-stream@^1.5.0, concat-stream@^1.5.2, concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +config-chain@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + configstore@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" @@ -1954,6 +1994,12 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + deep-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" @@ -2053,6 +2099,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-libc@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-0.2.0.tgz#47fdf567348a17ec25fcbf0b9e446348a76f9fb5" + detect-node@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" @@ -3137,6 +3187,12 @@ get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" +get-proxy@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" + dependencies: + npm-conf "^1.1.0" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -3340,6 +3396,16 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-symbol-support-x@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz#66ec2e377e0c7d7ccedb07a3a84d77510ff1bc4c" + +has-to-string-tag-x@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + dependencies: + has-symbol-support-x "^1.4.1" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -3703,6 +3769,10 @@ is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" +is-arrayish@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd" + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -3810,6 +3880,10 @@ is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -4015,6 +4089,13 @@ istanbul-reports@^1.1.0: dependencies: handlebars "^4.0.3" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + jest-changed-files@^20.0.3: version "20.0.3" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" @@ -4724,6 +4805,10 @@ mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -4752,6 +4837,18 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minipass@^2.0.2, minipass@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.2.1.tgz#5ada97538b1027b4cf7213432428578cb564011f" + dependencies: + yallist "^3.0.0" + +minizlib@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.0.4.tgz#8ebb51dd8bbe40b0126b5633dbb36b284a2f523c" + dependencies: + minipass "^2.2.1" + mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -4789,6 +4886,10 @@ nan@^2.3.0: version "2.5.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" +nan@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -4916,6 +5017,13 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +npm-conf@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -5000,7 +5108,7 @@ once@^1.3.0: dependencies: wrappy "1" -once@^1.3.3, once@^1.4.0: +once@^1.3.1, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -5677,6 +5785,10 @@ prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + proxy-addr@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" @@ -6451,6 +6563,18 @@ shallowequal@^0.2.2: dependencies: lodash.keys "^3.1.2" +sharp@^0.18.4: + version "0.18.4" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.18.4.tgz#fe329c0f06896c28aa24376df1fff02ae57f2d34" + dependencies: + caw "^2.0.0" + color "^2.0.0" + detect-libc "^0.2.0" + nan "^2.6.2" + semver "^5.3.0" + simple-get "^2.7.0" + tar "^3.1.5" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -6486,6 +6610,24 @@ signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + +simple-get@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.7.0.tgz#ad37f926d08129237ff08c4f2edfd6f10e0380b5" + dependencies: + decompress-response "^3.3.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + dependencies: + is-arrayish "^0.3.1" + slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" @@ -6919,6 +7061,16 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" +tar@^3.1.5: + version "3.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-3.2.1.tgz#9aa8e41c88f09e76c166075bc71f93d5166e61b1" + dependencies: + chownr "^1.0.1" + minipass "^2.0.2" + minizlib "^1.0.3" + mkdirp "^0.5.0" + yallist "^3.0.2" + test-exclude@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.0.tgz#04ca70b7390dd38c98d4a003a173806ca7991c91" @@ -7161,6 +7313,10 @@ url-parse@^1.1.8: querystringify "~1.0.0" requires-port "1.0.x" +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -7490,6 +7646,10 @@ yallist@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4" +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + yargs-parser@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"