Merge branch 'hotfix-applyChanges-delivery' of https://gitea.verdnatura.es/verdnatura/salix into hotfix-applyChanges-delivery

This commit is contained in:
Alexandre Riera 2023-04-18 07:19:35 +02:00
commit 18b859ae71
6 changed files with 107 additions and 598 deletions

View File

@ -1,7 +1,6 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const fs = require('fs/promises');
const path = require('path');
const uuid = require('uuid');
module.exports = Self => {
Self.remoteMethodCtx('upload', {
@ -36,7 +35,7 @@ module.exports = Self => {
const fileOptions = {};
const args = ctx.args;
let srcFile;
let tempFilePath;
try {
const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection);
if (!hasWriteRole)
@ -53,15 +52,20 @@ module.exports = Self => {
});
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name);
tempFilePath = path.join(file.client.root, file.container, file.name);
const fileName = `${uuid.v4()}.png`;
await models.Image.registerImage(args.collection, srcFile, fileName, args.id);
} catch (e) {
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
const fileName = `${args.id}.png`;
throw e;
await models.Image.resize({
collectionName: args.collection,
srcFile: tempFilePath,
fileName: fileName,
entityId: args.id
});
} finally {
try {
await fs.unlink(tempFilePath);
} catch (error) { }
}
};
};

View File

@ -1,161 +1,110 @@
const fs = require('fs-extra');
const sharp = require('sharp');
const path = require('path');
const readChunk = require('read-chunk');
const imageType = require('image-type');
const bmp = require('bmp-js');
const gm = require('gm');
module.exports = Self => {
require('../methods/image/download')(Self);
require('../methods/image/upload')(Self);
// Function extracted from jimp package (utils)
function scan(image, x, y, w, h, f) {
// round input
x = Math.round(x);
y = Math.round(y);
w = Math.round(w);
h = Math.round(h);
for (let _y = y; _y < y + h; _y++) {
for (let _x = x; _x < x + w; _x++) {
const idx = (image.bitmap.width * _y + _x) << 2;
f.call(image, _x, _y, idx);
}
}
return image;
}
// Function extracted from jimp package (type-bmp)
function fromAGBR(bitmap) {
return scan({bitmap}, 0, 0, bitmap.width, bitmap.height, function(
x,
y,
index
) {
const alpha = this.bitmap.data[index + 0];
const blue = this.bitmap.data[index + 1];
const green = this.bitmap.data[index + 2];
const red = this.bitmap.data[index + 3];
this.bitmap.data[index + 0] = red;
this.bitmap.data[index + 1] = green;
this.bitmap.data[index + 2] = blue;
this.bitmap.data[index + 3] = bitmap.is_with_alpha ? alpha : 0xff;
}).bitmap;
}
Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
Self.resize = async function({collectionName, srcFile, fileName, entityId}) {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
const myOptions = {transaction: tx};
try {
const collection = await models.ImageCollection.findOne({
const collection = await models.ImageCollection.findOne(
{
fields: [
'id',
'name',
'maxWidth',
'maxHeight',
'model',
'property'
'property',
],
where: {name: collectionName},
include: {
relation: 'sizes',
scope: {
fields: ['width', 'height', 'crop']
}
}
}, myOptions);
fields: ['width', 'height', 'crop'],
},
},
}
);
const data = {
// Insert image row
await models.Image.upsertWithWhere(
{
name: fileName,
collectionFk: collectionName
};
const newImage = await Self.upsertWithWhere(data, {
},
{
name: fileName,
collectionFk: collectionName,
updated: Date.vnNow()
}, myOptions);
// Resizes and saves the image
const container = await models.ImageContainer.container(collectionName);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, fileName);
const buffer = readChunk.sync(srcFilePath, 0, 12);
const type = imageType(buffer);
let sharpOptions;
let imgSrc = srcFilePath;
if (type.mime == 'image/bmp') {
const bmpBuffer = fs.readFileSync(srcFilePath);
const bmpData = fromAGBR(bmp.decode(bmpBuffer));
imgSrc = bmpData.data;
sharpOptions = {
raw: {
width: bmpData.width,
height: bmpData.height,
channels: 4
},
failOn: 'none'
};
updated: Date.vnNow() / 1000,
}
);
const resizeOpts = {
withoutEnlargement: true,
fit: 'inside'
};
// Update entity image file name
const model = models[collection.model];
if (!model) throw new Error('No matching model found');
await fs.mkdir(dstDir, {recursive: true});
await sharp(imgSrc, sharpOptions)
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png()
.toFile(dstFile);
const entity = await model.findById(entityId);
if (entity) {
await entity.updateAttribute(
collection.property,
fileName
);
}
const sizes = collection.sizes();
for (let size of sizes) {
const dstDir = path.join(collectionDir, `${size.width}x${size.height}`);
const dstFile = path.join(dstDir, fileName);
const resizeOpts = {
withoutEnlargement: true,
fit: size.crop ? 'cover' : 'inside'
};
// Resize
const container = await models.ImageContainer.container(
collectionName
);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
await fs.mkdir(dstDir, {recursive: true});
await sharp(imgSrc, sharpOptions)
.resize(size.width, size.height, resizeOpts)
.png()
.toFile(dstFile);
}
// To max size
const {maxWidth, maxHeight} = collection;
const fullSizePath = path.join(collectionDir, 'full');
const toFullSizePath = `${fullSizePath}/${fileName}`;
const model = models[collection.model];
await fs.mkdir(fullSizePath, {recursive: true});
await new Promise((resolve, reject) => {
gm(srcFile)
.resize(maxWidth, maxHeight, '>')
.setFormat('png')
.quality(100)
.write(toFullSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
});
if (!model)
throw new Error('Matching model not found');
// To collection sizes
for (const size of collection.sizes()) {
const {width, height} = size;
const item = await model.findById(entityId, null, myOptions);
if (item) {
await item.updateAttribute(
collection.property,
fileName,
myOptions
);
}
const sizePath = path.join(collectionDir, `${width}x${height}`);
const toSizePath = `${sizePath}/${fileName}`;
if (fs.existsSync(srcFilePath))
await fs.unlink(srcFilePath);
await fs.mkdir(sizePath, {recursive: true});
await new Promise((resolve, reject) => {
const gmInstance = gm(srcFile);
await tx.commit();
if (size.crop) {
gmInstance
.resize(width, height, '^')
.gravity('Center')
.crop(width, height);
}
return newImage;
} catch (e) {
await tx.rollback();
throw e;
if (!size.crop) gmInstance.resize(width, height, '>');
gmInstance
.setFormat('png')
.quality(100)
.write(toSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
});
}
};
};

View File

@ -162,14 +162,8 @@ export default class UploadPhoto extends Component {
if (!this.newPhoto.files)
throw new Error(`Select an image`);
const viewportType = this.viewportSelection;
const output = viewportType.output;
const options = {
type: 'blob',
size: {
width: output.width,
height: output.height
}
};
return this.editor.result(options)
.then(blob => this.newPhoto.blob = blob)

View File

@ -1,9 +1,7 @@
const axios = require('axios');
const uuid = require('uuid');
const fs = require('fs/promises');
const {createWriteStream} = require('fs');
const path = require('path');
const gm = require('gm');
module.exports = Self => {
Self.remoteMethod('download', {
@ -27,13 +25,9 @@ module.exports = Self => {
const maxAttempts = 3;
const collectionName = 'catalog';
const tx = await Self.beginTransaction({});
let tempFilePath;
let queueRow;
try {
const myOptions = {transaction: tx};
queueRow = await Self.findOne(
{
fields: ['id', 'itemFk', 'url', 'attempts'],
@ -44,58 +38,14 @@ module.exports = Self => {
},
},
order: 'priority, attempts, updated',
},
myOptions
}
);
if (!queueRow) return;
const collection = await models.ImageCollection.findOne(
{
fields: [
'id',
'maxWidth',
'maxHeight',
'model',
'property',
],
where: {name: collectionName},
include: {
relation: 'sizes',
scope: {
fields: ['width', 'height', 'crop'],
},
},
},
myOptions
);
const fileName = `${uuid.v4()}.png`;
const fileName = `${queueRow.itemFk}.png`;
tempFilePath = path.join(tempPath, fileName);
// Insert image row
await models.Image.create(
{
name: fileName,
collectionFk: collectionName,
updated: Date.vnNow(),
},
myOptions
);
// Update item
const model = models[collection.model];
if (!model) throw new Error('No matching model found');
const item = await model.findById(queueRow.itemFk, null, myOptions);
if (item) {
await item.updateAttribute(
collection.property,
fileName,
myOptions
);
}
// Download remote image
const response = await axios.get(queueRow.url, {
responseType: 'stream',
@ -108,71 +58,22 @@ module.exports = Self => {
writeStream.on('error', error => reject(error));
});
// Resize
const container = await models.ImageContainer.container(
collectionName
);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
// To max size
const {maxWidth, maxHeight} = collection;
const fullSizePath = path.join(collectionDir, 'full');
const toFullSizePath = `${fullSizePath}/${fileName}`;
await fs.mkdir(fullSizePath, {recursive: true});
await new Promise((resolve, reject) => {
gm(tempFilePath)
.resize(maxWidth, maxHeight, '>')
.setFormat('png')
.write(toFullSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
await models.Image.resize({
collectionName: collectionName,
srcFile: tempFilePath,
fileName: fileName,
entityId: queueRow.itemFk
});
// To collection sizes
for (const size of collection.sizes()) {
const {width, height} = size;
const sizePath = path.join(collectionDir, `${width}x${height}`);
const toSizePath = `${sizePath}/${fileName}`;
await fs.mkdir(sizePath, {recursive: true});
await new Promise((resolve, reject) => {
const gmInstance = gm(tempFilePath);
if (size.crop) {
gmInstance
.resize(width, height, '^')
.gravity('Center')
.crop(width, height);
}
if (!size.crop) gmInstance.resize(width, height, '>');
gmInstance
.setFormat('png')
.write(toSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
});
}
try {
await fs.unlink(tempFilePath);
} catch (error) { }
await queueRow.destroy(myOptions);
await queueRow.destroy();
// Restart queue
Self.download();
await tx.commit();
} catch (error) {
await tx.rollback();
if (queueRow.attempts < maxAttempts) {
await queueRow.updateAttributes({
error: error,

364
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "salix-back",
"version": "23.08.01",
"version": "23.12.01",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "salix-back",
"version": "23.08.01",
"version": "23.12.01",
"license": "GPL-3.0",
"dependencies": {
"axios": "^1.2.2",
@ -41,7 +41,6 @@
"puppeteer": "^18.0.5",
"read-chunk": "^3.2.0",
"require-yaml": "0.0.1",
"sharp": "^0.31.3",
"smbhash": "0.0.1",
"strong-error-handler": "^2.3.2",
"uuid": "^3.3.3",
@ -5444,17 +5443,6 @@
"node": ">=0.10.0"
}
},
"node_modules/color": {
"version": "4.2.3",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"license": "MIT",
@ -5469,14 +5457,6 @@
"version": "1.1.4",
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"license": "ISC",
@ -6149,6 +6129,7 @@
},
"node_modules/deep-extend": {
"version": "0.6.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4.0.0"
@ -7630,13 +7611,6 @@
"node": ">=0.10.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/expand-tilde": {
"version": "2.0.2",
"dev": true,
@ -8891,10 +8865,6 @@
"assert-plus": "^1.0.0"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"license": "MIT"
},
"node_modules/glob": {
"version": "7.2.0",
"license": "ISC",
@ -11516,6 +11486,7 @@
},
"node_modules/ini": {
"version": "1.3.8",
"dev": true,
"license": "ISC"
},
"node_modules/internal-ip": {
@ -11645,10 +11616,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.3.2",
"license": "MIT"
},
"node_modules/is-bigint": {
"version": "1.0.4",
"dev": true,
@ -15610,10 +15577,6 @@
"node": ">=0.10.0"
}
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
"license": "MIT"
},
"node_modules/natural-compare": {
"version": "1.4.0",
"dev": true,
@ -15655,43 +15618,6 @@
"node": ">=4.0.0"
}
},
"node_modules/node-abi": {
"version": "3.28.0",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/lru-cache": {
"version": "6.0.0",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/semver": {
"version": "7.3.8",
"license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/yallist": {
"version": "4.0.0",
"license": "ISC"
},
"node_modules/node-addon-api": {
"version": "5.0.0",
"license": "MIT"
@ -17539,30 +17465,6 @@
"node": ">=0.10.0"
}
},
"node_modules/prebuild-install": {
"version": "7.1.1",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/precond": {
"version": "0.2.3",
"engines": {
@ -18057,6 +17959,7 @@
},
"node_modules/rc": {
"version": "1.2.8",
"dev": true,
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
@ -19786,55 +19689,6 @@
"node": ">=8"
}
},
"node_modules/sharp": {
"version": "0.31.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz",
"integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==",
"hasInstallScript": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.1",
"node-addon-api": "^5.0.0",
"prebuild-install": "^7.1.1",
"semver": "^7.3.8",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
"engines": {
"node": ">=14.15.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/sharp/node_modules/lru-cache": {
"version": "6.0.0",
"license": "ISC",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp/node_modules/semver": {
"version": "7.3.8",
"license": "ISC",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sharp/node_modules/yallist": {
"version": "4.0.0",
"license": "ISC"
},
"node_modules/shebang-command": {
"version": "2.0.0",
"license": "MIT",
@ -19893,77 +19747,6 @@
"version": "3.0.7",
"license": "ISC"
},
"node_modules/simple-concat": {
"version": "1.0.1",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-get/node_modules/decompress-response": {
"version": "6.0.0",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/simple-get/node_modules/mimic-response": {
"version": "3.1.0",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/simple-update-notifier": {
"version": "1.0.7",
"dev": true,
@ -20864,6 +20647,7 @@
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -30018,13 +29802,6 @@
"object-visit": "^1.0.0"
}
},
"color": {
"version": "4.2.3",
"requires": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
}
},
"color-convert": {
"version": "2.0.1",
"requires": {
@ -30034,13 +29811,6 @@
"color-name": {
"version": "1.1.4"
},
"color-string": {
"version": "1.9.1",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"color-support": {
"version": "1.1.3"
},
@ -30487,7 +30257,8 @@
}
},
"deep-extend": {
"version": "0.6.0"
"version": "0.6.0",
"dev": true
},
"deep-is": {
"version": "0.1.4"
@ -31501,9 +31272,6 @@
}
}
},
"expand-template": {
"version": "2.0.3"
},
"expand-tilde": {
"version": "2.0.2",
"dev": true,
@ -32389,9 +32157,6 @@
"assert-plus": "^1.0.0"
}
},
"github-from-package": {
"version": "0.0.0"
},
"glob": {
"version": "7.2.0",
"requires": {
@ -34296,7 +34061,8 @@
"version": "2.0.4"
},
"ini": {
"version": "1.3.8"
"version": "1.3.8",
"dev": true
},
"internal-ip": {
"version": "4.3.0",
@ -34372,9 +34138,6 @@
"has-tostringtag": "^1.0.0"
}
},
"is-arrayish": {
"version": "0.3.2"
},
"is-bigint": {
"version": "1.0.4",
"dev": true,
@ -37151,9 +36914,6 @@
"to-regex": "^3.0.1"
}
},
"napi-build-utils": {
"version": "1.0.2"
},
"natural-compare": {
"version": "1.4.0",
"dev": true
@ -37182,29 +36942,6 @@
"nocache": {
"version": "2.1.0"
},
"node-abi": {
"version": "3.28.0",
"requires": {
"semver": "^7.3.5"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.3.8",
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0"
}
}
},
"node-addon-api": {
"version": "5.0.0"
},
@ -38439,23 +38176,6 @@
"version": "3.3.1",
"dev": true
},
"prebuild-install": {
"version": "7.1.1",
"requires": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^1.0.1",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
}
},
"precond": {
"version": "0.2.3"
},
@ -38789,6 +38509,7 @@
},
"rc": {
"version": "1.2.8",
"dev": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@ -40001,38 +39722,6 @@
"kind-of": "^6.0.2"
}
},
"sharp": {
"version": "0.31.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz",
"integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==",
"requires": {
"color": "^4.2.3",
"detect-libc": "^2.0.1",
"node-addon-api": "^5.0.0",
"prebuild-install": "^7.1.1",
"semver": "^7.3.8",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
"dependencies": {
"lru-cache": {
"version": "6.0.0",
"requires": {
"yallist": "^4.0.0"
}
},
"semver": {
"version": "7.3.8",
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0"
}
}
},
"shebang-command": {
"version": "2.0.0",
"requires": {
@ -40073,34 +39762,6 @@
"signal-exit": {
"version": "3.0.7"
},
"simple-concat": {
"version": "1.0.1"
},
"simple-get": {
"version": "4.0.1",
"requires": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
},
"dependencies": {
"decompress-response": {
"version": "6.0.0",
"requires": {
"mimic-response": "^3.1.0"
}
},
"mimic-response": {
"version": "3.1.0"
}
}
},
"simple-swizzle": {
"version": "0.2.2",
"requires": {
"is-arrayish": "^0.3.1"
}
},
"simple-update-notifier": {
"version": "1.0.7",
"dev": true,
@ -40746,7 +40407,8 @@
}
},
"strip-json-comments": {
"version": "2.0.1"
"version": "2.0.1",
"dev": true
},
"strong-error-handler": {
"version": "2.3.2",

View File

@ -44,7 +44,6 @@
"puppeteer": "^18.0.5",
"read-chunk": "^3.2.0",
"require-yaml": "0.0.1",
"sharp": "^0.31.3",
"smbhash": "0.0.1",
"strong-error-handler": "^2.3.2",
"uuid": "^3.3.3",