diff --git a/.gitignore b/.gitignore index 3a1ef38..aa2c979 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ node_modules *.log public/* -storage/logs -storage/city +storage/*/* *.xml apidoc www diff --git a/.vscode/settings.json b/.vscode/settings.json index 7ea8603..ffa3528 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,10 +2,9 @@ "editor.formatOnSave": true, "prettier.configPath": ".prettierrc.cjs", "editor.defaultFormatter": "esbenp.prettier-vscode", - "eslint.packageManager": "yarn", "npm.packageManager": "yarn", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.validate": ["javascript"], "[javascript]": { diff --git a/app/config/config.js b/app/config/config.js index bfb0080..c9b139b 100644 --- a/app/config/config.js +++ b/app/config/config.js @@ -21,10 +21,11 @@ export const db = { }; export const back = process.env.APP_BACKEND_PUBLIC_URL; -export const apiPath = "/api"; +export const apiPath = "api"; export const front = process.env.APP_FRONTEND_PUBLIC_URL; export const port = process.env.INTERNAL_PORT || 80; export const docPath = "doc"; +export const frontWebhookSecret = process.env.APP_FRONTEND_WEBHOOK_SECRET; export const mail = { mailjet_apikey_public: process.env.APP_MAILJET_PUBLIC_KEY, @@ -50,3 +51,4 @@ export const cors_origin = process.env.APP_CORS_ORIGIN // Documents export const userProfilePicMediaPath = join(storagePath, "user"); export const cityPicMediaPath = join(storagePath, "city"); +export const articlePicMediaPath = join(storagePath, "articles"); diff --git a/app/controllers/articleController.js b/app/controllers/articleController.js new file mode 100644 index 0000000..111210d --- /dev/null +++ b/app/controllers/articleController.js @@ -0,0 +1,105 @@ +import { join } from "path"; +import { articlePicMediaPath } from "../config/config"; +import { models } from "../models"; +import { reloadPaths } from "../services/nextRenderService"; + +export async function getAllArticles(req, res, next) { + try { + const articles = await models.Article.findAll(); + return res.status(200).send(articles); + } catch (e) { + next(e); + } +} + +export async function getArticleBySlug(req, res, next) { + try { + const article = await models.Article.findByPk(req.params.article_slug); + + if (!article) { + return res.sendStatus(404); + } + + return res.status(200).send(article); + } catch (e) { + next(e); + } +} + +export async function createArticle(req, res, next) { + try { + let slug = req.body.title.replace(/\b\w+'(?=[A-Za-zÀ-ÖØ-öø-ÿ])/gi, ""); + slug = slug.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + slug = slug.replace(/[^\w\s]/gi, ""); + slug = slug.replace(/\s+/g, "-"); + slug = slug.toLowerCase(); + + const article = await models.Article.create( + { ...req.body, slug }, + { + individualHooks: true, + hooks: true, + }, + ); + await reloadPaths("/blog"); + return res.status(200).send(article); + } catch (e) { + next(e); + } +} + +export async function deleteArticle(req, res, next) { + try { + await models.Article.destroy({ + where: { slug: req.params.article_slug }, + individualHooks: true, + hooks: true, + }); + await reloadPaths("/blog"); + return res.sendStatus(200); + } catch (e) { + next(e); + } +} + +export async function updateArticle(req, res, next) { + try { + let slug = req.body.title.replace(/\b\w+'(?=[A-Za-zÀ-ÖØ-öø-ÿ])/gi, ""); + slug = slug.normalize("NFD").replace(/[\u0300-\u036f]/g, ""); + slug = slug.replace(/[^\w\s]/gi, ""); + slug = slug.replace(/\s+/g, "-"); + slug = slug.toLowerCase(); + + await models.Article.update( + { ...req.body, slug }, + { + where: { slug: req.params.article_slug }, + individualHooks: true, + hooks: true, + }, + ); + await reloadPaths("/blog"); + next(); + } catch (e) { + next(e); + } +} + +export async function accessArticlePicture(req, res, next) { + try { + const article = await models.Article.findByPk(req.params.article_slug, { + fields: [req.params.image_type], + }); + if (!article || !article[req.params.image_type]) return res.sendStatus(404); + + req.file = { + path: join( + articlePicMediaPath, + article.getDataValue(req.params.image_type), + ), + }; + next(); + } catch (error) { + next(error); + } +} diff --git a/app/controllers/userController.js b/app/controllers/userController.js index f2d243a..b64ebd8 100644 --- a/app/controllers/userController.js +++ b/app/controllers/userController.js @@ -132,7 +132,7 @@ export const logout = function (req, res) { res.status(200).send("Cookie successfully deleted!"); }; -// Create endpoint /api/users/:id for GET +// Create endpoint /users/:id for GET export const getUserById = function (req, res) { logger.debug("getUserById :" + req.params.user_id); models.User.findByPk(req.params.user_id, { diff --git a/app/index.js b/app/index.js index 69a38b5..33bd4a1 100644 --- a/app/index.js +++ b/app/index.js @@ -59,10 +59,10 @@ app.use(compression()); app.use( bodyParser.urlencoded({ extended: true, - limit: "5mb", + limit: "10mb", }), ); -app.use(bodyParser.json({ limit: "1mb" })); +app.use(bodyParser.json({ limit: "10mb" })); app.use((req, res, next) => { if (req.query.relations) { req.relations = new Proxy( @@ -90,7 +90,7 @@ app.use((error, req, res, _next) => { res.sendStatus(500); }); -//plug our router from routes.js to /api URI +//plug our router from routes.js to / URI app.use(apiPath, router); if (app.get("env") === "development") { diff --git a/app/models/article.model.js b/app/models/article.model.js new file mode 100644 index 0000000..8f1b13b --- /dev/null +++ b/app/models/article.model.js @@ -0,0 +1,101 @@ +import { join } from "path"; +import sequelize from "sequelize"; +const { DataTypes, Model } = sequelize; +import { + ignoreURL, + modelFileDeleter, + modelFileWriter, +} from "../services/fileService"; +import { apiPath, back, articlePicMediaPath } from "../config/config"; + +export default function (sequelize) { + class Article extends Model {} + + Article.init( + { + slug: { + type: DataTypes.STRING(220), + allowNull: false, + primaryKey: true, + }, + title: { + type: DataTypes.STRING(200), + allowNull: false, + }, + description: { + type: DataTypes.STRING(400), + allowNull: false, + }, + content: { + type: DataTypes.TEXT, + allowNull: false, + }, + readingTime: { + type: DataTypes.INTEGER, + allowNull: false, + }, + image: { + type: DataTypes.STRING(60), + allowNull: false, + get() { + const picture = this.getDataValue("image"); + return picture + ? new URL( + join( + apiPath, + "articles", + String(this.getDataValue("slug")), + "image", + ), + back, + ) + : picture; + }, + }, + thumbnail: { + type: DataTypes.STRING(70), + get() { + const picture = this.getDataValue("thumbnail"); + return picture + ? new URL( + join( + apiPath, + "articles", + String(this.getDataValue("slug")), + "thumbnail", + ), + back, + ) + : picture; + }, + }, + }, + { + sequelize, + }, + ); + + const pictureWriter = modelFileWriter( + "image", + articlePicMediaPath, + "picture_", + (mime) => { + if (!mime.startsWith("image")) + return Promise.reject(new Error("file not accepted")); + }, + { quality: 70, width: 1500 }, + true, + ); + + Article.beforeCreate(ignoreURL("image", pictureWriter)); + + Article.beforeUpdate(ignoreURL("image", pictureWriter)); + Article.beforeUpdate(ignoreURL("thumbnail")); + + Article.beforeDestroy( + modelFileDeleter("image", articlePicMediaPath), + modelFileDeleter("thumbnail", articlePicMediaPath), + ); + + return Article; +} diff --git a/app/routes/articles.js b/app/routes/articles.js new file mode 100644 index 0000000..06f0a33 --- /dev/null +++ b/app/routes/articles.js @@ -0,0 +1,30 @@ +import express from "express"; +import { + getAllArticles, + getArticleBySlug, + updateArticle, + deleteArticle, + createArticle, + accessArticlePicture, +} from "../controllers/articleController"; +import { downloadFile } from "../services/fileService"; + +const articlesRouter = express.Router(); + +articlesRouter.route("/").get(getAllArticles); +articlesRouter.route("/:article_slug").get(getArticleBySlug); + +articlesRouter + .route("/its-a-fucking-route-to-manage-article") + .post(createArticle); + +articlesRouter + .route("/its-a-fucking-route-to-manage-article/:article_slug") + .put(updateArticle, getArticleBySlug) + .delete(deleteArticle); + +articlesRouter + .route("/:article_slug/:image_type") + .get(accessArticlePicture, downloadFile); + +export default articlesRouter; diff --git a/app/routes/index.js b/app/routes/index.js index 0b83a30..b6b21af 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -14,6 +14,7 @@ import { AppError } from "../services/errorService"; import usersRouter from "./users.js"; import citiesRouter from "./cities.js"; import airportsRouter from "./airports"; +import articlesRouter from "./articles"; // ROUTES FOR OUR API // ============================================================================= @@ -22,6 +23,7 @@ const router = express.Router(); // get an instance of the express Router router.use("/users", usersRouter); router.use("/cities", citiesRouter); router.use("/airports", airportsRouter); +router.use("/articles", articlesRouter); /** * @api {get} / Test route diff --git a/app/services/fileService.js b/app/services/fileService.js index 1a43480..0c37526 100644 --- a/app/services/fileService.js +++ b/app/services/fileService.js @@ -13,7 +13,7 @@ export function removeBase64Header(base64) { return base64.replace(/^data:\w+\/[^;]+(?:;filename=\w+\.\w+)?;base64,/, ""); } -export function writeFile(file, path, name) { +export function writeFile(file, path, name, withThumbnail) { const data = removeBase64Header(file); const extension = Mime.getExtension( file.match(/^data:(?\w+\/[^;]+)(?:;filename=\w+\.\w+)?;base64,.*/) @@ -21,6 +21,9 @@ export function writeFile(file, path, name) { ); const filename = name + "." + extension; const pathName = nodePath.join(path, filename); + + const thumbnailName = name + "_thumbnail.jpg"; // + extension; + const pathThumbnail = nodePath.join(path, thumbnailName); return new Promise((resolve, reject) => { logger.debug("path: %o", path); fs.mkdir(path, { recursive: true }, (err) => { @@ -30,7 +33,21 @@ export function writeFile(file, path, name) { logger.error("%O", err); return reject(err); } - return resolve(filename); + if (withThumbnail) { + const buf = Buffer.from(data, "base64"); + sharp(buf) + .resize({ width: 800 }) + .jpeg({ quality: 60, force: true }) + .toFile(pathThumbnail, (err) => { + if (err) { + logger.error("%O", err); + return reject(err); + } + resolve([filename, thumbnailName]); + }); + } else { + resolve([filename]); + } }); } else { return reject(err); @@ -39,24 +56,47 @@ export function writeFile(file, path, name) { }); } -export function writeFileCompressed(file, path, name, compressionOptions) { +export function writeFileCompressed( + file, + path, + name, + compressionOptions, + withThumbnail, +) { const data = removeBase64Header(file); const filename = name + ".jpg"; //+ extension; const pathName = nodePath.join(path, filename); + + const thumbnailName = name + "_thumbnail.jpg"; // + extension; + const pathThumbnail = nodePath.join(path, thumbnailName); + return new Promise((resolve, reject) => { logger.debug("path: %o", path); fs.mkdir(path, { recursive: true }, (err) => { if (!err) { const buf = Buffer.from(data, "base64"); sharp(buf) - .resize({ width: compressionOptions?.width || 1000 }) + .resize({ width: compressionOptions?.width || 2000 }) .jpeg({ quality: compressionOptions?.quality || 60, force: true }) .toFile(pathName, (err) => { if (err) { logger.error("%O", err); return reject(err); } - resolve(filename); + if (withThumbnail) { + sharp(buf) + .resize({ width: 800 }) + .jpeg({ quality: 60, force: true }) + .toFile(pathThumbnail, (err) => { + if (err) { + logger.error("%O", err); + return reject(err); + } + resolve([filename, thumbnailName]); + }); + } else { + resolve([filename]); + } }); } else { return reject(err); @@ -84,7 +124,8 @@ function getPrefixValue(prefix, instance) { * @param {string|function} path * @param {string|function} prefix * @param {function=} validate - * @param {{quality?: number, width?: number}} compressionOptions + * @param {{quality?: number, width?: number}?} compressionOptions + * @param {boolean?} withThumbnail * @returns {function(...[*]=)} */ export function modelFileWriter( @@ -93,6 +134,7 @@ export function modelFileWriter( prefix, validate, compressionOptions, + withThumbnail = false, ) { return (instance, options) => { if (!instance.changed(attributeName)) return; @@ -150,8 +192,10 @@ export function modelFileWriter( typeof path === "function" ? path(instance) : path, name, compressionOptions, - ).then((createdName) => { - instance[attributeName] = createdName; + withThumbnail, + ).then((createdNames) => { + instance[attributeName] = createdNames[0]; + if (withThumbnail) instance.thumbnail = createdNames[1]; return Promise.resolve(); }); } @@ -160,8 +204,10 @@ export function modelFileWriter( file, typeof path === "function" ? path(instance) : path, name, - ).then((createdName) => { - instance[attributeName] = createdName; + withThumbnail, + ).then((createdNames) => { + instance[attributeName] = createdNames[0]; + if (withThumbnail) instance.thumbnail = createdNames[1]; return Promise.resolve(); }); }); @@ -270,8 +316,8 @@ export function ignoreURL(attributeName, callback) { : (instance, options) => { if ( instance.changed(attributeName) && - instance[attributeName] && - instance[attributeName].startsWith("http") + instance.getDataValue(attributeName) && + instance.getDataValue(attributeName).startsWith("http") ) { return ignoreAttribute(attributeName)(instance, options); } diff --git a/app/services/nextRenderService.js b/app/services/nextRenderService.js new file mode 100644 index 0000000..8ddd292 --- /dev/null +++ b/app/services/nextRenderService.js @@ -0,0 +1,16 @@ +import axios from "axios"; +import { front, frontWebhookSecret } from "../config/config"; +import loggerFactory from "../log"; +const logger = loggerFactory(import.meta.url); + +export async function reloadPaths(pathname) { + try { + await axios.post(`${front}/api/reloadPrerendering`, { + pathname, + secret: frontWebhookSecret, + }); + } catch (e) { + logger.error("Failed reload NextJS prerendering"); + } + return; +} diff --git a/yarn.lock b/yarn.lock index 58461a4..37028da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1487,6 +1487,11 @@ axios@^0.21.4: dependencies: follow-redirects "^1.14.0" +b4a@^1.6.4: + version "1.6.6" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" + integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== + babel-eslint@^10.0.1: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" @@ -1504,6 +1509,33 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bare-events@^2.0.0, bare-events@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.2.2.tgz#a98a41841f98b2efe7ecc5c5468814469b018078" + integrity sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ== + +bare-fs@^2.1.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.2.2.tgz#286bf54cc6f15f613bee6bb26f0c61c79fb14f06" + integrity sha512-X9IqgvyB0/VA5OZJyb5ZstoN62AzD7YxVGog13kkfYWYqJYcK0kcqLZ6TrmH5qr4/8//ejVcX4x/a0UvaogXmA== + dependencies: + bare-events "^2.0.0" + bare-os "^2.0.0" + bare-path "^2.0.0" + streamx "^2.13.0" + +bare-os@^2.0.0, bare-os@^2.1.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.2.1.tgz#c94a258c7a408ca6766399e44675136c0964913d" + integrity sha512-OwPyHgBBMkhC29Hl3O4/YfxW9n7mdTr2+SsO29XBWKKJsbgj3mnorDB80r5TiCQgQstgE5ga1qNYrpes6NvX2w== + +bare-path@^2.0.0, bare-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.0.tgz#830f17fd39842813ca77d211ebbabe238a88cb4c" + integrity sha512-DIIg7ts8bdRKwJRJrUMy/PICEaQZaPGZ26lsSx9MJSwIhSrcdHn7/C8W+XmnG/rKi6BaRcz+JO00CjZteybDtw== + dependencies: + bare-os "^2.1.0" + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2577,10 +2609,10 @@ detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-libc@^2.0.0, detect-libc@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== +detect-libc@^2.0.0, detect-libc@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== dezalgo@1.0.3, dezalgo@^1.0.0: version "1.0.3" @@ -3353,6 +3385,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-fifo@^1.1.0, fast-fifo@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" @@ -5791,9 +5828,9 @@ nice-try@^1.0.4: integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-abi@^3.3.0: - version "3.33.0" - resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.33.0.tgz#8b23a0cec84e1c5f5411836de6a9b84bccf26e7f" - integrity sha512-7GGVawqyHF4pfd0YFybhv/eM9JwTtPqx0mAanQ146O3FlSh3pA24zf9IRQTOsfTSqXTNzPSP5iagAJ94jjuVog== + version "3.57.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.57.0.tgz#d772cb899236c0aa46778d0d25256917cf15eb15" + integrity sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g== dependencies: semver "^7.3.5" @@ -5802,10 +5839,10 @@ node-addon-api@^3.1.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== -node-addon-api@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.0.0.tgz#cfb3574e6df708ff71a30db6c4762d9e06e11c27" - integrity sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA== +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== node-emoji@^1.10.0: version "1.11.0" @@ -6638,9 +6675,9 @@ please-upgrade-node@^3.2.0: semver-compare "^1.0.0" prebuild-install@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + version "7.1.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.2.tgz#a5fd9986f5a6251fbc47e1e5c65de71e68c0a056" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -6833,6 +6870,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -7338,10 +7380,10 @@ semver@^7.1.1, semver@^7.1.2, semver@^7.1.3, semver@^7.3.5, semver@~7.3.2: dependencies: lru-cache "^6.0.0" -semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== dependencies: lru-cache "^6.0.0" @@ -7430,17 +7472,17 @@ setprototypeof@1.2.0: integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== sharp@^0.32.0: - version "0.32.0" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.0.tgz#146b3e1930d56518699908d9116d8a03be1f5cf6" - integrity sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA== + version "0.32.6" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.6.tgz#6ad30c0b7cd910df65d5f355f774aa4fce45732a" + integrity sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w== dependencies: color "^4.2.3" - detect-libc "^2.0.1" - node-addon-api "^6.0.0" + detect-libc "^2.0.2" + node-addon-api "^6.1.0" prebuild-install "^7.1.1" - semver "^7.3.8" + semver "^7.5.4" simple-get "^4.0.1" - tar-fs "^2.1.1" + tar-fs "^3.0.4" tunnel-agent "^0.6.0" shebang-command@^1.2.0: @@ -7710,6 +7752,16 @@ stream-combiner2@~1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" +streamx@^2.13.0, streamx@^2.15.0: + version "2.16.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.16.1.tgz#2b311bd34832f08aa6bb4d6a80297c9caef89614" + integrity sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ== + dependencies: + fast-fifo "^1.1.0" + queue-tick "^1.0.1" + optionalDependencies: + bare-events "^2.2.0" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -7957,7 +8009,7 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" -tar-fs@^2.0.0, tar-fs@^2.1.1: +tar-fs@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== @@ -7967,6 +8019,17 @@ tar-fs@^2.0.0, tar-fs@^2.1.1: pump "^3.0.0" tar-stream "^2.1.4" +tar-fs@^3.0.4: + version "3.0.5" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.5.tgz#f954d77767e4e6edf973384e1eb95f8f81d64ed9" + integrity sha512-JOgGAmZyMgbqpLwct7ZV8VzkEB6pxXFBVErLtb+XCOqzc6w1xiWKI9GVd6bwk68EX7eJ4DWmfXVmq8K2ziZTGg== + dependencies: + pump "^3.0.0" + tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^2.1.1" + bare-path "^2.1.0" + tar-stream@^2.1.4: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" @@ -7978,6 +8041,15 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^3.1.5: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + tar@^6.0.2, tar@^6.1.11: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"