From 32cd6581d11f02cc066ae715f1bf382e45e1725c Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sun, 15 Apr 2018 00:44:02 +0530 Subject: [PATCH] PDF generation - /api/method/pdf - frappe.getPDF - download using blob url --- client/desk/printpage.js | 32 ++------ client/index.js | 3 + client/pdf.js | 30 +++++++ client/view/tree.js | 5 +- common/print.js | 29 +++++++ config/rollup.config.app.js | 4 +- package.json | 3 +- server/index.js | 7 +- server/pdf.js | 34 +++++++- server/utils.js | 5 ++ utils/format.js | 9 ++- yarn.lock | 153 ++++++++++++++++++++++++++++++------ 12 files changed, 258 insertions(+), 56 deletions(-) create mode 100644 client/pdf.js create mode 100644 common/print.js diff --git a/client/desk/printpage.js b/client/desk/printpage.js index 468faa00..820590fe 100644 --- a/client/desk/printpage.js +++ b/client/desk/printpage.js @@ -1,5 +1,6 @@ -const Page = require('frappejs/client/view/page'); const frappe = require('frappejs'); +const Page = require('frappejs/client/view/page'); +const { getHTML } = require('frappejs/common/print'); const nunjucks = require('nunjucks/browser/nunjucks'); nunjucks.configure({ autoescape: false }); @@ -16,12 +17,8 @@ module.exports = class PrintPage extends Page { frappe.router.setRoute('edit', this.doctype, this.name) }); - this.addButton(frappe._('Print'), 'secondary', async () => { - const pdf = require('frappejs/server/pdf'); - const savePath = '/Users/farisansari/frappe.pdf'; - pdf(await this.getHTML(true), savePath); - const { shell } = require('electron'); - shell.openItem(savePath); + this.addButton(frappe._('PDF'), 'secondary', async () => { + frappe.getPDF(this.doctype, this.name); }); } @@ -37,28 +34,15 @@ module.exports = class PrintPage extends Page { } async renderTemplate() { + let doc = await frappe.getDoc(this.doctype, this.name); + frappe.desk.setActiveDoc(doc); + const html = await getHTML(this.doctype, this.name); try { - this.body.innerHTML = await this.getHTML(); + this.body.innerHTML = html; // this.setTitle(doc.name); } catch (e) { this.renderError('Template Error', e); throw e; } } - - async getHTML(pdf = false) { - this.printFormat = await frappe.getDoc('PrintFormat', this.meta.print.printFormat); - let doc = await frappe.getDoc(this.doctype, this.name); - let context = {doc: doc, frappe: frappe}; - frappe.desk.setActiveDoc(doc); - return ` - ${pdf ? ` - - ` : ''} - `; - } } diff --git a/client/index.js b/client/index.js index 40347d3c..4e3ac28d 100644 --- a/client/index.js +++ b/client/index.js @@ -4,6 +4,7 @@ const frappe = require('frappejs'); frappe.ui = require('./ui'); const Desk = require('./desk'); const Observable = require('frappejs/utils/observable'); +const { getPDF } = require('frappejs/client/pdf'); module.exports = { async start({server, columns = 2, makeDesk = 1}) { @@ -47,6 +48,8 @@ module.exports = { return await response.json(); } + + frappe.getPDF = getPDF; } }; diff --git a/client/pdf.js b/client/pdf.js new file mode 100644 index 00000000..f8c029e6 --- /dev/null +++ b/client/pdf.js @@ -0,0 +1,30 @@ +async function getPDF(doctype, name) { + const headers = { + 'Accept': 'application/pdf', + 'Content-Type': 'application/json' + } + + const res = await fetch('/api/method/pdf', { + method: 'POST', + headers, + body: JSON.stringify({ doctype, name }) + }); + + const blob = await res.blob(); + showFile(blob); +} + + +function showFile(blob, filename='file.pdf') { + const newBlob = new Blob([blob], { type: "application/pdf" }) + const data = window.URL.createObjectURL(newBlob); + const link = document.createElement('a'); + link.href = data; + link.download = filename; + link.click(); + setTimeout(() => window.URL.revokeObjectURL(data), 100); +} + +module.exports = { + getPDF +} diff --git a/client/view/tree.js b/client/view/tree.js index 97d117eb..6a523114 100644 --- a/client/view/tree.js +++ b/client/view/tree.js @@ -38,8 +38,9 @@ module.exports = class BaseTree extends BaseList { this.body.innerHTML = ''; this.dirty = false; - let accountingSettings = await frappe.db.getSingle('AccountingSettings'); - let rootLabel = accountingSettings.companyName; + const rootLabel = this.treeSettings.getRootLabel ? + await this.treeSettings.getRootLabel() : + this.doctype; this.renderTree(rootLabel); this.trigger('state-change'); diff --git a/common/print.js b/common/print.js new file mode 100644 index 00000000..c56f32fc --- /dev/null +++ b/common/print.js @@ -0,0 +1,29 @@ +const frappe = require('frappejs'); +const nunjucks = require('nunjucks/browser/nunjucks'); + +async function getHTML(doctype, name) { + const meta = frappe.getMeta(doctype); + const printFormat = await frappe.getDoc('PrintFormat', meta.print.printFormat); + let doc = await frappe.getDoc(doctype, name); + let context = {doc: doc, frappe: frappe}; + + console.log(context); + + let html; + try { + html = nunjucks.renderString(printFormat.template, context); + } catch (error) { + console.log(error); + html = ''; + } + + return ` + + `; +} + +module.exports = { + getHTML +} diff --git a/config/rollup.config.app.js b/config/rollup.config.app.js index 510f7eac..4bb5c22b 100644 --- a/config/rollup.config.app.js +++ b/config/rollup.config.app.js @@ -4,11 +4,13 @@ module.exports = { file: './www/dist/js/bundle.js', format: 'iife', name: 'desk', - globals: ['io', 'nunjucks'] // for socketio client, which is imported directly + sourcemap: true, + globals: ['io', 'nunjucks'], // for socketio client, which is imported directly, }, plugins: [ require('rollup-plugin-commonjs')(), require('rollup-plugin-json')(), + require('rollup-plugin-html')(), require('rollup-plugin-node-resolve')({ preferBuiltins: true }), diff --git a/package.json b/package.json index 4b31fc35..3340ad0f 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "eslint": "^4.19.1", "express": "^4.16.2", "flatpickr": "^4.3.2", - "frappejs": "../frappejs", "frappe-datatable": "frappe/datatable", + "frappejs": "../frappejs", "jquery": "^3.3.1", "mkdirp": "^0.5.1", "mocha": "^4.1.0", @@ -36,6 +36,7 @@ "puppeteer": "^1.2.0", "rollup": "^0.55.1", "rollup-plugin-commonjs": "^8.3.0", + "rollup-plugin-html": "^0.2.1", "rollup-plugin-json": "^2.3.0", "rollup-plugin-node-resolve": "^3.0.2", "rollup-plugin-postcss": "^1.2.7", diff --git a/server/index.js b/server/index.js index ed42f4ef..c734d9e5 100644 --- a/server/index.js +++ b/server/index.js @@ -12,6 +12,7 @@ const frappeModels = require('frappejs/models'); const common = require('frappejs/common'); const bodyParser = require('body-parser'); const fs = require('fs'); +const { setupExpressRoute: setRouteForPDF } = require('frappejs/server/pdf'); require.extensions['.html'] = function (module, filename) { module.exports = fs.readFileSync(filename, 'utf8'); @@ -19,6 +20,7 @@ require.extensions['.html'] = function (module, filename) { module.exports = { async start({backend, connectionParams, models, staticPath = './'}) { + await this.init(); if (models) { @@ -41,10 +43,12 @@ module.exports = { restAPI.setup(app); // listen + server.listen(frappe.config.port); + frappe.app = app; frappe.server = server; - server.listen(frappe.config.port); + setRouteForPDF(); }, async init() { @@ -52,6 +56,7 @@ module.exports = { await frappe.init(); frappe.registerModels(frappeModels, 'server'); frappe.registerLibs(common); + await frappe.login(); }, diff --git a/server/pdf.js b/server/pdf.js index 3d1f39c4..651b430d 100644 --- a/server/pdf.js +++ b/server/pdf.js @@ -1,6 +1,12 @@ +const frappe = require('frappejs'); const puppeteer = require('puppeteer'); +const fs = require('fs'); +const path = require('path'); +const { getTmpDir } = require('frappejs/server/utils'); +const { getHTML } = require('frappejs/common/print'); +const { getRandomString } = require('frappejs/utils'); -module.exports = async function (html, filepath) { +async function makePDF(html, filepath) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.setContent(html); @@ -10,3 +16,29 @@ module.exports = async function (html, filepath) { }); await browser.close(); } + +function setupExpressRoute() { + if (!frappe.app) return; + frappe.app.post('/api/method/pdf', frappe.asyncHandler(handlePDFRequest)); +} + +async function handlePDFRequest(req, res) { + const args = req.body; + const { doctype, name } = args; + const html = await getHTML(doctype, name); + + const filepath = path.join(getTmpDir(), `frappe-pdf-${getRandomString()}.pdf`); + await makePDF(html, filepath); + + const file = fs.createReadStream(filepath); + const stat = fs.statSync(filepath); + res.setHeader('Content-Length', stat.size); + res.setHeader('Content-Type', 'application/pdf'); + res.setHeader('Content-Disposition', `attachment; filename=${path.basename(filepath)}`); + file.pipe(res); +} + +module.exports = { + makePDF, + setupExpressRoute +} diff --git a/server/utils.js b/server/utils.js index 02a01901..755d48ab 100644 --- a/server/utils.js +++ b/server/utils.js @@ -1,6 +1,7 @@ const mkdirp = require('mkdirp'); const fs = require('fs'); const getDirName = require('path').dirname; +const os = require('os'); module.exports = { writeFile(fullpath, contents) { @@ -13,5 +14,9 @@ module.exports = { }); }); }); + }, + + getTmpDir() { + return os.tmpdir(); } } \ No newline at end of file diff --git a/utils/format.js b/utils/format.js index 9f91b2ed..c863f849 100644 --- a/utils/format.js +++ b/utils/format.js @@ -16,7 +16,14 @@ module.exports = { value = markdown.makeHtml(value || ''); } else if (field.fieldtype === 'Date') { - value = moment(value).format(frappe.SystemSettings.dateFormat.toUpperCase()); + let dateFormat; + if (!frappe.SystemSettings) { + dateFormat = 'yyyy-mm-dd'; + } else { + dateFormat = frappe.SystemSettings.dateFormat; + } + + value = moment(value).format(dateFormat.toUpperCase()); } else { if (value===null || value===undefined) { diff --git a/yarn.lock b/yarn.lock index 75830045..bece6004 100644 --- a/yarn.lock +++ b/yarn.lock @@ -412,6 +412,13 @@ callsites@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" +camel-case@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camelcase-css@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-1.0.1.tgz#157c4238265f5cf94a1dffde86446552cbf3f705" @@ -511,6 +518,12 @@ clap@^1.0.9: dependencies: chalk "^1.1.3" +clean-css@4.1.x: + version "4.1.11" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" + dependencies: + source-map "0.5.x" + cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" @@ -613,6 +626,10 @@ commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" +commander@2.15.x, commander@~2.15.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + commander@^2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -1016,22 +1033,6 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -eslint-config-airbnb-base@^12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" - dependencies: - eslint-restricted-globals "^0.1.1" - -eslint-config-airbnb@^16.1.0: - version "16.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz#2546bfb02cc9fe92284bf1723ccf2e87bc45ca46" - dependencies: - eslint-config-airbnb-base "^12.1.0" - -eslint-restricted-globals@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" - eslint-scope@^3.7.1: version "3.7.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" @@ -1118,6 +1119,10 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" +estree-walker@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e" + estree-walker@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa" @@ -1369,14 +1374,55 @@ forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" -frappe-datatable@^0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/frappe-datatable/-/frappe-datatable-0.0.3.tgz#55d3fd7bafdf2a7380efab2ae2aaaa956624fca0" +frappe-datatable@frappe/datatable: + version "0.0.4" + resolved "https://codeload.github.com/frappe/datatable/tar.gz/4bb400230087fbf97e8587a34916e14f77fa01cd" dependencies: clusterize.js "^0.18.0" lodash "^4.17.5" sortablejs "^1.7.0" +frappejs@../frappejs: + version "0.0.5" + dependencies: + autoprefixer "^7.2.4" + awesomplete "^1.1.2" + body-parser "^1.18.2" + bootstrap "^4.0.0" + clusterize.js "^0.18.0" + codemirror "^5.35.0" + commander "^2.13.0" + eslint "^4.19.1" + express "^4.16.2" + flatpickr "^4.3.2" + frappe-datatable frappe/datatable + frappejs "../frappejs" + jquery "^3.3.1" + mkdirp "^0.5.1" + mocha "^4.1.0" + moment "^2.20.1" + mysql "^2.15.0" + node-fetch "^1.7.3" + node-sass "^4.7.2" + nodemon "^1.14.7" + nunjucks "^3.1.0" + octicons "^7.2.0" + popper.js "^1.12.9" + precss "^2.0.0" + puppeteer "^1.2.0" + rollup "^0.55.1" + rollup-plugin-commonjs "^8.3.0" + rollup-plugin-json "^2.3.0" + rollup-plugin-node-resolve "^3.0.2" + rollup-plugin-postcss "^1.2.7" + rollup-plugin-replace "^2.0.0" + rollup-plugin-sass "^0.5.3" + showdown "^1.8.6" + socket.io "^2.0.4" + sortablejs "^1.7.0" + sqlite3 "^3.1.13" + walk "^2.3.9" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -1672,7 +1718,7 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -he@1.1.1: +he@1.1.1, he@1.1.x: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -1692,6 +1738,18 @@ html-comment-regex@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" +html-minifier@^3.0.2: + version "3.5.14" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.14.tgz#88653b24b344274e3e3d7052f1541ebea054ac60" + dependencies: + camel-case "3.0.x" + clean-css "4.1.x" + commander "2.15.x" + he "1.1.x" + param-case "2.1.x" + relateurl "0.2.x" + uglify-js "3.3.x" + http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -2191,6 +2249,10 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -2381,6 +2443,12 @@ negotiator@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + dependencies: + lower-case "^1.1.1" + node-fetch@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -2652,6 +2720,12 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" +param-case@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + dependencies: + no-case "^2.2.0" + parse-glob@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" @@ -3444,6 +3518,10 @@ regjsparser@^0.1.4: dependencies: jsesc "~0.5.0" +relateurl@0.2.x: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3601,6 +3679,13 @@ rollup-plugin-commonjs@^8.3.0: resolve "^1.4.0" rollup-pluginutils "^2.0.1" +rollup-plugin-html@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-html/-/rollup-plugin-html-0.2.1.tgz#a1862eca87ae54b677689d0d4133911e8226463d" + dependencies: + html-minifier "^3.0.2" + rollup-pluginutils "^1.5.0" + rollup-plugin-json@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/rollup-plugin-json/-/rollup-plugin-json-2.3.0.tgz#3c07a452c1b5391be28006fbfff3644056ce0add" @@ -3656,6 +3741,13 @@ rollup-plugin-sass@^0.5.3: estree-walker "^0.3.0" micromatch "^2.3.11" +rollup-pluginutils@^1.5.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408" + dependencies: + estree-walker "^0.2.1" + minimatch "^3.0.2" + rollup@^0.55.1: version "0.55.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.55.1.tgz#baf4f23abe3014b29e56dea7d72d9946e56ac7dd" @@ -3846,17 +3938,17 @@ sortablejs@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.7.0.tgz#80a2b2370abd568e1cec8c271131ef30a904fa28" +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + source-map@^0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: amdefine ">=0.0.4" -source-map@^0.5.3, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.6.1: +source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" @@ -4161,6 +4253,13 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" +uglify-js@3.3.x: + version "3.3.21" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.21.tgz#851a34cbb31840ecb881968ed07dd3a61e7264a0" + dependencies: + commander "~2.15.0" + source-map "~0.6.1" + uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -4219,6 +4318,10 @@ update-notifier@^2.3.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + url-parse-lax@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"