diff --git a/src/constants.js b/src/constants.js index feb477e..2f0bc6f 100644 --- a/src/constants.js +++ b/src/constants.js @@ -18,11 +18,14 @@ const BASE_ROUTE = process.env.BASE_ROUTE ? ) : '/'; +const STOCK_DATA_URL = `https://github.com/paramsiddharth/rema/releases/download/stock-data-v1.0/static.zip`; + module.exports = { INTERNAL_STATIC_DIR, MAX_CAIRO_DIMENSION, SINGLE_WHITE_PIXEL, PORT, DB, - BASE_ROUTE + BASE_ROUTE, + STOCK_DATA_URL }; \ No newline at end of file diff --git a/src/helpers/render.js b/src/helpers/render.js index 9741818..07f6417 100644 --- a/src/helpers/render.js +++ b/src/helpers/render.js @@ -12,16 +12,19 @@ const { SINGLE_WHITE_PIXEL } = require('../constants'); -const RESOURCES = path.join(INTERNAL_STATIC_DIR, 'fonts.json'); -if (fs.existsSync(RESOURCES)) { - const fonts = fs.readJSONSync(RESOURCES).filter(i => i.type === 'font'); - for (const font of fonts) { - const { - path: fontPath, - family - } = font; - registerFont(path.join(INTERNAL_STATIC_DIR, fontPath), { family }); +if (!process.env.FONTS_LOADED) { + const RESOURCES = path.join(INTERNAL_STATIC_DIR, 'items.json'); + if (fs.existsSync(RESOURCES)) { + const fonts = fs.readJSONSync(RESOURCES).filter(i => i.type === 'font'); + for (const font of fonts) { + const { + path: fontPath, + family + } = font; + registerFont(path.join(INTERNAL_STATIC_DIR, fontPath), { family }); + } } + process.env.FONTS_LOADED = 1; } // Render a preview of a template or a certificate diff --git a/src/index.js b/src/index.js index 7209aee..5e689fc 100644 --- a/src/index.js +++ b/src/index.js @@ -35,7 +35,7 @@ const exitHandler = function(sig, err) { if (fs.existsSync(tempDirectory.name)) fs.emptyDirSync(tempDirectory.name); tempDirectory.removeCallback(); - console.log(`\rExitting Rema... 🌸`); + console.log(`Exitting Rema... 🌸`); process.exit(exitCode); }; @@ -54,12 +54,6 @@ const certificateRouter = require('./routes/certificate'); // For global await support (async () => { -// Perform some initial checks -const initResults = await require('./initCheck')(); -console.log(initResults.msg); -if (!initResults.success) - process.exit(1); - // Connect to database try { await mongoose.connect(DB, { @@ -67,12 +61,18 @@ try { useUnifiedTopology: true, useCreateIndex: true }); - console.log(`Connected to database. 🔐`); + console.log(`Connected to the database. 🔐`); } catch(e) { console.error(`Failed to connect to database: "${e.message}"`); process.exit(1); } +// Perform some initial checks +const initResults = await require('./initCheck')(); +console.log(initResults.msg); +if (!initResults.success) + process.exit(1); + const app = express(); // Show debug output to console diff --git a/src/initCheck.js b/src/initCheck.js index 7e4d284..5b85d35 100644 --- a/src/initCheck.js +++ b/src/initCheck.js @@ -1,9 +1,15 @@ const fs = require('fs-extra'); const path = require('path'); const { check: portInUse } = require('tcp-port-used'); +const axios = require('axios').default; +const unzip = require('unzipper'); +const { registerFont } = require('canvas'); + +const Template = require('./models/template'); const { INTERNAL_STATIC_DIR, - PORT + PORT, + STOCK_DATA_URL } = require('./constants'); const checks = { @@ -12,25 +18,10 @@ const checks = { action: async () => { const port = PORT; - // Creating a new server just to check for a free port has some caveats - /* const inUse = p => new Promise(resolve => { - const server = net.createServer(); - - server.once('error', err => { - if (err.code === 'EADDRINUSE') - resolve(true); - }); - - server.once('listening', () => { - server.close(); - resolve(false); - }); - - server.listen(p); - }); */ - if (await portInUse(port)) throw new Error(`Port ${port} already in use!`); + + return `Port available for use!`; }, critical: true }, @@ -41,11 +32,98 @@ const checks = { if (!fullInternalStaticDir.endsWith(path.sep)) fullInternalStaticDir += path.sep; - if (!fs.existsSync(fullInternalStaticDir)) - throw new Error(`Internal static directory '${fullInternalStaticDir}' doesn't exist!`); + if (!fs.existsSync(fullInternalStaticDir)) { + try { + fs.ensureDirSync(fullInternalStaticDir); + } catch(e) { + throw new Error(`Failed to ensure internal static directory '${fullInternalStaticDir}' (${e.message})!`); + } + } + + return `Internal static directory ensured!`; }, critical: true }, + stockMaterial: { + msg: `Checking if stock material exists...`, + action: async () => { + if (!fs.existsSync(INTERNAL_STATIC_DIR)) + throw new Error(`Missing internal static directory '${INTERNAL_STATIC_DIR}'!`); + + const stockDir = path.resolve(INTERNAL_STATIC_DIR, 'stock') + path.sep; + const templates = await Template.find({ name: /^stock-/ }); + + if (!fs.existsSync(stockDir) || (templates.length < 1 && !fs.existsSync(path.join(INTERNAL_STATIC_DIR, 'items.json')))) { + console.log(`Stock material not found! Downloading...`); + + try { + fs.ensureDirSync(stockDir); + fs.emptyDirSync(stockDir); + + const stockZip = path.join(process.env.TMP_DIR, 'stock.zip'); + + const done = await new Promise(async resolve => { + const resp = await axios.get(STOCK_DATA_URL, { responseType: 'stream' }); + const downloadStream = fs.createWriteStream(stockZip); + resp.data.pipe(downloadStream); + + downloadStream.on('close', () => { + console.log(`Download finished! Extracting...`); + const extractStream = fs.createReadStream(stockZip); + const extractionStream = unzip.Extract({ path: INTERNAL_STATIC_DIR }); + extractStream.pipe(extractionStream); + extractionStream.on('close', () => resolve('OK')); + }); + }); + + if (done !== 'OK') + throw new Error(); + + console.log(`Extracted! Loading material into database...`); + } catch(e) { + throw new Error(`Couldn't ensure stock material in Rema (${e.message})!`); + } + } + + try { + const items = fs.readJSONSync(path.join(INTERNAL_STATIC_DIR, 'items.json')); + + const temps = items.filter(i => i.type === 'template'); + if (templates.length < 1) { + console.log(`Found ${temps.length} stock templates.`); + + let i = 0; + for (const t of temps) { + const { + name, + path: templatePath + } = t; + if ((await Template.findOne({ name })) == null) { + try { + process.stdout.write(`Loading template '${name}' to database...`); + const fullPath = path.join(INTERNAL_STATIC_DIR, templatePath); + const obj = fs.readJSONSync(fullPath); + const te = new Template(obj); + await te.save() + process.stdout.write(`\r` + ' '.repeat(60)); + console.log(`\r'${name}' added to database.`); + i++; + } catch(e) { + process.stdout.write(`\r` + ' '.repeat(60)); + console.log(`\rFailed to load tempalte '${name}' into database.`); + } + } else + console.log(`'${name}' already in database. Skipping...`); + } + } + } catch(e) { + throw new Error(`Failed to load stock templates into database (${e.message})!`); + } + + return `Stock material loaded!`; + }, + critical: false + } }; module.exports = async () => { @@ -65,7 +143,8 @@ module.exports = async () => { console.log(`${i}. ${msg}`); try { - await action(); + const ret = await action(); + console.log(ret); successCount++; } catch(e) { console.log(e.message); diff --git a/src/package-lock.json b/src/package-lock.json index a393a7d..738e8e4 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "rema", - "version": "2.0.0-beta", + "version": "2.1.0-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "rema", - "version": "2.0.0-beta", + "version": "2.1.0-beta", "license": "ISC", "dependencies": { "axios": "^0.21.1", @@ -29,6 +29,7 @@ "strftime": "^0.10.0", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", + "unzipper": "^0.10.11", "uuid": "^8.3.2" }, "devDependencies": { @@ -288,6 +289,26 @@ "node": ">= 0.8" } }, + "node_modules/big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -449,6 +470,22 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -555,6 +592,17 @@ "node": ">=6" } }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -1078,6 +1126,14 @@ "node": ">=10" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -1296,6 +1352,42 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -1795,6 +1887,11 @@ "node": ">=8" } }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -2748,6 +2845,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, "node_modules/setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -3039,6 +3141,14 @@ "node": "*" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "engines": { + "node": "*" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -3119,6 +3229,28 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + }, "node_modules/update-notifier": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", @@ -3665,6 +3797,20 @@ "safe-buffer": "5.1.2" } }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "requires": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + } + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3795,6 +3941,16 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, + "buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==" + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=" + }, "busboy": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", @@ -3880,6 +4036,14 @@ "simple-get": "^3.0.3" } }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "requires": { + "traverse": ">=0.3.0 <0.4" + } + }, "chalk": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", @@ -4272,6 +4436,14 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==" }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -4433,6 +4605,35 @@ "dev": true, "optional": true }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -4817,6 +5018,11 @@ "package-json": "^6.3.0" } }, + "listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=" + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -5522,6 +5728,11 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -5735,6 +5946,11 @@ } } }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=" + }, "truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -5800,6 +6016,30 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "unzipper": { + "version": "0.10.11", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.11.tgz", + "integrity": "sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==", + "requires": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + }, + "dependencies": { + "bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=" + } + } + }, "update-notifier": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", diff --git a/src/package.json b/src/package.json index f57280e..eaa4c64 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "rema", - "version": "2.0.0-beta", + "version": "2.1.0-beta", "description": "A powerful certificate generation and management system.", "main": "index.js", "private": "true", @@ -46,6 +46,7 @@ "strftime": "^0.10.0", "tcp-port-used": "^1.0.2", "tmp": "^0.2.1", + "unzipper": "^0.10.11", "uuid": "^8.3.2" }, "devDependencies": {