summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/cleanup104
-rw-r--r--docs/url-scheme.md34
-rw-r--r--lib/errors.js6
-rw-r--r--lib/migrations/20200321153000-fix-account-deletion.js61
-rw-r--r--lib/web/note/util.js4
-rw-r--r--locales/ar.json32
-rw-r--r--locales/fr.json38
-rw-r--r--package.json2
-rw-r--r--public/css/slide.css6
-rw-r--r--yarn.lock26
10 files changed, 250 insertions, 63 deletions
diff --git a/bin/cleanup b/bin/cleanup
new file mode 100755
index 00000000..edf72bf2
--- /dev/null
+++ b/bin/cleanup
@@ -0,0 +1,104 @@
+#!/usr/bin/env node
+
+const logger = require('../lib/logger')
+const models = require('../lib/models')
+
+logger.info('Cleaning up notes that should already have been removed. Sorry.')
+
+async function cleanup () {
+ await models.Note.findAll({
+ include: [{
+ model: models.User,
+ as: 'owner',
+ attributes: ['id']
+ }],
+ attributes: ['id', 'ownerId']
+ }).then(async function (notes) {
+ for (let i = 0, noteCount = notes.length; i < noteCount; i++) {
+ const item = notes[i]
+ if (item.ownerId != null && !item.owner) {
+ await models.Note.destroy({
+ where: {
+ id: item.id
+ }
+ })
+ await models.Revision.destroy({
+ where: {
+ noteId: item.id
+ }
+ })
+ await models.Author.destroy({
+ where: {
+ noteId: item.id
+ }
+ })
+ logger.info(`Deleted note ${item.id} from user ${item.ownerId}`)
+ }
+ }
+ })
+ await models.Author.findAll({
+ include: [{
+ model: models.User,
+ as: 'user',
+ attributes: ['id']
+ }],
+ attributes: ['id', 'userId']
+ }).then(async function (authors) {
+ for (let i = 0, authorCount = authors.length; i < authorCount; i++) {
+ const item = authors[i]
+ if (item.userId != null && !item.user) {
+ await models.Author.destroy({
+ where: {
+ id: item.id
+ }
+ })
+ logger.info(`Deleted authorship ${item.id} from user ${item.userId}`)
+ }
+ }
+ })
+
+ await models.Author.findAll({
+ include: [{
+ model: models.Note,
+ as: 'note',
+ attributes: ['id']
+ }],
+ attributes: ['id', 'noteId']
+ }).then(async function (authors) {
+ for (let i = 0, authorCount = authors.length; i < authorCount; i++) {
+ const item = authors[i]
+ if (item.noteId != null && !item.note) {
+ await models.Author.destroy({
+ where: {
+ id: item.id
+ }
+ })
+ logger.info(`Deleted authorship ${item.id} from note ${item.noteId}`)
+ }
+ }
+ })
+
+ await models.Revision.findAll({
+ include: [{
+ model: models.Note,
+ as: 'note',
+ attributes: ['id']
+ }],
+ attributes: ['id', 'noteId']
+ }).then(async function (revisions) {
+ for (let i = 0, revisionCount = revisions.length; i < revisionCount; i++) {
+ const item = revisions[i]
+ if (item.noteId != null && !item.note) {
+ await models.Revision.destroy({
+ where: {
+ id: item.id
+ }
+ })
+ logger.info(`Deleted revision ${item.id} from note ${item.userId}`)
+ }
+ }
+ })
+ process.exit(0)
+}
+
+cleanup()
diff --git a/docs/url-scheme.md b/docs/url-scheme.md
new file mode 100644
index 00000000..88f34855
--- /dev/null
+++ b/docs/url-scheme.md
@@ -0,0 +1,34 @@
+# URL scheme
+
+CodiMD has three different modes for viewing a stored note. Each mode has a slightly different URL for accessing it. This document gives an overview about these URLs.
+We assume that you replace `pad.example.com` with the domain of your instance.
+
+## Default (random)
+
+When you create a new note by clicking the "New note" button, your note is given a long random id and a random short-id. The long id is needed for accessing the editor and the live-update view. The short-id is used for the "published" version of a note that is read-only and does not update in realtime as well as for the presentation mode.
+
+| example URL | prefix | mode | content updates |
+| -------------------------------------- | ------ | ----------------- | --------------- |
+| pad.example.com/Ndmv3oCyREKZMjSGR9uhnQ | _none_ | editor | in realtime |
+| pad.example.com/s/ByXF7k-YI | s/ | read-only version | on reload |
+| pad.example.com/p/ByXF7k-YI | p/ | presentation mode | on reload |
+
+## FreeURL mode
+
+If the setting `CMD_ALLOW_FREEURL` is enabled, users may create notes with a custom alias URL by just visiting the editor version of a custom alias. The published version and the presentation mode may also be accessed with the custom alias.
+
+| example URL | prefix | mode | content updates |
+| --------------------------------- | ------ | ----------------- | --------------- |
+| pad.example.com/my-awesome-note | _none_ | editor | in realtime |
+| pad.example.com/s/my-awesome-note | s/ | read-only version | on reload |
+| pad.example.com/p/my-awesome-note | p/ | presentation mode | on reload |
+
+## Different editor modes
+
+The editor has three different sub-modes. All of these update the content in realtime.
+
+| example URL | icon in the navbar | behaviour |
+| ------------------------------- | -------------------| ----------------------------------------------- |
+| pad.example.com/longnoteid?edit | pencil | Full-screen markdown editor for the content |
+| pad.example.com/longnoteid?view | eye | Full-screen view of the note without the editor |
+| pad.example.com/longnoteid?both | columns | markdown editor and view mode side-by-side |
diff --git a/lib/errors.js b/lib/errors.js
index f86e8aa3..950b4cae 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -7,8 +7,10 @@ module.exports = {
responseError(res, '403', 'Forbidden', 'oh no.')
} else {
if (!req.session) req.session = {}
- req.session.returnTo = req.originalUrl || config.serverUrl + '/'
- req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
+ if (req.originalUrl !== '/403') {
+ req.session.returnTo = config.serverURL + (req.originalUrl || '/')
+ req.flash('error', 'You are not allowed to access this page. Maybe try logging in?')
+ }
res.redirect(config.serverURL + '/')
}
},
diff --git a/lib/migrations/20200321153000-fix-account-deletion.js b/lib/migrations/20200321153000-fix-account-deletion.js
new file mode 100644
index 00000000..e794e993
--- /dev/null
+++ b/lib/migrations/20200321153000-fix-account-deletion.js
@@ -0,0 +1,61 @@
+'use strict'
+const { spawnSync } = require('child_process')
+const path = require('path')
+module.exports = {
+ up: function (queryInterface, Sequelize) {
+ const cleanup = spawnSync('./bin/cleanup', { cwd: path.resolve(__dirname, '../../') })
+ if (cleanup.status !== 0) {
+ throw new Error('Unable to cleanup')
+ }
+ return queryInterface.addConstraint('Notes', ['ownerId'], {
+ type: 'foreign key',
+ name: 'Notes_owner_fkey',
+ references: {
+ table: 'Users',
+ field: 'id'
+ },
+ onDelete: 'cascade'
+ }).then(function () {
+ return queryInterface.addConstraint('Revisions', ['noteId'], {
+ type: 'foreign key',
+ name: 'Revisions_note_fkey',
+ references: {
+ table: 'Notes',
+ field: 'id'
+ },
+ onDelete: 'cascade'
+ })
+ }).then(function () {
+ return queryInterface.addConstraint('Authors', ['noteId'], {
+ type: 'foreign key',
+ name: 'Author_note_fkey',
+ references: {
+ table: 'Notes',
+ field: 'id'
+ },
+ onDelete: 'cascade'
+ })
+ }).then(function () {
+ return queryInterface.addConstraint('Authors', ['userId'], {
+ type: 'foreign key',
+ name: 'Author_user_fkey',
+ references: {
+ table: 'Users',
+ field: 'id'
+ },
+ onDelete: 'cascade'
+ })
+ })
+ },
+
+ down: function (queryInterface, Sequelize) {
+ return queryInterface.removeConstraint('Notes', 'Notes_owner_fkey')
+ .then(function () {
+ return queryInterface.removeConstraint('Revisions', 'Revisions_note_fkey')
+ }).then(function () {
+ return queryInterface.removeConstraint('Authors', 'Author_note_fkey')
+ }).then(function () {
+ return queryInterface.removeConstraint('Authors', 'Author_user_fkey')
+ })
+ }
+}
diff --git a/lib/web/note/util.js b/lib/web/note/util.js
index eadfb1a3..c5affd05 100644
--- a/lib/web/note/util.js
+++ b/lib/web/note/util.js
@@ -19,7 +19,7 @@ exports.findNote = function (req, res, callback, include) {
include: include || null
}).then(function (note) {
if (!note) {
- return exports.newNote(req, res, null)
+ return exports.newNote(req, res, '')
}
if (!exports.checkViewPermission(req, note)) {
return errors.errorForbidden(res)
@@ -102,7 +102,7 @@ exports.getPublishData = function (req, res, note, callback) {
}
function isRevealTheme (theme) {
- if (fs.existsSync(path.join(__dirname, '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) {
+ if (fs.existsSync(path.join(__dirname, '..', '..', '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) {
return theme
}
return undefined
diff --git a/locales/ar.json b/locales/ar.json
index 88186f2d..ed726689 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -1,15 +1,15 @@
{
"Collaborative markdown notes": "ملاحظات ماركداون تعاونية",
- "Realtime collaborative markdown notes on all platforms.": "Collaborate on markdown notes on all platforms in realtime.",
- "Best way to write and share your knowledge in markdown.": "The best platform to write and share markdown.",
+ "Realtime collaborative markdown notes on all platforms.": "تعاون آني في ملاحظات ماركداون على كافة المنصات.",
+ "Best way to write and share your knowledge in markdown.": "أحسن منصة لكتابة ومشاركة ماركداون.",
"Intro": "مُقدِّمة",
"History": "التاريخ",
- "New guest note": "ملاحظة ضيف جديد",
- "Collaborate with URL": "Real time collaboration",
+ "New guest note": "ملاحظة جديدة كضَيف",
+ "Collaborate with URL": "تعاون آني",
"Support charts and MathJax": "دعم المنحنيات البيانية و MathJax",
"Support slide mode": "دعم الشرائح التقديمية",
"Sign In": "لِج",
- "Below is the history from browser": "Below is history from this browser",
+ "Below is the history from browser": "تحته سِجِل هذا المتصفّح",
"Welcome!": "أهلا بك!",
"New note": "ملاحظة جديدة",
"or": "أو",
@@ -25,14 +25,14 @@
"Import history": "استيراد التاريخ",
"Clear history": "مسح التاريخ",
"Refresh history": "حدث التاريخ",
- "No history": "No history",
+ "No history": "ليس هناك سِجِل",
"Import from browser": "استيراد من المتصفح",
"Releases": "إصدارات",
"Are you sure?": "هل أنت واثق؟",
"Do you really want to delete this note?": "هل تريد حقًا حذف هذه الملاحظة؟",
"All users will lose their connection.": "سيفقد جميع المستخدمين اتصالهم.",
"Cancel": "إلغاء",
- "Yes, do it!": "Yes, do it!",
+ "Yes, do it!": "نعم ، قم بذلك!",
"Choose method": "اختر الطريقة",
"Sign in via %s": "لِج عبر %s",
"New": "جديد",
@@ -52,11 +52,11 @@
"Upload Image": "تحميل صورة",
"Menu": "القائمة",
"This page need refresh": "هذه الصفحة بحاجة إلى تحديث",
- "You have an incompatible client version.": "نسخة العميل غير متوافقة ",
+ "You have an incompatible client version.": "نسخة عميلك غير متوافقة.",
"Refresh to update.": "حدث الصفحة للحصول على التحديث",
- "New version available!": "نسخة جديدة متوفرة",
- "See releases notes here": "إطلع على ملاحضة الاصدار هنا ",
- "Refresh to enjoy new features.": "حدث الصفحة لتستمتع بالمميزات الجديدة",
+ "New version available!": "نسخة جديدة متوفرة!",
+ "See releases notes here": "إطلع على ملاحظات الإصدار هنا",
+ "Refresh to enjoy new features.": "حدث الصفحة لتستمتع بالميزات الجديدة.",
"Your user state has changed.": "لقد تغيرت حالة المستخدم الخاصة بك.",
"Refresh to load new user state.": "قم بتحديث الصفحة لتحميل حالة المستخدم الجديدة.",
"Refresh": "تحديث",
@@ -71,7 +71,7 @@
"Cheatsheet": "Cheatsheet",
"Example": "مثال",
"Syntax": "Syntax",
- "Header": "الترويسة",
+ "Header": "الرأسية",
"Unordered List": "قائمة غير مرتبة",
"Ordered List": "قائمة مرتبة",
"Todo List": "Checklist",
@@ -85,17 +85,17 @@
"Image": "صورة",
"Code": "الشفرة",
"Externals": "Externals",
- "This is a alert area.": "This is an alert area.",
+ "This is a alert area.": "هذه منطقة تنبيه.",
"Revert": "تراجع",
"Import from clipboard": "استيراد من الحافظة",
- "Paste your markdown or webpage here...": "Paste your markdown or webpage here…",
+ "Paste your markdown or webpage here...": "يرجى وضع ماركداون أو صفحة الويب الخاصة بك هنا…",
"Clear": "امسح",
"This note is locked": "هذه الملاحظة مقفلة",
"Sorry, only owner can edit this note.": "آسف ، يمكن للمالك فقط تعديل هذه الملاحظة.",
"OK": "حسنا",
"Reach the limit": "Reach the limit",
"Sorry, you've reached the max length this note can be.": "عذرًا ، لقد بلغت الحد الأقصى لطول هذه الملاحظة.",
- "Please reduce the content or divide it to more notes, thank you!": "الرجاء قصر هذه الملاحظة",
+ "Please reduce the content or divide it to more notes, thank you!": "يرجى جعل هذه الملاحظة قصيرة.",
"Import from Gist": "استيراد من Gist",
"Paste your gist url here...": "الصق عنوان url الخاص بك هنا...",
"Import from Snippet": "استرداد من سنيبت",
@@ -105,7 +105,7 @@
"Export to Snippet": "صدر إلى سنيبت",
"Select Visibility Level": "حدد مستوى الرؤية",
"Night Theme": "الوضع الليلي",
- "Follow us on %s and %s.": "تابِعنا على %s و %s.",
+ "Follow us on %s and %s.": "تابِعنا على %s و على %s.",
"Privacy": "الخصوصية",
"Terms of Use": "شروط الاستخدام",
"Do you really want to delete your user account?": "هل تريد حقًا حذف حسابك؟",
diff --git a/locales/fr.json b/locales/fr.json
index 3dc434ae..ebda3c69 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -6,11 +6,11 @@
"History": "Historique",
"New guest note": "Nouvelle note invité",
"Collaborate with URL": "Collaborez avec l'URL",
- "Support charts and MathJax": "Supporte les graphiques et MathJax",
- "Support slide mode": "Supporte le mode présentation",
+ "Support charts and MathJax": "Gère les graphiques et MathJax",
+ "Support slide mode": "Gère le mode présentation",
"Sign In": "Se connecter",
"Below is the history from browser": "Ci-dessous, l'historique du navigateur",
- "Welcome!": "Bienvenue !",
+ "Welcome!": "Bienvenue !",
"New note": "Nouvelle note",
"or": "ou",
"Sign Out": "Se déconnecter",
@@ -23,16 +23,16 @@
"Time": "Date",
"Export history": "Exporter l'historique",
"Import history": "Importer l'historique",
- "Clear history": "Supprimer l'historique",
+ "Clear history": "Effacer l'historique",
"Refresh history": "Actualiser l'historique",
"No history": "Pas d'historique",
"Import from browser": "Importer depuis le navigateur",
"Releases": "Versions",
- "Are you sure?": "Ëtes-vous sûr ?",
- "Do you really want to delete this note?": "Voulez-vous vraiment supprimer cette note ?",
+ "Are you sure?": "Ëtes-vous sûr ?",
+ "Do you really want to delete this note?": "Voulez-vous vraiment supprimer cette note ?",
"All users will lose their connection.": "Tous les utilisateurs perdront leur connexion.",
"Cancel": "Annuler",
- "Yes, do it!": "Oui, je suis sûr !",
+ "Yes, do it!": "Oui, je suis sûr !",
"Choose method": "Choisir la méthode",
"Sign in via %s": "Se connecter depuis %s",
"New": "Nouvelle",
@@ -45,16 +45,16 @@
"Clipboard": "Presse-papier",
"Download": "Télécharger",
"Raw HTML": "HTML brut",
- "Edit": "Éditer",
+ "Edit": "Modifier",
"View": "Voir",
"Both": "Les deux",
"Help": "Aide",
- "Upload Image": "Uploader une image",
+ "Upload Image": "Téléverser une image",
"Menu": "Menu",
"This page need refresh": "Cette page doit être rechargée",
"You have an incompatible client version.": "Vous avez une version client incompatible.",
"Refresh to update.": "Recharger pour mettre à jour.",
- "New version available!": "Nouvelle version disponible !",
+ "New version available!": "Nouvelle version disponible !",
"See releases notes here": "Voir les commentaires de version ici",
"Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalités.",
"Your user state has changed.": "Votre statut utilisateur a changé.",
@@ -63,7 +63,7 @@
"Contacts": "Contacts",
"Report an issue": "Signaler un problème",
"Meet us on %s": "Rencontrez-nous sur %s",
- "Send us email": "Envoyez-nous un mail",
+ "Send us email": "Envoyez-nous un courriel",
"Documents": "Documents",
"Features": "Fonctionnalités",
"YAML Metadata": "Métadonnées YAML",
@@ -71,7 +71,7 @@
"Cheatsheet": "Pense-bête",
"Example": "Exemple",
"Syntax": "Syntaxe",
- "Header": "En-tête",
+ "Header": "Entête",
"Unordered List": "Liste à puce",
"Ordered List": "List numérotée",
"Todo List": "Liste de tâches",
@@ -86,16 +86,16 @@
"Code": "Code",
"Externals": "Externes",
"This is a alert area.": "Ceci est un texte d'alerte.",
- "Revert": "Revenir en arrière",
+ "Revert": "Annuler",
"Import from clipboard": "Importer depuis le presse-papier",
"Paste your markdown or webpage here...": "Collez votre markdown ou votre page web ici...",
- "Clear": "Vider",
+ "Clear": "Effacer",
"This note is locked": "Cette note est verrouillée",
- "Sorry, only owner can edit this note.": "Désolé, seul le propriétaire peut éditer cette note.",
+ "Sorry, only owner can edit this note.": "Désolé, seul le propriétaire peut modifier cette note.",
"OK": "OK",
"Reach the limit": "Atteindre la limite",
"Sorry, you've reached the max length this note can be.": "Désolé, vous avez atteint la longueur maximale que cette note peut avoir.",
- "Please reduce the content or divide it to more notes, thank you!": "Merci de réduire le contenu ou de le diviser en plusieurs notes !",
+ "Please reduce the content or divide it to more notes, thank you!": "Merci de réduire le contenu ou de le diviser en plusieurs notes !",
"Import from Gist": "Importer depuis Gist",
"Paste your gist url here...": "Coller l'URL de votre Gist ici...",
"Import from Snippet": "Importer depuis Snippet",
@@ -108,15 +108,15 @@
"Follow us on %s and %s.": "Suivez-nous sur %s, et %s.",
"Privacy": "Confidentialité",
"Terms of Use": "Conditions d'utilisation",
- "Do you really want to delete your user account?": "Voulez-vous vraiment supprimer votre compte utilisateur ?",
+ "Do you really want to delete your user account?": "Voulez-vous vraiment supprimer votre compte utilisateur ?",
"This will delete your account, all notes that are owned by you and remove all references to your account from other notes.": "Cela supprimera votre compte, toutes les notes dont vous êtes propriétaire et supprimera toute référence à votre compte dans les autres notes.",
"Delete user": "Supprimer l'utilisateur",
"Export user data": "Exporter les données utilisateur",
- "Help us translating on %s": "Aidez nous à traduire sur %s",
+ "Help us translating on %s": "Aidez-nous à traduire sur %s",
"Source Code": "Code source",
"Register": "S'enregistrer",
"Powered by %s": "Propulsé par %s",
- "Help us translating": "Aidez nous à traduire",
+ "Help us translating": "Aidez-nous à traduire",
"Join the community": "Rejoignez la communauté",
"Imprint": "Mentions légales"
} \ No newline at end of file
diff --git a/package.json b/package.json
index 7f78d14f..d5805cc4 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"i18n": "^0.8.3",
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
"ionicons": "~2.0.1",
- "jquery": "^3.4.1",
+ "jquery": "^3.5.0",
"jquery-mousewheel": "^3.1.13",
"jquery-ui": "^1.12.1",
"js-cookie": "^2.1.3",
diff --git a/public/css/slide.css b/public/css/slide.css
index f8f9c717..0ba87cbb 100644
--- a/public/css/slide.css
+++ b/public/css/slide.css
@@ -255,8 +255,10 @@ pre.abc > svg {
margin-bottom: -.25em !important;
}
-.reveal .slides, .reveal .backgrounds, .reveal.overview {
- transform-style: preserve-3d;
+@media only screen {
+ .reveal .slides, .reveal .backgrounds, .reveal.overview {
+ transform-style: preserve-3d;
+ }
}
.slides, #meta {
diff --git a/yarn.lock b/yarn.lock
index 4eed293d..cb59bb77 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5463,10 +5463,10 @@ jquery-ui@^1.12.1:
resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51"
integrity sha1-vLQEXI3QU5wTS8FIjN0+dop6nlE=
-jquery@^3.4.1:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2"
- integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==
+jquery@^3.5.0:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9"
+ integrity sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==
js-beautify@^1.8.8:
version "1.10.3"
@@ -6793,22 +6793,6 @@ node-libs-browser@^2.2.1:
util "^0.11.0"
vm-browserify "^1.0.1"
-node-pre-gyp@*:
- version "0.14.0"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
- integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
- dependencies:
- detect-libc "^1.0.2"
- mkdirp "^0.5.1"
- needle "^2.2.1"
- nopt "^4.0.1"
- npm-packlist "^1.1.6"
- npmlog "^4.0.2"
- rc "^1.2.7"
- rimraf "^2.6.1"
- semver "^5.3.0"
- tar "^4.4.2"
-
node-pre-gyp@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
@@ -9612,7 +9596,7 @@ tar-stream@^1.5.0:
to-buffer "^1.1.1"
xtend "^4.0.0"
-tar@^4, tar@^4.4.2:
+tar@^4:
version "4.4.13"
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==