Version 5.1.1

This commit is contained in:
Chrystian Huot 2021-05-19 15:25:12 -04:00
parent 07e7a563bb
commit cd2004cef6
13 changed files with 191 additions and 168 deletions

View file

@ -16,6 +16,12 @@ This one is a big one... **Be sure to backup your config.json and your database.
- Minor changes to the webapp look, less rounded.
- Talkgroup buttons label now wraps on 2 lines.
_v5.1.1_
- Fix database migration script to version 5.1 to filter out duplicate property values on unique fields.
- Fix payload too large error message when saving configuration from the administrative dashboard.
- Bring back the load-rrdb, load-tr and random uuid command line tools.
# Version 5.0
- Add rdioScanner.options.autoPopulate which by default is true. The configuration file will now be automatically populated from new received calls with unknown system/talkgroup.

View file

@ -1,6 +1,6 @@
{
"name": "rdio-scanner-client",
"version": "5.1.0",
"version": "5.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -34,5 +34,5 @@
"build": "ng build --base-href ./ --prod",
"start": "ng serve"
},
"version": "5.1.0"
"version": "5.1.1"
}

View file

@ -49,7 +49,7 @@ export class RdioScannerAdminImportCsvComponent {
const group = tg[this.fields[this.mode][4]];
if (!config.groups?.find((g) => g.label === group)) {
const id = config.groups?.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 0);
const id = config.groups?.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 1);
config.groups?.push({ _id: id, label: group });
}
@ -57,7 +57,7 @@ export class RdioScannerAdminImportCsvComponent {
const tag = tg[this.fields[this.mode][3]];
if (!config.tags?.find((t) => t.label === tag)) {
const id = config.tags?.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 0);
const id = config.tags?.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 1);
config.tags?.push({ _id: id, label: tag });
}

View file

@ -86,7 +86,36 @@ Admin password has been reset.
If you do not specify a password after the `reset-admin-password` command, the default password will be configured, which is `rdio-scanner`.
# 6. Finally
# 6. Optional command line tools for hardcore users
Load a radioreference.com talkgroups file into a system:
```bash
$ docker run -it --rm chuot/rdio-scanner:latest load-rrdb
USAGE: load-rrdb <system_id> <input_tg_csv>
```
Load a trunk-recorder talkgroups file into a system:
```bash
$ docker run -it --rm chuot/rdio-scanner:latest load-tr
USAGE: load-tr <system_id> <input_tg_csv>
```
Generate random UUIDs
```bash
$ docker run -it --rm chuot/rdio-scanner:latest random-uuid
1e5ff9c6-fdaf-495d-b67e-95a73e6c0e06
$ docker run -it --rm chuot/rdio-scanner:latest random-uuid 5
c5ab8726-f59a-4609-8d35-7dd534b9d038
a54d5bdb-0e59-4616-9498-cfc42ac572f1
061b78a1-b2a9-47a8-91e5-8b285a46a268
5da5c824-fe4a-4cf8-8146-8a6806af4eed
92b1eafa-02ff-4993-9133-6098d6d37888
```
# 7. Finally
Remember that [Rdio Scanner](https://github.com/chuot/rdio-scanner) must be supplied with audio files from a recorder. Either you mount another volume with the `--volume /recordings:/home/radio/recordings` options and use a `dirWatch` to monitor it, or you use the API with an `upload script`. See the [examples folder](./examples) for some examples.

View file

@ -82,7 +82,37 @@ Admin password has been reset.
If you do not specify a password after the `reset-admin-password` command, the default password will be configured, which is `rdio-scanner`.
# 7. Finally
# 7. Optional command line tools for hardcore users
Load a radioreference.com talkgroups file into a system:
```bash
$ node server load-rrdb
USAGE: load-rrdb <system_id> <input_tg_csv>
```
Load a trunk-recorder talkgroups file into a system:
```bash
$ node server load-tr
USAGE: load-tr <system_id> <input_tg_csv>
```
Generate random UUIDs
```bash
$ node server random-uuid
1e5ff9c6-fdaf-495d-b67e-95a73e6c0e06
$ node server random-uuid 5
c5ab8726-f59a-4609-8d35-7dd534b9d038
a54d5bdb-0e59-4616-9498-cfc42ac572f1
061b78a1-b2a9-47a8-91e5-8b285a46a268
5da5c824-fe4a-4cf8-8146-8a6806af4eed
92b1eafa-02ff-4993-9133-6098d6d37888
```
# 8. Finally
Remember that [Rdio Scanner](https://github.com/chuot/rdio-scanner) must be supplied with audio files from a recorder. Either you use a `dirWatch` to monitor a folder, or you use the API with an `upload script`. See the [examples folder](./examples) for some examples.

View file

@ -14,5 +14,5 @@
"update": "node update"
},
"type": "module",
"version": "5.1.0"
"version": "5.1.1"
}

View file

@ -133,8 +133,8 @@ export class App extends EventEmitter {
this.router.disable('x-powered-by');
this.router.use(compression());
this.router.use(cors());
this.router.use(express.json());
this.router.use(express.urlencoded({ extended: false }));
this.router.use(express.json({ limit: '10mb' }));
this.router.use(express.urlencoded({ extended: false, limit: '10mb' }));
this.router.use(helmet({ contentSecurityPolicy: false }));
this.router.use(express.static(staticDir));
this.router.use((req, res, next) => {

View file

@ -20,7 +20,9 @@
'use strict';
import EventEmitter from 'events';
import path from 'path';
import Umzug from 'umzug';
import url from 'url';
import { Admin } from './admin.js';
import { API } from './api.js';
@ -31,6 +33,7 @@ import { DirWatch } from './dir-watch.js';
import { Downstream } from './downstream.js';
import { Log } from './log.js';
import { Models } from './models.js';
import { Utils } from './utils.js';
import { WebSocket } from './websocket.js';
import rs001CreateSystem from './migrations/20191028144433-create-rdio-scanner-system.js';
@ -41,6 +44,8 @@ import rs005OptimizeCalls from './migrations/20200123094105-optimize-rdio-scanne
import rs006NewV4Tables from './migrations/20200428132918-new-v4-tables.js';
import rs007NewV51Tables from './migrations/20210115105958-new-v5.1-tables.js';
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
const migrations = [
{ name: '20191028144433-create-rdio-scanner-system', up: rs001CreateSystem.up, down: rs001CreateSystem.down },
{ name: '20191029092201-create-rdio-scanner-call', up: rs002CreateCall.up, down: rs002CreateCall.down },
@ -84,8 +89,46 @@ export class RdioScanner extends EventEmitter {
this.config = new Config(this);
this.utils = new Utils(this);
this.config.once('ready', async () => {
if (process.argv[2] === 'reset-admin-password') {
const cmd = process.argv[2];
if (cmd === 'load-rrdb' || cmd === 'load-tr') {
if (process.argv.length === 5) {
try {
const sysId = parseInt(process.argv[3], 10);
const file = path.resolve(process.env.APP_DATA || dirname, process.argv[4]);
this.utils.loadCSV(sysId, file, cmd !== 'load-tr' ? 1 : 0);
this.config.once('persisted', () => {
console.log(`File ${file} imported successfully into system ${sysId}`);
process.exit();
});
} catch (error) {
console.error(error.message);
process.exit();
}
} else {
console.log('USAGE: load-rrdb <system_id> <input_tg_csv>');
process.exit();
}
} else if (cmd === 'random-uuid') {
const count = parseInt(process.argv[3], 10) || 1;
this.utils.randomUUID(count).forEach((uuid) => console.log(uuid));
process.exit();
} else if (cmd === 'reset-admin-password') {
if (typeof process.argv[3] === 'string' && process.argv[3].length) {
this.config.adminPassword = process.argv[3];

View file

@ -245,7 +245,7 @@ function parseAccess(access) {
}
return { code, ident, order, systems };
}).filter((acc) => acc.code !== null);
}).filter((acc, idx, arr) => acc.code !== null && arr.findIndex((v) => v.code === acc.code) === idx);
}
function parseApiKeys(apiKeys) {
@ -279,7 +279,7 @@ function parseApiKeys(apiKeys) {
order: index + 1,
systems,
};
}).filter((api) => api.key !== null);
}).filter((api, idx, arr) => api.key !== null && arr.findIndex((v) => v.key === api.key) === idx);
}
function parseDirWatch(dirWatch) {
@ -299,7 +299,7 @@ function parseDirWatch(dirWatch) {
type: ['sdr-trunk', 'trunk-recorder'].includes(dw.type) ? dw.type : null,
usePolling: typeof dw.usePolling === 'boolean' ? dw.usePolling
: typeof dw.usePolling === 'number' ? true : defaults.dirWatch.usePolling,
})).filter((dw) => dw.directory !== null && dw.system !== null);
})).filter((dw, idx, arr) => dw.directory !== null && dw.system !== null && arr.findIndex((v) => v.directory === dw.directory) === idx);
}
function parseDownstreams(downstreams) {
@ -325,7 +325,7 @@ function parseDownstreams(downstreams) {
systems: rewriteSystemsProperty(ds.systems, true),
url: typeof ds.url === 'string' ? ds.url : null,
};
}).filter((ds) => !(ds.apiKey === null || ds.url === null));
}).filter((ds, idx, arr) => !(ds.apiKey === null || ds.url === null) && arr.findIndex((v) => v.apiKey === ds.apiKey) === idx);
}
function parseOptions(options) {
@ -368,7 +368,7 @@ function parseSystems(systems) {
id: typeof unit.id === 'number' ? unit.id : null,
label: typeof unit.label === 'string' ? unit.label : `${unit.id}`,
})).filter((unit) => unit.id !== null) : [],
})).filter((system) => system.id !== null).sort((sysA, sysB) => {
})).filter((system, idx, arr) => system.id !== null && arr.indexOf(system) === idx).sort((sysA, sysB) => {
if (typeof sysA.order === 'number' && typeof sysB.order !== 'number') {
return -1;

View file

@ -19,118 +19,96 @@
'use strict';
const fs = require('fs');
const path = require('path');
const uuid = require('uuid');
import fs from 'fs';
import path from 'path';
import url from 'url';
import { v4 as uuidv4 } from 'uuid';
class Utils {
const dirname = path.dirname(url.fileURLToPath(import.meta.url));
export class Utils {
constructor(ctx = {}) {
this.config = ctx.config;
}
loadRRDB(sysId, input) {
loadCSV(sysId, file, type) {
if (typeof sysId !== 'number' || sysId < 0) {
throw new Error(`Invalid system id ${JSON.stringify(sysId)}`);
}
if (typeof input !== 'string' || !input.length) {
throw new Error(`Invalid filename ${JSON.stringify(input)}`);
if (typeof file !== 'string' || !file.length) {
throw new Error(`Invalid filename ${JSON.stringify(file)}`);
}
input = path.resolve(__dirname, input);
file = path.resolve(dirname, file);
if (!fs.existsSync(input)) {
throw new Error(`${input} file not found!`);
if (!fs.existsSync(file)) {
throw new Error(`${file} file not found!`);
}
const csv = fs.readFileSync(input, 'utf8').split(/[\n|\r|\r\n]/).map((csv) => csv.split(/,/));
type = [0, 1].includes(type) ? type : 0;
// [id, label, description, tag, group]
const fields = [
[0, 3, 4, 5, 6], // trunk-recorder
[0, 2, 4, 5, 6], // radioreference.com
];
const groups = this.config.groups;
const tags = this.config.tags;
const csv = fs.readFileSync(file, 'utf8').split(/[\n|\r|\r\n]/).map((csv) => csv.split(/,/));
const system = {
id: sysId,
label: path.parse(input).name,
label: path.parse(file).name,
talkgroups: [],
};
for (let line of csv) {
const encrypted = typeof line[3] === 'string' ? line[3].includes('E') : false;
const id = parseInt(line[0], 10);
const label = line[2] || '';
const name = line[4] || '';
const tag = line[5] || '';
const group = line[6] || '';
const id = parseInt(line[fields[type][0]], 10);
const label = line[fields[type][1]] || '';
const name = line[fields[type][2]] || '';
const tagLabel = line[fields[type][3]] || '';
const groupLabel = line[fields[type][4]] || '';
if (!encrypted && id > 0 && label.length && name.length && tag.length && group.length) {
system.talkgroups.push({ id, label, name, tag, group });
if (isNaN(id) || id <= 0 || !label.length || !name.length) continue;
if (!groups.find((g) => g.label === groupLabel)) {
const id = groups.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 1);
console.log(id, groupLabel);
groups.push({ _id: id, label: groupLabel });
}
if (!tags.find((t) => t.label === tagLabel)) {
const id = tags.reduce((pv, cv) => typeof cv._id === 'number' && cv._id >= pv ? cv._id + 1 : pv, 1);
tags.push({ _id: id, label: tagLabel });
}
const groupId = groups.find((g) => g.label === groupLabel)?._id;
const tagId = tags.find((t) => t.label === tagLabel)?._id;
system.talkgroups.push({ id, label, name, tagId, groupId });
}
system.talkgroups.sort((a, b) => a.label.localeCompare(b.label));
this.config.systems = this.config.systems.filter((system) => system.id !== sysId);
this.config.systems.push(system);
this.config.systems.sort((a, b) => a.id - b.id);
return `File ${input} imported successfully into system ${sysId}`;
}
loadTR(sysId, input) {
if (typeof sysId !== 'number' || sysId < 0) {
throw new Error(`Invalid system id ${JSON.stringify(sysId)}`);
}
if (typeof input !== 'string' || !input.length) {
throw new Error(`Invalid filename ${JSON.stringify(input)}`);
}
input = path.resolve(__dirname, input);
if (!fs.existsSync(input)) {
throw new Error(`${input} file not found!`);
}
const csv = fs.readFileSync(input, 'utf8').split(/[\n|\r|\r\n]/).map((csv) => csv.split(/,/));
const system = {
id: sysId,
label: path.parse(input).name,
talkgroups: [],
};
for (let line of csv) {
const encrypted = typeof line[2] === 'string' ? line[2].includes('E') : false;
const id = parseInt(line[0], 10);
const label = line[3] || '';
const name = line[4] || '';
const tag = line[5] || '';
const group = line[6] || '';
if (!encrypted && id > 0 && label.length && name.length && tag.length && group.length) {
system.talkgroups.push({ id, label, name, tag, group });
}
}
system.talkgroups.sort((a, b) => a.label.localeCompare(b.label));
this.config.systems = this.config.systems.filter((system) => system.id !== sysId);
this.config.systems.push(system);
this.config.systems.sort((a, b) => a.id - b.id);
return `File ${input} imported successfully into system ${sysId}`;
this.config.groups = groups;
this.config.tags = tags;
this.config.systems = this.config.systems.filter((sys) => sys.id !== sysId).concat(system);
}
randomUUID(count = 1) {
const uuids = [];
for (let i = 0; i < count; i++) {
uuids.push(uuid.v4());
uuids.push(uuidv4());
}
return uuids;
}
}
module.exports = Utils;
}

View file

@ -1,6 +1,6 @@
{
"name": "rdio-scanner-server",
"version": "5.1.0",
"version": "5.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -785,16 +785,6 @@
}
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@ -1234,9 +1224,9 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.3.730",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.730.tgz",
"integrity": "sha512-1Tr3h09wXhmqXnvDyrRe6MFgTeU0ZXy3+rMJWTrOHh/HNesWwBBrKnMxRJWZ86dzs8qQdw2c7ZE1/qeGHygImA==",
"version": "1.3.732",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.732.tgz",
"integrity": "sha512-qKD5Pbq+QMk4nea4lMuncUMhpEiQwaJyCW7MrvissnRcBDENhVfDmAqQYRQ3X525oTzhar9Zh1cK0L2d1UKYcw==",
"dev": true
},
"emoji-regex": {
@ -1813,17 +1803,6 @@
"is-property": "^1.0.2"
}
},
"get-intrinsic": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
"dev": true,
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.1"
}
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -1946,12 +1925,6 @@
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==",
"dev": true
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
@ -2117,15 +2090,6 @@
"binary-extensions": "^2.0.0"
}
},
"is-boolean-object": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
"integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
"dev": true,
"requires": {
"call-bind": "^1.0.0"
}
},
"is-ci": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz",
@ -2183,12 +2147,6 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-number-object": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==",
"dev": true
},
"is-obj": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
@ -2208,15 +2166,6 @@
"isobject": "^3.0.1"
}
},
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
"dev": true,
"requires": {
"isobject": "^3.0.1"
}
},
"is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
@ -2228,12 +2177,6 @@
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true
},
"is-string": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
"dev": true
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
@ -4315,9 +4258,9 @@
}
},
"watchpack": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz",
"integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==",
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz",
"integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==",
"dev": true,
"requires": {
"glob-to-regexp": "^0.4.1",
@ -4325,9 +4268,9 @@
}
},
"webpack": {
"version": "5.37.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.37.0.tgz",
"integrity": "sha512-yvdhgcI6QkQkDe1hINBAJ1UNevqNGTVaCkD2SSJcB8rcrNNl922RI8i2DXUAuNfANoxwsiXXEA4ZPZI9q2oGLA==",
"version": "5.37.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.37.1.tgz",
"integrity": "sha512-btZjGy/hSjCAAVHw+cKG+L0M+rstlyxbO2C+BOTaQ5/XAnxkDrP5sVbqWhXgo4pL3X2dcOib6rqCP20Zr9PLow==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.0",
@ -4484,12 +4427,6 @@
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
"dev": true
},
"wildcard": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
"integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
"dev": true
},
"wkx": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",

View file

@ -24,7 +24,7 @@
},
"devDependencies": {
"eslint": "^7.26.0",
"webpack": "^5.37.0",
"webpack": "^5.37.1",
"webpack-cli": "^4.7.0",
"webpack-node-externals": "^3.0.0"
},
@ -46,5 +46,5 @@
"start": "nodemon"
},
"type": "module",
"version": "5.1.0"
"version": "5.1.1"
}