From 198880773d8e0ed6a1768c2ccc4d3c92d29c7d87 Mon Sep 17 00:00:00 2001 From: csmig Date: Tue, 13 Feb 2024 11:18:13 -0500 Subject: [PATCH 01/13] notional idea --- lib/alarm.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 lib/alarm.js diff --git a/lib/alarm.js b/lib/alarm.js new file mode 100644 index 0000000..b1599fe --- /dev/null +++ b/lib/alarm.js @@ -0,0 +1,86 @@ +import { EventEmitter } from "node:events" + +/** + * Represents the state of each alarm type. + * @typedef {Object} Alarms + * @property {boolean} apiOffline - the STIG manager API is unreachable. + * @property {boolean} authOffline - the OIDC IdP is unreachable. + * @property {boolean} noToken - the OIDC IdP did not issue the client a token. + * @property {boolean} noGrant - the client has an insufficient grant on the configured Collection. + */ + +/** + * @typedef {'apiOffline' | 'authOffline' | 'noToken' | 'noGrant'} AlarmType + */ + +class Alarm extends EventEmitter { + /** @type {Alarms} */ + #alarms + + constructor () { + super() + this.#alarms = { + apiOffline: false, + authOffline: false, + noToken: false, + noGrant: false, + } + } + + /** + * Emits 'alarmRaised' or 'alarmLowered' based on 'state', passing the alarmType + * @param {AlarmType} event + * @param {boolean} state + */ + #emitAlarmEvent (alarmType, state) { + if (state) { + this.emit('alarmRaised', alarmType) + } + else { + this.emit('alarmLowered', alarmType) + } + } + + /** + * Sets the state of the apiOffline alarm + * @param {boolean} state + */ + apiOffline (state) { + this.#alarms.apiOffline = state + this.#emitAlarmEvent( 'apiOffline', state) + } + + /** + * Sets the state of the authOffline alarm + * @param {boolean} state + */ + authOffline (state) { + this.#alarms.authOffline = state + this.#emitAlarmEvent( 'authOffline', state) + } + + /** + * Sets the state of the noToken alarm + * @param {boolean} state + */ + noToken (state) { + this.#alarms.noToken = state + this.#emitAlarmEvent( 'noToken', state) + } + + /** + * Sets the state of the noGrant alarm + * @param {boolean} state + */ + noGrant (state) { + this.#alarms.noGrant = state + this.#emitAlarmEvent( 'noGrant', state) + } + + /** @type {Alarms} */ + get alarms() { + return this.#alarms + } +} + +export default new Alarm() \ No newline at end of file From eebe4653844e8348dd1981605233d572a9fcb4f3 Mon Sep 17 00:00:00 2001 From: csmig Date: Fri, 1 Mar 2024 12:27:01 -0500 Subject: [PATCH 02/13] Squashed commit of the following: commit f08e1878ef3959341195a655c64866e61c8f3a39 Author: csmig <33138761+csmig@users.noreply.github.com> Date: Thu Feb 29 18:58:21 2024 +0000 chore: remove/update dependencies (#97) commit a70d38a4310389781c14ea0e9b4d6759e183320c Author: csmig <33138761+csmig@users.noreply.github.com> Date: Thu Feb 22 19:41:45 2024 +0000 feat: in scan mode, migrate addToHistory() calls to the queue handlers (#93) Co-authored-by: matte22 commit d0077929cdbd8dc3309f458c24e4a79490f3af5f Author: Mathew <77069472+Matte22@users.noreply.github.com> Date: Thu Feb 22 13:28:34 2024 -0500 test: Create Workflow for unit testing. (#92) commit e398da963cb780c6058c910ed143a36f89874a83 Author: Mathew <77069472+Matte22@users.noreply.github.com> Date: Mon Feb 19 17:57:22 2024 -0500 feat: scan mode history management (#90) commit 00f497f810e30105be6179389f25f39f91bda0b9 Author: csmig <33138761+csmig@users.noreply.github.com> Date: Fri Feb 16 15:37:43 2024 +0000 feat: pass filename to parsers as sourceRef (#91) --- .github/workflows/sonarcloud.yml | 2 +- .github/workflows/unit-testing.yml | 40 + .gitignore | 3 +- dist/.gitignore | 2 +- index.js | 13 +- lib/args.js | 21 +- lib/cargo.js | 10 +- lib/parse.js | 19 +- lib/scan.js | 338 ++++-- package-lock.json | 1660 ++++++++++++++++++++++++---- package.json | 14 +- test/scan/historyfile.test.js | 247 +++++ 12 files changed, 2058 insertions(+), 311 deletions(-) create mode 100644 .github/workflows/unit-testing.yml create mode 100644 test/scan/historyfile.test.js diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 71f6c9e..be37743 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -40,7 +40,7 @@ jobs: -Dsonar.projectName=NUWCDIVNPT_stigman-watcher -Dsonar.organization=nuwcdivnpt -Dsonar.inclusions=**/*.js - -Dsonar.exclusions=**/node_modules/**, + -Dsonar.exclusions=**/node_modules/**,**/test/**,**/coverage/** # This will fail the action if Quality Gate fails (leaving out for now ) diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml new file mode 100644 index 0000000..c2cbb21 --- /dev/null +++ b/.github/workflows/unit-testing.yml @@ -0,0 +1,40 @@ +name: Run Unit Tests and Upload Coverage Artifact +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "lib/**" + - "index.js" + - "test/**" + pull_request: + branches: + - main + paths: + - "lib/**" + - "index.js" + - "test/**" + +jobs: + build_test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install app dependencies + run: npm ci + - name: Create .env file + run: echo -e "WATCHER_COLLECTION=1\nWATCHER_API_BASE=url\nWATCHER_AUTHORITY=auth\nWATCHER_CLIENT_ID=clientId\nWATCHER_CLIENT_SECRET=secret" > .env + - name: Run tests + run: npm test + - name: Upload coverage to github + uses: actions/upload-artifact@v3 + if: ${{ always() }} + with: + name: coverage + path: coverage \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2706b84..d64dc17 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ watched/ .env *.log log.json -bundle.js \ No newline at end of file +bundle.js +coverage/ \ No newline at end of file diff --git a/dist/.gitignore b/dist/.gitignore index cd84c12..7508a5c 100644 --- a/dist/.gitignore +++ b/dist/.gitignore @@ -9,4 +9,4 @@ * # Except this file -!.gitignore +!.gitignore \ No newline at end of file diff --git a/index.js b/index.js index 344f430..1596982 100755 --- a/index.js +++ b/index.js @@ -10,11 +10,19 @@ import startFsEventWatcher from './lib/events.js' import { getOpenIDConfiguration, getToken } from './lib/auth.js' import * as api from './lib/api.js' import { serializeError } from 'serialize-error' -import startScanner from './lib/scan.js' +import { initScanner } from './lib/scan.js' import semverGte from 'semver/functions/gte.js' const minApiVersion = '1.2.7' +process.on('SIGINT', () => { + logger.info({ + component: 'main', + message: 'received SIGINT, exiting' + }) + process.exit(0) +}) + run() async function run() { @@ -22,6 +30,7 @@ async function run() { logger.info({ component: 'main', message: 'running', + pid: process.pid, options: getObfuscatedConfig(options) }) @@ -30,7 +39,7 @@ async function run() { startFsEventWatcher() } else if (options.mode === 'scan') { - startScanner() + initScanner() } } catch (e) { diff --git a/lib/args.js b/lib/args.js index c556140..6ea8d27 100644 --- a/lib/args.js +++ b/lib/args.js @@ -14,17 +14,17 @@ const component = 'args' function getVersion() { try { - const packageJsonText = readFileSync('./package.json', 'utf8'); - return JSON.parse(packageJsonText).version; + const packageJsonText = readFileSync('./package.json', 'utf8') + return JSON.parse(packageJsonText).version } catch (error) { - console.error('Error reading package.json:', error); + console.error('Error reading package.json:', error) } } let configValid = true -const version = getVersion(); +const version = getVersion() // Use .env, if present, to setup the environment config() @@ -54,18 +54,18 @@ const getBoolean = (envvar, defaultState = true) => { } } const parseIntegerArg = (value) => { - const parsedValue = parseInt(value, 10); + const parsedValue = parseInt(value, 10) if (isNaN(parsedValue)) { - throw new InvalidOptionArgumentError('Not a number.'); + throw new InvalidOptionArgumentError('Not a number.') } - return parsedValue; + return parsedValue } const parseIntegerEnv = (value) => { - const parsedValue = parseInt(value, 10); + const parsedValue = parseInt(value, 10) if (isNaN(parsedValue)) { return undefined } - return parsedValue; + return parsedValue } // Build the Command @@ -92,7 +92,8 @@ program .option('--add-existing', 'For `--mode events`, existing files in the path will generate an `add` event (`WATCHER_ADD_EXISTING=1`). Ignored if `--mode scan`, negate with `--no-add-existing`.', getBoolean('WATCHER_ADD_EXISTING', false)) .option('--no-add-existing', 'Ignore existing files in the watched path (`WATCHER_ADD_EXISTING=0`).') .option('--cargo-delay ', 'Milliseconds to delay processing the queue (`WATCHER_CARGO_DELAY`)', parseIntegerArg, parseIntegerEnv(pe.WATCHER_CARGO_DELAY) ?? 2000) -.option('--cargo-size ', 'Maximum queue size that triggers processing (`WATCHER_CARGO_SIZE`)', parseIntegerArg, parseIntegerEnv(pe.WATCHER_CARGO_SIZE) ?? 25) +.option('--history-write-interval ', 'Interval in milliseconds for when to periodically sync history file(`WATCHER_HISTORY_WRITE_INTERVAL`)', parseIntegerArg, parseIntegerEnv(pe.WATCHER_HISTORY_WRITE_INTERVAL) ?? 15000) +.option('--cargo-size ', 'Maximum queue size that triggers processing (`WATCHER_CARGO_SIZE`)', parseIntegerArg, parseIntegerEnv(pe.WATCHER_CARGO_SIZE) ?? 10) .option('--create-objects', 'Create Assets or STIG Assignments as needed (`WATCHER_CREATE_OBJECTS=1`). Negate with `--no-create-objects`.', getBoolean('WATCHER_CREATE_OBJECTS', true)) .option('--no-create-objects', 'Do not create Assets or STIG Assignments (`WATCHER_CREATE_OBJECTS=0`).') .option('--ignore-dir [name...]', 'DEPRECATED, use --ignore-glob. Sub-directory name to ignore. Can be invoked multiple times.(`WATCHER_IGNORE_DIRS=`)', pe.WATCHER_IGNORE_DIRS?.split(',')) diff --git a/lib/cargo.js b/lib/cargo.js index ef46260..ffa5814 100644 --- a/lib/cargo.js +++ b/lib/cargo.js @@ -3,7 +3,8 @@ import { logger, getSymbol } from './logger.js' import Queue from 'better-queue' import * as api from './api.js' import { serializeError } from 'serialize-error' -import { TaskObject } from 'stig-manager-client-modules' +import { TaskObject } from '@nuwcdivnpt/stig-manager-client-modules' +import { addToHistory } from './scan.js' const component = 'cargo' let batchId = 0 @@ -64,6 +65,7 @@ async function writer ( taskAsset ) { }) } + return true } catch (error) { const errorObj = { @@ -87,6 +89,7 @@ async function writer ( taskAsset ) { errorObj.error = serializeError(error) } logger.error(errorObj) + return false } } @@ -94,12 +97,15 @@ async function resultsHandler( parsedResults, cb ) { const component = 'batch' try { batchId++ + const isModeScan = options.mode === 'scan' logger.info({component: component, message: `batch started`, batchId: batchId, size: parsedResults.length}) const apiAssets = await api.getCollectionAssets(options.collectionId) const apiStigs = await api.getInstalledStigs() const tasks = new TaskObject ({ parsedResults, apiAssets, apiStigs, options:options }) + isModeScan && tasks.errors.length && addToHistory(tasks.errors.map(e => e.sourceRef)) for ( const taskAsset of tasks.taskAssets.values() ) { - await writer( taskAsset ) + const success = await writer( taskAsset ) + isModeScan && success && addToHistory(taskAsset.sourceRefs) } logger.info({component: component, message: 'batch ended', batchId: batchId}) cb() diff --git a/lib/parse.js b/lib/parse.js index f598f94..41bfd7b 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1,15 +1,11 @@ import { cache } from './api.js' -import { XMLParser } from 'fast-xml-parser' import Queue from 'better-queue' import { logger } from './logger.js' import { cargoQueue } from './cargo.js' import { promises as fs } from 'fs' -import he from 'he' -import { reviewsFromCkl, reviewsFromScc, reviewsFromCklb } from 'stig-manager-client-modules' - -const valueProcessor = function (tagName, tagValue, jPath, hasAttributes, isLeafNode) { - he.decode(tagValue) -} +import { reviewsFromCkl, reviewsFromScc, reviewsFromCklb } from '@nuwcdivnpt/stig-manager-client-modules' +import { addToHistory } from './scan.js' +import { options } from './args.js' const defaultImportOptions = { autoStatus: 'saved', @@ -69,11 +65,9 @@ async function parseFileAndEnqueue (file, cb) { importOptions, fieldSettings, allowAccept, - valueProcessor, - XMLParser, - scapBenchmarkMap + scapBenchmarkMap, + sourceRef: file }) - parseResult.file = file logger.debug({component: component, message: `parse results`, results: parseResult}) cargoQueue.push( parseResult ) @@ -85,12 +79,13 @@ async function parseFileAndEnqueue (file, cb) { stats: checklist.stats }) } - logger.verbose({component: component, message: `results queued`, file: parseResult.file, + logger.verbose({component: component, message: `results queued`, file: parseResult.sourceRef, target: parseResult.target.name, checklists: checklistInfo }) cb(null, parseResult) } catch (e) { logger.warn({component: component, message: e.message, file: file}) + options.mode === 'scan' && addToHistory(file) cb(e, null) } } diff --git a/lib/scan.js b/lib/scan.js index ced42ca..bde0814 100644 --- a/lib/scan.js +++ b/lib/scan.js @@ -1,94 +1,306 @@ - -import fastGlob from 'fast-glob' -import {options} from './args.js' import { logger } from './logger.js' -import { existsSync, createWriteStream } from 'fs' -import { queue } from './parse.js' +import { options } from './args.js' +import { queue as parseQueue} from './parse.js' import { serializeError } from 'serialize-error' +import fg from 'fast-glob' import lineByLine from 'n-readlines' - +import fs from 'node:fs' const component = 'scan' -const history = new Set() -if (options.historyFile && existsSync(options.historyFile)) { - const liner = new lineByLine(options.historyFile) - let lineCount = 0 - let line - while (line = liner.next()) { - history.add(line.toString('ascii')) - lineCount++ +const historySet = new Set() // in memory history set +let isWriteScheduled = false // flag to indicate if there is pending files to write to the history file + +/** + * Utility function that calls initHistory() and startScanner() + */ +function initScanner() { + initHistory() + startScanner() +} + +/** + * Starts a fast-glob stream and manages the history of scanned files. + * References options properties {path, ignoreDot, ignoreGlob, oneShot}. + */ +async function startScanner() { + const discoveredFiles = new Set() // in memory set of files discovered in the current scan + try { + // scan the path for files + const stream = fg.stream([`${options.path}/**/*.ckl`, `${options.path}/**/*.xml`, `${options.path}/**/*.cklb`], { + dot: !options.ignoreDot, + suppressErrors: true, + ignore: options.ignoreGlob ?? [] + }) + logger.info({ component, message: `scan started`, path: options.path }) + // for each file discovered + for await (const entry of stream) { + discoveredFiles.add(entry) + logger.verbose({ component, message: `discovered file`, file: entry }) + // check if the file is in the history + if (historySet.has(entry)) { + logger.verbose({component, message: `history match`, file: entry}) + } + // if the file is not in the history, add it to the in memory history set. + else { + parseQueue.push(entry) + logger.info({component, message: `queued for parsing`, file: entry}) + } + } + //Remove stale files: those in historySet but not found in the current scan + removeStaleFiles(discoveredFiles) + logger.info({ component, message: `scan ended`, path: options.path }) + } + catch (e) { + logger.error({ component, error: serializeError(e) }) + } + finally { + if (!options.oneShot) { + scheduleNextScan() + } + else { + logger.info({ component, message: `one-shot scan completed`, path: options.path }) + } } - logger.verbose({ - component: component, - message: `history initialized from file`, - file: options.historyFile, - entries: lineCount - }) } -let historyStream -if (options.historyFile) { - historyStream = createWriteStream(options.historyFile, { flags: 'a' }); +/** + * Deletes entries from historySet that are not present in currentFilesSet. + * @param {Set} currentFilesSet - The set of current files. + */ +function removeStaleFiles(currentFilesSet){ + const staleFiles = Array.from(historySet).filter(file => !currentFilesSet.has(file)) + if (staleFiles.length > 0) { + removeFromHistory(staleFiles) + } } -const interval = options.scanInterval +/** + * Schedules the next scan at options.scanInterval milliseconds from now. + * References options properties {path, scanInterval}. + */ +function scheduleNextScan() { + setTimeout(() => { + startScanner().catch(e => { + logger.error({ component, error: serializeError(e) }) + }) + }, options.scanInterval) -export default async function startScanner () { - try { - const stream = fastGlob.stream([`${options.path}/**/*.ckl`, `${options.path}/**/*.xml`,`${options.path}/**/*.cklb` ], { - dot: !options.ignoreDot, - suppressErrors: true, - ignore: options.ignoreGlob ?? [] + logger.info({ + component, + message: `scan scheduled`, + path: options.path, + delay: options.scanInterval + }) +} + +/** + * Returns immediately if options.historyFile is falsy. + * Initializes the history Set by reading it from a file and adding each line to the history set. + * Creates history file if necessary and if successful sets up SIGINT handler and initializes a write interval. + * References options properties {historyFile, historyWriteInterval} + */ +function initHistory() { + historySet.clear() + + // Log history set values on SIGUSR2 + process.on('SIGUSR2', logHistory) + + // no history file specified, no need to setup + if (!options.historyFile) return + + if (isHistoryFileReadable()) { + // initialize history set with content of history file + const liner = new lineByLine(options.historyFile) + let line + while (line = liner.next()) { + // add each line to the history set + historySet.add(line.toString('ascii')) + } + logger.verbose({ + component, + message: `history initialized from file`, + file:options.historyFile, }) - logger.info({component: component, message: `scan started`, path: options.path}) + } + else { + logger.warn({ + component, + message: 'history file is not readable, scan history is uninitialized', + file: options.historyFile + }) + } - for await (const entry of stream) { - logger.verbose({component: component, message: `discovered file`, file: entry}) - if (history.has(entry)) { - logger.verbose({component: component, message: `history match`, file: entry}) - } - else { - history.add(entry) - logger.verbose({component: component, message: `history add`, file: entry}) - if (options.historyFile) _writeHistory(entry) - queue.push(entry) - logger.info({component: component, message: `queued for parsing`, file: entry}) - } + if (isHistoryFileWriteable()) { + // Handle the interrupt signal + process.prependListener('SIGINT', interruptHandler) + // Set the write interval handler + setInterval(writeIntervalHandler, options.historyWriteInterval) + logger.verbose({ + component, + message: `history file is writable, periodic writes enabled`, + file:options.historyFile, + writeInterval: options.historyWriteInterval + }) + } + else { + logger.warn({ + component, + message: 'history file is not writable, scan history will not be flushed', + file: options.historyFile + }) + } +} + +/** + * Writes historySet to options.historyFile before exiting. + * Intended to be a callback function for the SIGINT signal + */ +function interruptHandler() { + logger.info({ + component, + message: `received SIGINT, try writing history to file` + }) + writeHistoryToFile() +} + +/** + * If isWriteScheduled is true, writes historySet to file and sets isWriteScheduled to false + * Intended to be a callback function of setInterval() + */ +function writeIntervalHandler() { + if (!isWriteScheduled) return + writeHistoryToFile() + isWriteScheduled = false +} + +/** + * Logs an info message with the historySet entries. + * Intended to be a callback function for the SIGUSER2 signal + */ +function logHistory() { + logger.info({ + component, + message: `received SIGUSR2, dumping historySet entries`, + history: Array.from(historySet) + }) +} + +/** + * Removes files from the history set and schedules a write to the history file. + * @param {string|string[]} files - The file(s) to be removed. + */ +function removeFromHistory (files) { + // process array of files + if (Array.isArray(files)) { + for (const entry of files) { + historySet.delete(entry) } - logger.info({component: component, message: `scan ended`, path: options.path}) } - catch(e) { - logger.error({component: component, error: serializeError(e)}) + // process single file + else { + historySet.delete(files) } - finally { - if (!options.oneShot) scheduleNextScan() + + isWriteScheduled = true // Indicate that there's work to be done + logger.verbose({ + component, + message: `removed from history`, + file: files + }) +} + +/** + * Adds files to the history set and schedules a write to the history file. + * @param {Array|string} files - The file(s) to be added. + */ +function addToHistory (files) { + // process array of files + if (Array.isArray(files)) { + for (const entry of files) { + historySet.add(entry) + } + } else { + // single item + historySet.add(files) } + + isWriteScheduled = true + logger.verbose({ + component, + message: `added to history`, + file: files + }) } -async function _writeHistory (entry) { +/** + * Saves the historySet entries to a history file. + */ +function writeHistoryToFile() { try { - historyStream.write(`${entry}\n`) + if (isHistoryFileWriteable()) { + const data = Array.from(historySet).join('\n') + '\n' + fs.writeFileSync(options.historyFile, data) + logger.verbose({ + component:component, + message: `history file overwritten with history data from memory`, + file: options.historyFile + }) + } + else { + logger.warn({ + component, + message: 'history file is not writable, scan history will not be flushed', + file: options.historyFile + }) + } } catch (e) { logger.error({ - component: component, + component, + message: 'failure writing to history file', error: serializeError(e) }) } } -function scheduleNextScan(delay = options.scanInterval) { - setTimeout(() => { - startScanner().catch(e => { - logger.error({ component: component, error: serializeError(e) }); - }); - }, delay); +/** + * Test if options.historyFile is readable. + * @returns {boolean} + */ +function isHistoryFileReadable() { + try { + fs.accessSync(options.historyFile, fs.constants.R_OK) + return true + } + catch { + return false + } +} - logger.info({ - component: component, - message: `scan scheduled`, - path: options.path, - delay: delay - }); +/** + * Test if options.historyFile is writable. + * If options.historyFile does not exist, create it. + * @returns {boolean} + */ +function isHistoryFileWriteable() { + try { + if (fs.existsSync(options.historyFile)) { + fs.accessSync(options.historyFile, fs.constants.W_OK) + } + else { + fs.closeSync(fs.openSync(options.historyFile, 'w')) + } + return true + } + catch { + return false + } } +export { + startScanner, + initHistory, + initScanner, + addToHistory, + removeFromHistory, +} diff --git a/package-lock.json b/package-lock.json index 7580ddf..be28065 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,37 +9,45 @@ "version": "1.4.1", "license": "MIT", "dependencies": { + "@nuwcdivnpt/stig-manager-client-modules": "^1.4.1", "atob": "^2.1.2", "better-queue": "^3.8.10", "chokidar": "^3.5.1", "commander": "^7.2.0", "dotenv": "^8.2.0", "fast-glob": "^3.2.5", - "fast-xml-parser": "^4.0.7", "got": "^11.8.2", - "he": "^1.2.0", "jsonwebtoken": "^9.0.0", "n-readlines": "^1.0.1", "prompt-sync": "4.1.6", "semver": "^7.3.5", "serialize-error": "^8.0.1", - "stig-manager-client-modules": "github:nuwcdivnpt/stig-manager-client-modules#semver:^1.0.0", "winston": "^3.3.3" }, "bin": { "stigman-watcher": "index.js" }, "devDependencies": { - "esbuild": "^0.20.0" + "c8": "^9.1.0", + "chai": "^5.0.3", + "esbuild": "^0.20.0", + "mocha": "^10.2.0", + "nodemon": "^3.0.3" }, "engines": { "node": ">=14" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", "engines": { "node": ">=0.1.90" } @@ -55,9 +63,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.0.tgz", - "integrity": "sha512-fGFDEctNh0CcSwsiRPxiaqX0P5rq+AqE0SRhYGZ4PX46Lg1FNR6oCxJghf8YgY0WQEgQuh3lErUFE4KxLeRmmw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.1.tgz", + "integrity": "sha512-m55cpeupQ2DbuRGQMMZDzbv9J9PgVelPjlcmM5kxHnrBdBx6REaEd7LamYV7Dm8N7rCyR/XwU6rVP8ploKtIkA==", "cpu": [ "ppc64" ], @@ -71,9 +79,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.0.tgz", - "integrity": "sha512-3bMAfInvByLHfJwYPJRlpTeaQA75n8C/QKpEaiS4HrFWFiJlNI0vzq/zCjBrhAYcPyVPG7Eo9dMrcQXuqmNk5g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.1.tgz", + "integrity": "sha512-4j0+G27/2ZXGWR5okcJi7pQYhmkVgb4D7UKwxcqrjhvp5TKWx3cUjgB1CGj1mfdmJBQ9VnUGgUhign+FPF2Zgw==", "cpu": [ "arm" ], @@ -87,9 +95,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.0.tgz", - "integrity": "sha512-aVpnM4lURNkp0D3qPoAzSG92VXStYmoVPOgXveAUoQBWRSuQzt51yvSju29J6AHPmwY1BjH49uR29oyfH1ra8Q==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.1.tgz", + "integrity": "sha512-hCnXNF0HM6AjowP+Zou0ZJMWWa1VkD77BXe959zERgGJBBxB+sV+J9f/rcjeg2c5bsukD/n17RKWXGFCO5dD5A==", "cpu": [ "arm64" ], @@ -103,9 +111,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.0.tgz", - "integrity": "sha512-uK7wAnlRvjkCPzh8jJ+QejFyrP8ObKuR5cBIsQZ+qbMunwR8sbd8krmMbxTLSrDhiPZaJYKQAU5Y3iMDcZPhyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.1.tgz", + "integrity": "sha512-MSfZMBoAsnhpS+2yMFYIQUPs8Z19ajwfuaSZx+tSl09xrHZCjbeXXMsUF/0oq7ojxYEpsSo4c0SfjxOYXRbpaA==", "cpu": [ "x64" ], @@ -119,9 +127,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.0.tgz", - "integrity": "sha512-AjEcivGAlPs3UAcJedMa9qYg9eSfU6FnGHJjT8s346HSKkrcWlYezGE8VaO2xKfvvlZkgAhyvl06OJOxiMgOYQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.1.tgz", + "integrity": "sha512-Ylk6rzgMD8klUklGPzS414UQLa5NPXZD5tf8JmQU8GQrj6BrFA/Ic9tb2zRe1kOZyCbGl+e8VMbDRazCEBqPvA==", "cpu": [ "arm64" ], @@ -135,9 +143,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.0.tgz", - "integrity": "sha512-bsgTPoyYDnPv8ER0HqnJggXK6RyFy4PH4rtsId0V7Efa90u2+EifxytE9pZnsDgExgkARy24WUQGv9irVbTvIw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.1.tgz", + "integrity": "sha512-pFIfj7U2w5sMp52wTY1XVOdoxw+GDwy9FsK3OFz4BpMAjvZVs0dT1VXs8aQm22nhwoIWUmIRaE+4xow8xfIDZA==", "cpu": [ "x64" ], @@ -151,9 +159,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.0.tgz", - "integrity": "sha512-kQ7jYdlKS335mpGbMW5tEe3IrQFIok9r84EM3PXB8qBFJPSc6dpWfrtsC/y1pyrz82xfUIn5ZrnSHQQsd6jebQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.1.tgz", + "integrity": "sha512-UyW1WZvHDuM4xDz0jWun4qtQFauNdXjXOtIy7SYdf7pbxSWWVlqhnR/T2TpX6LX5NI62spt0a3ldIIEkPM6RHw==", "cpu": [ "arm64" ], @@ -167,9 +175,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.0.tgz", - "integrity": "sha512-uG8B0WSepMRsBNVXAQcHf9+Ko/Tr+XqmK7Ptel9HVmnykupXdS4J7ovSQUIi0tQGIndhbqWLaIL/qO/cWhXKyQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.1.tgz", + "integrity": "sha512-itPwCw5C+Jh/c624vcDd9kRCCZVpzpQn8dtwoYIt2TJF3S9xJLiRohnnNrKwREvcZYx0n8sCSbvGH349XkcQeg==", "cpu": [ "x64" ], @@ -183,9 +191,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.0.tgz", - "integrity": "sha512-2ezuhdiZw8vuHf1HKSf4TIk80naTbP9At7sOqZmdVwvvMyuoDiZB49YZKLsLOfKIr77+I40dWpHVeY5JHpIEIg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.1.tgz", + "integrity": "sha512-LojC28v3+IhIbfQ+Vu4Ut5n3wKcgTu6POKIHN9Wpt0HnfgUGlBuyDDQR4jWZUZFyYLiz4RBBBmfU6sNfn6RhLw==", "cpu": [ "arm" ], @@ -199,9 +207,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.0.tgz", - "integrity": "sha512-uTtyYAP5veqi2z9b6Gr0NUoNv9F/rOzI8tOD5jKcCvRUn7T60Bb+42NDBCWNhMjkQzI0qqwXkQGo1SY41G52nw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.1.tgz", + "integrity": "sha512-cX8WdlF6Cnvw/DO9/X7XLH2J6CkBnz7Twjpk56cshk9sjYVcuh4sXQBy5bmTwzBjNVZze2yaV1vtcJS04LbN8w==", "cpu": [ "arm64" ], @@ -215,9 +223,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.0.tgz", - "integrity": "sha512-c88wwtfs8tTffPaoJ+SQn3y+lKtgTzyjkD8NgsyCtCmtoIC8RDL7PrJU05an/e9VuAke6eJqGkoMhJK1RY6z4w==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.1.tgz", + "integrity": "sha512-4H/sQCy1mnnGkUt/xszaLlYJVTz3W9ep52xEefGtd6yXDQbz/5fZE5dFLUgsPdbUOQANcVUa5iO6g3nyy5BJiw==", "cpu": [ "ia32" ], @@ -231,9 +239,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.0.tgz", - "integrity": "sha512-lR2rr/128/6svngnVta6JN4gxSXle/yZEZL3o4XZ6esOqhyR4wsKyfu6qXAL04S4S5CgGfG+GYZnjFd4YiG3Aw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.1.tgz", + "integrity": "sha512-c0jgtB+sRHCciVXlyjDcWb2FUuzlGVRwGXgI+3WqKOIuoo8AmZAddzeOHeYLtD+dmtHw3B4Xo9wAUdjlfW5yYA==", "cpu": [ "loong64" ], @@ -247,9 +255,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.0.tgz", - "integrity": "sha512-9Sycc+1uUsDnJCelDf6ZNqgZQoK1mJvFtqf2MUz4ujTxGhvCWw+4chYfDLPepMEvVL9PDwn6HrXad5yOrNzIsQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.1.tgz", + "integrity": "sha512-TgFyCfIxSujyuqdZKDZ3yTwWiGv+KnlOeXXitCQ+trDODJ+ZtGOzLkSWngynP0HZnTsDyBbPy7GWVXWaEl6lhA==", "cpu": [ "mips64el" ], @@ -263,9 +271,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.0.tgz", - "integrity": "sha512-CoWSaaAXOZd+CjbUTdXIJE/t7Oz+4g90A3VBCHLbfuc5yUQU/nFDLOzQsN0cdxgXd97lYW/psIIBdjzQIwTBGw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.1.tgz", + "integrity": "sha512-b+yuD1IUeL+Y93PmFZDZFIElwbmFfIKLKlYI8M6tRyzE6u7oEP7onGk0vZRh8wfVGC2dZoy0EqX1V8qok4qHaw==", "cpu": [ "ppc64" ], @@ -279,9 +287,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.0.tgz", - "integrity": "sha512-mlb1hg/eYRJUpv8h/x+4ShgoNLL8wgZ64SUr26KwglTYnwAWjkhR2GpoKftDbPOCnodA9t4Y/b68H4J9XmmPzA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.1.tgz", + "integrity": "sha512-wpDlpE0oRKZwX+GfomcALcouqjjV8MIX8DyTrxfyCfXxoKQSDm45CZr9fanJ4F6ckD4yDEPT98SrjvLwIqUCgg==", "cpu": [ "riscv64" ], @@ -295,9 +303,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.0.tgz", - "integrity": "sha512-fgf9ubb53xSnOBqyvWEY6ukBNRl1mVX1srPNu06B6mNsNK20JfH6xV6jECzrQ69/VMiTLvHMicQR/PgTOgqJUQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.1.tgz", + "integrity": "sha512-5BepC2Au80EohQ2dBpyTquqGCES7++p7G+7lXe1bAIvMdXm4YYcEfZtQrP4gaoZ96Wv1Ute61CEHFU7h4FMueQ==", "cpu": [ "s390x" ], @@ -311,9 +319,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.0.tgz", - "integrity": "sha512-H9Eu6MGse++204XZcYsse1yFHmRXEWgadk2N58O/xd50P9EvFMLJTQLg+lB4E1cF2xhLZU5luSWtGTb0l9UeSg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.1.tgz", + "integrity": "sha512-5gRPk7pKuaIB+tmH+yKd2aQTRpqlf1E4f/mC+tawIm/CGJemZcHZpp2ic8oD83nKgUPMEd0fNanrnFljiruuyA==", "cpu": [ "x64" ], @@ -327,9 +335,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.0.tgz", - "integrity": "sha512-lCT675rTN1v8Fo+RGrE5KjSnfY0x9Og4RN7t7lVrN3vMSjy34/+3na0q7RIfWDAj0e0rCh0OL+P88lu3Rt21MQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.1.tgz", + "integrity": "sha512-4fL68JdrLV2nVW2AaWZBv3XEm3Ae3NZn/7qy2KGAt3dexAgSVT+Hc97JKSZnqezgMlv9x6KV0ZkZY7UO5cNLCg==", "cpu": [ "x64" ], @@ -343,9 +351,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.0.tgz", - "integrity": "sha512-HKoUGXz/TOVXKQ+67NhxyHv+aDSZf44QpWLa3I1lLvAwGq8x1k0T+e2HHSRvxWhfJrFxaaqre1+YyzQ99KixoA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.1.tgz", + "integrity": "sha512-GhRuXlvRE+twf2ES+8REbeCb/zeikNqwD3+6S5y5/x+DYbAQUNl0HNBs4RQJqrechS4v4MruEr8ZtAin/hK5iw==", "cpu": [ "x64" ], @@ -359,9 +367,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.0.tgz", - "integrity": "sha512-GDwAqgHQm1mVoPppGsoq4WJwT3vhnz/2N62CzhvApFD1eJyTroob30FPpOZabN+FgCjhG+AgcZyOPIkR8dfD7g==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.1.tgz", + "integrity": "sha512-ZnWEyCM0G1Ex6JtsygvC3KUUrlDXqOihw8RicRuQAzw+c4f1D66YlPNNV3rkjVW90zXVsHwZYWbJh3v+oQFM9Q==", "cpu": [ "x64" ], @@ -375,9 +383,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.0.tgz", - "integrity": "sha512-0vYsP8aC4TvMlOQYozoksiaxjlvUcQrac+muDqj1Fxy6jh9l9CZJzj7zmh8JGfiV49cYLTorFLxg7593pGldwQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.1.tgz", + "integrity": "sha512-QZ6gXue0vVQY2Oon9WyLFCdSuYbXSoxaZrPuJ4c20j6ICedfsDilNPYfHLlMH7vGfU5DQR0czHLmJvH4Nzis/A==", "cpu": [ "arm64" ], @@ -391,9 +399,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.0.tgz", - "integrity": "sha512-p98u4rIgfh4gdpV00IqknBD5pC84LCub+4a3MO+zjqvU5MVXOc3hqR2UgT2jI2nh3h8s9EQxmOsVI3tyzv1iFg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.1.tgz", + "integrity": "sha512-HzcJa1NcSWTAU0MJIxOho8JftNp9YALui3o+Ny7hCh0v5f90nprly1U3Sj1Ldj/CvKKdvvFsCRvDkpsEMp4DNw==", "cpu": [ "ia32" ], @@ -407,9 +415,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.0.tgz", - "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.1.tgz", + "integrity": "sha512-0MBh53o6XtI6ctDnRMeQ+xoCN8kD2qI1rY1KgF/xdWQwoFeKou7puvDfV8/Wv4Ctx2rRpET/gGdz3YlNtNACSA==", "cpu": [ "x64" ], @@ -422,6 +430,40 @@ "node": ">=12" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -454,6 +496,12 @@ "node": ">= 8" } }, + "node_modules/@nuwcdivnpt/stig-manager-client-modules": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@nuwcdivnpt/stig-manager-client-modules/-/stig-manager-client-modules-1.4.1.tgz", + "integrity": "sha512-Zd0l9v4JdjnmCtMGPlj7d3vuA7iwXWW6Cw91pKopC6RG6Pf0uL5HhF2FOKNJUgqa4drrlAl5/lFI8aRYK30cag==", + "hasInstallScript": true + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -488,9 +536,15 @@ } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true }, "node_modules/@types/keyv": { "version": "3.1.4", @@ -501,18 +555,65 @@ } }, "node_modules/@types/node": { - "version": "18.11.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.17.tgz", - "integrity": "sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==" + "version": "20.11.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.22.tgz", + "integrity": "sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dependencies": { "@types/node": "*" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -525,10 +626,25 @@ "node": ">= 8" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, "node_modules/atob": { "version": "2.1.2", @@ -541,6 +657,12 @@ "node": ">= 4.5.0" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, "node_modules/better-queue": { "version": "3.8.12", "resolved": "https://registry.npmjs.org/better-queue/-/better-queue-3.8.12.tgz", @@ -564,6 +686,15 @@ "node": ">=8" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -575,11 +706,42 @@ "node": ">=8" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "node_modules/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -589,9 +751,9 @@ } }, "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -605,16 +767,63 @@ "node": ">=8" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.0.tgz", + "integrity": "sha512-kDZ7MZyM6Q1DhR9jy7dalKohXQ2yrlXkk59CR52aRKxJrobmlBNqnFQxX9xOX8w+4mz8SYlKJa/7D7ddltFXCw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.0.0", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.0.0.tgz", + "integrity": "sha512-tjLAOBHKVxtPoHe/SA7kNOMvhCRdCJ3vETdeY0RuAc9popf+hyaSV6ZEg9hr4cpWF7jmo/JSWEnLDrnijS9Tog==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -627,10 +836,27 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -652,17 +878,21 @@ } }, "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-string": { "version": "1.9.1", @@ -673,6 +903,19 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -690,6 +933,67 @@ "node": ">= 10" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -715,6 +1019,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.1.tgz", + "integrity": "sha512-nwQCf6ne2gez3o1MxWifqkciwt0zhl0LO1/UwVu4uMBuPmflWM4oQ70XMqHqnBJA+nhzncaqL9HVL6KkHJ28lw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -723,6 +1036,15 @@ "node": ">=10" } }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dotenv": { "version": "8.6.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", @@ -739,6 +1061,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", @@ -753,9 +1081,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.0.tgz", - "integrity": "sha512-6iwE3Y2RVYCME1jLpBqq7LQWK3MW6vjV2bZy6gt/WrqkY+WE74Spyc0ThAOYpMtITvnjX09CrC6ym7A/m9mebA==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.1.tgz", + "integrity": "sha512-OJwEgrpWm/PCMsLVWXKqvcjme3bHNpOgN7Tb6cQnR5n0TPbQx1/Xrn7rqM+wn17bYeT6MGB5sn1Bh5YiGi70nA==", "dev": true, "hasInstallScript": true, "bin": { @@ -765,35 +1093,56 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.0", - "@esbuild/android-arm": "0.20.0", - "@esbuild/android-arm64": "0.20.0", - "@esbuild/android-x64": "0.20.0", - "@esbuild/darwin-arm64": "0.20.0", - "@esbuild/darwin-x64": "0.20.0", - "@esbuild/freebsd-arm64": "0.20.0", - "@esbuild/freebsd-x64": "0.20.0", - "@esbuild/linux-arm": "0.20.0", - "@esbuild/linux-arm64": "0.20.0", - "@esbuild/linux-ia32": "0.20.0", - "@esbuild/linux-loong64": "0.20.0", - "@esbuild/linux-mips64el": "0.20.0", - "@esbuild/linux-ppc64": "0.20.0", - "@esbuild/linux-riscv64": "0.20.0", - "@esbuild/linux-s390x": "0.20.0", - "@esbuild/linux-x64": "0.20.0", - "@esbuild/netbsd-x64": "0.20.0", - "@esbuild/openbsd-x64": "0.20.0", - "@esbuild/sunos-x64": "0.20.0", - "@esbuild/win32-arm64": "0.20.0", - "@esbuild/win32-ia32": "0.20.0", - "@esbuild/win32-x64": "0.20.0" + "@esbuild/aix-ppc64": "0.20.1", + "@esbuild/android-arm": "0.20.1", + "@esbuild/android-arm64": "0.20.1", + "@esbuild/android-x64": "0.20.1", + "@esbuild/darwin-arm64": "0.20.1", + "@esbuild/darwin-x64": "0.20.1", + "@esbuild/freebsd-arm64": "0.20.1", + "@esbuild/freebsd-x64": "0.20.1", + "@esbuild/linux-arm": "0.20.1", + "@esbuild/linux-arm64": "0.20.1", + "@esbuild/linux-ia32": "0.20.1", + "@esbuild/linux-loong64": "0.20.1", + "@esbuild/linux-mips64el": "0.20.1", + "@esbuild/linux-ppc64": "0.20.1", + "@esbuild/linux-riscv64": "0.20.1", + "@esbuild/linux-s390x": "0.20.1", + "@esbuild/linux-x64": "0.20.1", + "@esbuild/netbsd-x64": "0.20.1", + "@esbuild/openbsd-x64": "0.20.1", + "@esbuild/sunos-x64": "0.20.1", + "@esbuild/win32-arm64": "0.20.1", + "@esbuild/win32-ia32": "0.20.1", + "@esbuild/win32-x64": "0.20.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -805,31 +1154,10 @@ "node": ">=8.6.0" } }, - "node_modules/fast-xml-parser": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz", - "integrity": "sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", - "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dependencies": { "reusify": "^1.0.4" } @@ -850,11 +1178,89 @@ "node": ">=8" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -869,6 +1275,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -904,14 +1329,30 @@ "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, "bin": { "he": "bin/he" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -929,6 +1370,22 @@ "node": ">=10.19.0" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -958,6 +1415,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -977,6 +1443,15 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -988,20 +1463,92 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/jsonwebtoken": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", - "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", "dependencies": { "jws": "^3.2.2", - "lodash": "^4.17.21", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", "ms": "^2.1.1", - "semver": "^7.3.8" + "semver": "^7.5.4" }, "engines": { "node": ">=12", @@ -1028,9 +1575,9 @@ } }, "node_modules/keyv": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz", - "integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dependencies": { "json-buffer": "3.0.1" } @@ -1040,21 +1587,95 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/logform": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.4.2.tgz", - "integrity": "sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", "fecha": "^4.2.0", "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/loupe": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.0.tgz", + "integrity": "sha512-qKl+FrLXUhFuHUoDJG7f8P8gEMHq9NFS0c6ghXG1J0rldmZFQZoNVv/vyirE9qwCIhWZDsvEFd1sbFu3GvRQFg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" } }, "node_modules/lowercase-keys": { @@ -1076,6 +1697,21 @@ "node": ">=10" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1104,6 +1740,133 @@ "node": ">=4" } }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/mocha/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/mocha/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1122,6 +1885,92 @@ "resolved": "https://registry.npmjs.org/node-eta/-/node-eta-0.9.0.tgz", "integrity": "sha512-mTCTZk29tmX1OGfVkPt63H3c3VqXrI2Kvua98S7iUIB/Gbp0MNw05YtUomxQIxnnKMyRIIuY9izPcFixzhSBrA==" }, + "node_modules/nodemon": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", + "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1165,6 +2014,72 @@ "node": ">=8" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1181,6 +2096,12 @@ "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.1.6.tgz", "integrity": "sha512-dYjDha0af2vefm6soqnPnFEz2tAzwH/kb+pPoaCohRoPUxFXj+mymkOFgxX7Ylv59TdEr7OzktEizdK7MIMvIw==" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1220,10 +2141,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1244,6 +2174,15 @@ "node": ">=8.10.0" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -1311,17 +2250,17 @@ ] }, "node_modules/safe-stable-stringify": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz", - "integrity": "sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", "engines": { "node": ">=10" } }, "node_modules/semver": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", - "integrity": "sha512-SoftuTROv/cRjCze/scjGyiDtcUyxw1rgYQSZY7XTmtR5hX+dm76iDbTH8TkLPHCQmlbQVSSbNZCPM2hb0knnQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -1346,6 +2285,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", @@ -1354,6 +2335,18 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1362,15 +2355,6 @@ "node": "*" } }, - "node_modules/stig-manager-client-modules": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/nuwcdivnpt/stig-manager-client-modules.git#dc3732b2b7e2360813ce057e34a4de040ee26173", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "fast-xml-parser": "^4.3.2" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -1379,10 +2363,111 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, "node_modules/text-hex": { "version": "1.0.0", @@ -1400,10 +2485,25 @@ "node": ">=8.0" } }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/triple-beam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", - "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } }, "node_modules/type-fest": { "version": "0.20.2", @@ -1416,25 +2516,69 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/winston": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", - "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", "dependencies": { - "@colors/colors": "1.5.0", + "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", @@ -1451,16 +2595,39 @@ } }, "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "dependencies": { "logform": "^2.3.2", "readable-stream": "^3.6.0", "triple-beam": "^1.3.0" }, "engines": { - "node": ">= 6.4.0" + "node": ">= 12.0.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/wrappy": { @@ -1468,10 +2635,73 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 97f7979..8511b76 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,10 @@ "bin": { "stigman-watcher": "index.js" }, + "scripts": { + "test": "c8 --reporter=html --reporter=text mocha './test/**/*.test.js'", + "test:watch": "nodemon --ext 'js' --exec 'npm test'" + }, "type": "module", "author": "carl.a.smigielski@saic.com", "license": "MIT", @@ -17,24 +21,26 @@ "node": ">=14" }, "dependencies": { + "@nuwcdivnpt/stig-manager-client-modules": "^1.4.1", "atob": "^2.1.2", "better-queue": "^3.8.10", "chokidar": "^3.5.1", "commander": "^7.2.0", "dotenv": "^8.2.0", "fast-glob": "^3.2.5", - "fast-xml-parser": "^4.0.7", "got": "^11.8.2", - "he": "^1.2.0", "jsonwebtoken": "^9.0.0", "n-readlines": "^1.0.1", "prompt-sync": "4.1.6", "semver": "^7.3.5", "serialize-error": "^8.0.1", - "stig-manager-client-modules": "github:nuwcdivnpt/stig-manager-client-modules#semver:^1.0.0", "winston": "^3.3.3" }, "devDependencies": { - "esbuild": "^0.20.0" + "c8": "^9.1.0", + "chai": "^5.0.3", + "esbuild": "^0.20.0", + "mocha": "^10.2.0", + "nodemon": "^3.0.3" } } diff --git a/test/scan/historyfile.test.js b/test/scan/historyfile.test.js new file mode 100644 index 0000000..28278b2 --- /dev/null +++ b/test/scan/historyfile.test.js @@ -0,0 +1,247 @@ +import { expect } from 'chai' +import fs from 'fs' +import { initHistory, addToHistory, removeFromHistory } from '../../lib/scan.js' +import { logger } from '../../lib/logger.js' +import path from 'path' +import { options } from '../../lib/args.js' + +const emptyFn = () => undefined +for (const method of ['info', 'warn', 'error', 'verbose', 'debug', 'silly', 'http']) { + logger[method] = emptyFn +} + +function setOptions (o) { + for (const [key, value] of Object.entries(o)) { + options[key] = value + } +} + +describe('testing add/remove/init functions ', function () { + const historyFile = './watcher.test.history' + const scannedPath = './test/testFiles/TestScannedDirectory' + + beforeEach(function () { + fs.writeFileSync(historyFile, '') + }) + + afterEach(function () { + fs.unlinkSync(historyFile) + }) + + it('should correctly create an empty history file', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 15000, + oneShot: true, + historyWriteInterval: 10 + }) + initHistory() + expect(fs.existsSync(historyFile)).to.be.true + expect(fs.readFileSync(historyFile, 'utf8')).to.equal('') + }) + + it('should correctly add to history file', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 5000, + oneShot: true, + historyWriteInterval: 10 + }) + + initHistory() + + const file = './test/testFiles/file1.ckl' + + addToHistory(file) + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + const data = fs.readFileSync(historyFile, 'utf8').trim() // Trim the newline character + expect(data).to.equal(file) + }) + + it('should correctly remove from history file', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 5000, + oneShot: true, + historyWriteInterval: 10 + }) + + initHistory(options) + + const file = './test/testFiles/file1.ckl' + + addToHistory(file) + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + + removeFromHistory(file) + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + + const data = fs.readFileSync(historyFile, 'utf8').trim() + + expect(data).to.equal('') + }) +}) + +describe('testing starting with empty history file and adding entries', function () { + const historyFile = './watcher.test.history' + const scannedPath = './test/testFiles/' + const fileContents = '' + beforeEach(function () { + fs.writeFileSync(historyFile, '') + fs.mkdirSync(scannedPath, { recursive: true }) + for (let i = 1; i <= 5; i++) { + fs.writeFileSync(path.join(scannedPath, `file${i}.ckl`), fileContents) + } + }) + + afterEach(function () { + fs.unlinkSync(historyFile) + + const files = fs.readdirSync(scannedPath) + for (const file of files) { + fs.unlinkSync(path.join(scannedPath, file)) + } + fs.rmSync(scannedPath, { recursive: true }) + }) + + it('should correctly identify the 5 new files and update the history file with all 5', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 5000, + oneShot: true, + historyWriteInterval: 10 + }) + + // create history file + initHistory() + + + const files = fs.readdirSync(scannedPath) + for (const file of files) { + addToHistory(path.join(scannedPath, file)) + } + + + await new Promise(resolve => + setTimeout(resolve, options.historyWriteInterval) + ) + + // read the history file + const data = fs.readFileSync(historyFile, 'utf8') + const lines = data.split('\n').filter(line => line.trim() !== '') + + expect(lines.length).to.equal(5) + + const expectedHistoryEntries = [ + 'test/testFiles/file1.ckl', + 'test/testFiles/file2.ckl', + 'test/testFiles/file3.ckl', + 'test/testFiles/file4.ckl', + 'test/testFiles/file5.ckl' + ] + + for (const entry of expectedHistoryEntries) { + expect(lines).to.include(entry) + } + }) +}) + +describe('testing starting with empty history file and slowly adding and removing items to history manually', function () { + this.timeout(5000) + const historyFile = './watcher.test.history' + const scannedPath = './test/testFiles/' + + beforeEach(function () { + fs.writeFileSync(historyFile, '') + }) + + afterEach(function () { + fs.unlinkSync(historyFile) + }) + + it('should correctly remove the 2 files history file skip 2 files already in the history file and scanned directory and add one file to the history file', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 5000, + oneShot: true, + historyWriteInterval: 50 + }) + + // create history file + initHistory() + + addToHistory('./test/testFiles/file1.ckl') + addToHistory('./test/testFiles/file2.ckl') + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + + // read the history file + const data = fs.readFileSync(historyFile, 'utf8') + const lines = data.split('\n').filter(line => line.trim() !== '') + + expect(lines.length).to.equal(2) + expect(lines).to.include('./test/testFiles/file1.ckl') + expect(lines).to.include('./test/testFiles/file2.ckl') + + removeFromHistory('./test/testFiles/file1.ckl') + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + + // read the history file + const data2 = fs.readFileSync(historyFile, 'utf8') + const lines2 = data2.split('\n').filter(line => line.trim() !== '') + + expect(lines2.length).to.equal(1) + expect(lines2).to.include('./test/testFiles/file2.ckl') + }) +}) + +describe('testing no history file mode', function () { + const historyFile = null + const scannedPath = './test/testFiles/' + + it('should correctly run in no history file mode', async function () { + setOptions({ + historyFile: historyFile, + path: scannedPath, + scanInterval: 5000, + oneShot: true, + historyWriteInterval: 10 + }) + + initHistory() + + const file = './test/testFiles/file1.ckl' + + addToHistory(file) + + await new Promise(resolve => setTimeout(resolve, options.historyWriteInterval)) + + // expect no history file to be created + expect(fs.existsSync(historyFile)).to.be.false + }) +}) + +describe('cleaning up', function () { + after(async function () { + setTimeout(() => { + process.exit(0) // Delayed exit to allow Mocha to output results + }, 1000) // Adjust time as necessary for your environment + }) + + it('should clean up the history file', function () { + const historyFilePath = './watcher.test.history' + // Check if the file exists and delete it + if (fs.existsSync(historyFilePath)) { + fs.unlinkSync(historyFilePath) + } + }) +}) From d4ba1e5d3f8e6852cf3a4cc7aa427a952329580a Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 4 Mar 2024 15:35:22 -0500 Subject: [PATCH 03/13] 2024-03-04 session --- index.js | 19 ++++++++++++++-- lib/api.js | 34 +++++++++++++++++++++------- lib/auth.js | 64 +++++++++++++++++++++++++++-------------------------- lib/scan.js | 52 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 127 insertions(+), 42 deletions(-) diff --git a/index.js b/index.js index 1596982..8e3527c 100755 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ import * as api from './lib/api.js' import { serializeError } from 'serialize-error' import { initScanner } from './lib/scan.js' import semverGte from 'semver/functions/gte.js' +import Alarm from './lib/alarm.js' const minApiVersion = '1.2.7' @@ -21,9 +22,23 @@ process.on('SIGINT', () => { message: 'received SIGINT, exiting' }) process.exit(0) -}) +}) -run() +Alarm.on('alarmRaised', (alarmType) => { + logger.error({ + component: 'main', + message: `Alarm raised: ${alarmType}` + }) +}) + +Alarm.on('alarmLowered', (alarmType) => { + logger.error({ + component: 'main', + message: `Alarm lowered: ${alarmType}` + }) +}) + +await run() async function run() { try { diff --git a/lib/api.js b/lib/api.js index 00fd696..7754a4c 100644 --- a/lib/api.js +++ b/lib/api.js @@ -2,6 +2,7 @@ import got from 'got' import { options } from './args.js' import { getToken, tokens } from './auth.js' import { logger, getSymbol } from './logger.js' +import Alarm from './alarm.js' const cache = { collection: null, @@ -15,16 +16,26 @@ const _cache = cache export { _cache as cache } async function apiGet(endpoint, authenticate = true) { - try { - const requestOptions = { - responseType: 'json' - } - if (authenticate) { + const requestOptions = { + responseType: 'json', + timeout: { + request: 5000 + } + } + if (authenticate) { + try { await getToken() - requestOptions.headers = { - Authorization: `Bearer ${tokens.access_token}` - } } + catch (e) { + e.component = 'api' + logError(e) + throw(e) + } + requestOptions.headers = { + Authorization: `Bearer ${tokens.access_token}` + } + } + try { const response = await got.get(`${options.api}${endpoint}`, requestOptions) logResponse (response ) return response.body @@ -32,6 +43,13 @@ async function apiGet(endpoint, authenticate = true) { catch (e) { e.component = 'api' logError(e) + // grant problem + if (e.response?.statusCode === 403) { + Alarm.noGrant(true) + } + else { + Alarm.apiOffline(true) + } throw (e) } } diff --git a/lib/auth.js b/lib/auth.js index dd932e2..0d5a0f2 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -4,11 +4,12 @@ import atob from 'atob' import {options} from './args.js' import jwt from 'jsonwebtoken' import { randomBytes } from 'crypto' +import Alarm from './alarm.js' const self = {} self.url = null -self.threshold = 10 +self.threshold = 10 //seconds! self.scope = 'openid stig-manager:collection stig-manager:stig:read stig-manager:user:read' self.key = options.clientKey self.authenticateFn = options.clientKey ? authenticateSignedJwt : authenticateClientSecret @@ -23,27 +24,31 @@ let tokens, tokenDecoded * @throws {Error} - If there's an error fetching the OpenID configuration. */ async function getOpenIDConfiguration () { + const wellKnownUrl = `${options.authority}/.well-known/openid-configuration` + logger.debug({ + component: 'auth', + message: `sending openId configuration request`, + request: { + method: 'GET', + url: wellKnownUrl + } + }) + let response try { - const wellKnownUrl = `${options.authority}/.well-known/openid-configuration` - logger.debug({ - component: 'auth', - message: `sending openId configuration request`, - request: { - method: 'GET', - url: wellKnownUrl - } - }) - const response = await got.get(wellKnownUrl, { responseType: 'json' }) - logResponse(response) - self.url = response.body.token_endpoint - return response.body + response = await got.get(wellKnownUrl, { responseType: 'json' }) } catch (e) { if (e.response) { logResponse(e.response) } + else { + Alarm.authOffline(true) + } throw e } + logResponse(response) + self.url = response.body.token_endpoint + return response.body } /** @@ -54,32 +59,29 @@ async function getOpenIDConfiguration () { * @throws {Error} If there was an error retrieving the token. */ async function getToken () { + if (tokenDecoded?.exp - Math.ceil(new Date().getTime() / 1000) >= self.threshold) + return tokenDecoded try { - if (tokenDecoded) { - let expiresIn = - tokenDecoded.exp - Math.ceil(new Date().getTime() / 1000) - expiresIn -= self.threshold - if (expiresIn > self.threshold) { - return tokenDecoded - } - } - // getting new token tokens = await self.authenticateFn() - tokenDecoded = decodeToken(tokens.access_token) - logger.debug({ - component: 'auth', - message: `received token response`, - tokens: tokens, - tokenDecoded: tokenDecoded - }) - return tokenDecoded } catch (e) { if (e.response) { logResponse(e.response) + Alarm.noToken(true) + } + else { + Alarm.authOffline(true) } throw e } + tokenDecoded = decodeToken(tokens.access_token) + logger.debug({ + component: 'auth', + message: `received token response`, + tokens, + tokenDecoded + }) + return tokenDecoded } /** diff --git a/lib/scan.js b/lib/scan.js index bde0814..8bf38ea 100644 --- a/lib/scan.js +++ b/lib/scan.js @@ -5,10 +5,12 @@ import { serializeError } from 'serialize-error' import fg from 'fast-glob' import lineByLine from 'n-readlines' import fs from 'node:fs' +import Alarm from './alarm.js' const component = 'scan' const historySet = new Set() // in memory history set let isWriteScheduled = false // flag to indicate if there is pending files to write to the history file +let timeoutId // id of the active setTimeout /** * Utility function that calls initHistory() and startScanner() @@ -16,6 +18,8 @@ let isWriteScheduled = false // flag to indicate if there is pending files to wr function initScanner() { initHistory() startScanner() + Alarm.on('alarmRaised', onAlarmRaised) + Alarm.on('alarmLowered', onAlarmLowered) } /** @@ -79,7 +83,7 @@ function removeStaleFiles(currentFilesSet){ * References options properties {path, scanInterval}. */ function scheduleNextScan() { - setTimeout(() => { + timeoutId = setTimeout(() => { startScanner().catch(e => { logger.error({ component, error: serializeError(e) }) }) @@ -93,6 +97,19 @@ function scheduleNextScan() { }) } +/** + * Cancels the next scan and logs. + * References options properties {path}. + */ +function cancelNextScan() { + clearTimeout(timeoutId) + logger.info({ + component, + message: `scan cancelled`, + path: options.path + }) +} + /** * Returns immediately if options.historyFile is falsy. * Initializes the history Set by reading it from a file and adding each line to the history set. @@ -297,6 +314,39 @@ function isHistoryFileWriteable() { } } +/** + * @typedef {import('./alarm.js').AlarmType} AlarmType + */ + +/** + * Handles raised alarms + * @param {AlarmType} alarmType - The type of alarm. + * Intended to be a callback function of Alarm.on('alarmRaised') + */ +function onAlarmRaised(alarmType) { + logger.verbose({ + component, + message: `handling raised alarm`, + alarmType + }) + cancelNextScan() +} + +/** + * Handles lowered alarms + * @param {AlarmType} alarmType - The type of alarm. + * Intended to be a callback function of Alarm.on('alarmRaised') + */ +function onAlarmLowered(alarmType) { + logger.verbose({ + component, + message: `handling lowered alarm`, + alarmType + }) + startScanner() +} + + export { startScanner, initHistory, From 41128fbafd253796a22ac45916cfa8108de1e6ce Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 11 Mar 2024 10:59:29 -0400 Subject: [PATCH 04/13] squashed merge of main --- README.md | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d8126d0..3f7fd00 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

STIG Manager Watcher

- + A [STIG Manager](https://github.com/nuwcdivnpt/stig-manager) CLI client that watches a path for test result files formatted as CKL or XCCDF and posts the results to a Collection. @@ -28,7 +28,7 @@ C:/> stigman-watcher-win.exe [options] ``` ### Install globally via NPM and run the module ``` -$ npm install --global stigman-watcher +$ npm install --global @nuwcdivnpt/stigman-watcher $ stigman-watcher [options] ``` diff --git a/package.json b/package.json index 2efc2e2..c7ac8a5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "stigman-watcher", + "name": "@nuwcdivnpt/stigman-watcher", "version": "1.4.2", "description": "CLI that watches a path for STIG test result files on behalf of a STIG Manager Collection.", "main": "index.js", @@ -7,7 +7,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/nuwcdivnpt/stigman-watcher.git" + "url": "https://github.com/NUWCDIVNPT/stigman-watcher.git" }, "bin": { "stigman-watcher": "index.js" From f55e3fe31727930deb6baccdb739fff6a4ca6603 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 18 Mar 2024 06:48:37 -0400 Subject: [PATCH 05/13] retry handlers; shutdown event --- index.js | 29 +++++++++++++++++++++++++--- lib/alarm.js | 22 +++++++++++++++++++++- lib/api.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++++-- lib/auth.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++- lib/consts.js | 6 ++++++ lib/scan.js | 5 +++-- 6 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 lib/consts.js diff --git a/index.js b/index.js index 8e3527c..6127dff 100755 --- a/index.js +++ b/index.js @@ -7,12 +7,13 @@ if (!configValid) { process.exit(1) } import startFsEventWatcher from './lib/events.js' -import { getOpenIDConfiguration, getToken } from './lib/auth.js' +import * as auth from './lib/auth.js' import * as api from './lib/api.js' import { serializeError } from 'serialize-error' import { initScanner } from './lib/scan.js' import semverGte from 'semver/functions/gte.js' import Alarm from './lib/alarm.js' +import * as CONSTANTS from './lib/consts.js' const minApiVersion = '1.2.7' @@ -24,6 +25,14 @@ process.on('SIGINT', () => { process.exit(0) }) +Alarm.on('shutdown', (exitCode) => { + logger.info({ + component: 'main', + message: `received shutdown event with code ${exitCode}, exiting` + }) + process.exit(exitCode) +}) + Alarm.on('alarmRaised', (alarmType) => { logger.error({ component: 'main', @@ -50,6 +59,7 @@ async function run() { }) await preflightServices() + setupAlarmHandlers() if (options.mode === 'events') { startFsEventWatcher() } @@ -60,9 +70,22 @@ async function run() { catch (e) { logError(e) logger.end() + process.exitCode = CONSTANTS.ERR_FAILINIT } } +function setupAlarmHandlers() { + const alarmHandlers = { + apiOffline: api.offlineRetryHandler, + authOffline: auth.offlineRetryHandler, + noGrant: () => Alarm.shutdown(CONSTANTS.ERR_NOGRANT), + noToken: () => Alarm.shutdown(CONSTANTS.ERR_NOTOKEN) + } + Alarm.on('alarmRaised', (alarmType) => { + alarmHandlers[alarmType]?.() + }) +} + function logError(e) { const errorObj = { component: e.component || 'main', @@ -100,8 +123,8 @@ async function hasMinApiVersion () { async function preflightServices () { await hasMinApiVersion() - await getOpenIDConfiguration() - await getToken() + await auth.getOpenIDConfiguration() + await auth.getToken() logger.info({ component: 'main', message: `preflight token request suceeded`}) const promises = [ api.getCollection(options.collectionId), diff --git a/lib/alarm.js b/lib/alarm.js index b1599fe..55260fa 100644 --- a/lib/alarm.js +++ b/lib/alarm.js @@ -33,6 +33,10 @@ class Alarm extends EventEmitter { * @param {boolean} state */ #emitAlarmEvent (alarmType, state) { + if (alarmType === 'shutdown') { + this.emit('shutdown', state) + return + } if (state) { this.emit('alarmRaised', alarmType) } @@ -42,41 +46,57 @@ class Alarm extends EventEmitter { } /** - * Sets the state of the apiOffline alarm + * Sets the state of the apiOffline alarm + * and emits an alarmRaised or alarmLowered event * @param {boolean} state */ apiOffline (state) { + if (this.#alarms.apiOffline === state) return this.#alarms.apiOffline = state this.#emitAlarmEvent( 'apiOffline', state) } /** * Sets the state of the authOffline alarm + * and emits an alarmRaised or alarmLowered event * @param {boolean} state */ authOffline (state) { + if (this.#alarms.authOffline === state) return this.#alarms.authOffline = state this.#emitAlarmEvent( 'authOffline', state) } /** * Sets the state of the noToken alarm + * and emits an alarmRaised or alarmLowered event * @param {boolean} state */ noToken (state) { + if (this.#alarms.noToken === state) return this.#alarms.noToken = state this.#emitAlarmEvent( 'noToken', state) } /** * Sets the state of the noGrant alarm + * and emits an alarmRaised or alarmLowered event * @param {boolean} state */ noGrant (state) { + if (this.#alarms.noGrant === state) return this.#alarms.noGrant = state this.#emitAlarmEvent( 'noGrant', state) } + /** + * Emits a shutdown event with the provied exitCode + * @param {number} exitCode + */ + shutdown (exitCode) { + this.#emitAlarmEvent('shutdown', exitCode) + } + /** @type {Alarms} */ get alarms() { return this.#alarms diff --git a/lib/api.js b/lib/api.js index 7754a4c..6e02a8a 100644 --- a/lib/api.js +++ b/lib/api.js @@ -3,6 +3,7 @@ import { options } from './args.js' import { getToken, tokens } from './auth.js' import { logger, getSymbol } from './logger.js' import Alarm from './alarm.js' +import * as CONSTANTS from './consts.js' const cache = { collection: null, @@ -52,7 +53,7 @@ async function apiGet(endpoint, authenticate = true) { } throw (e) } -} +} export async function getScapBenchmarkMap() { const response = await apiGet('/stigs/scap-maps') @@ -60,10 +61,11 @@ export async function getScapBenchmarkMap() { return cache.scapBenchmarkMap } -export async function getDefinition(jsonPath) { +async function getDefinition(jsonPath) { cache.definition = await apiGet(`/op/definition${jsonPath ? '?jsonpath=' + encodeURIComponent(jsonPath) : ''}`, false) return cache.definition } +export {getDefinition} export async function getCollection(collectionId) { cache.collection = await apiGet(`/collections/${collectionId}`) @@ -196,3 +198,49 @@ function logError (e) { }) } +/** + * interval between API connectivity tests when in alarm condition + * @type {number} + */ +const alarmRetryDelay = 5000 +/** + * max number of API connectivity tests when in alarm condition + * @type {number} + */ +const alarmRetryLimit = 5 + +/** + * count of API connectivity tests when in alarm condition + * @type {number} + */ +let alarmRetryCount = 1 + +/** + * Handler for when 'apiOffline' alarm is raised. + * Tests for API connectivity by calling the /op/defintion endpoint + * and increments alarmRetryCount until reaching the alarmRetryLimit + */ +function offlineRetryHandler() { + if (alarmRetryCount >= alarmRetryLimit) { + logger.info({ + conponent: 'api', + message: 'API connectivity maximum tries reached, requesting shutdown' + }) + Alarm.shutdown(CONSTANTS.ERR_APIOFFLINE) + } + logger.info({ + conponent: 'api', + message: 'Testing if API is online' + }) + getDefinition('$.info.version') + .then(() => { + alarmRetryCount = 1 + Alarm.apiOffline(false) + }) + .catch(() => { + alarmRetryCount++ + setTimeout(offlineRetryHandler, alarmRetryDelay) + }) +} +export {offlineRetryHandler} + diff --git a/lib/auth.js b/lib/auth.js index 0d5a0f2..373eb97 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -5,6 +5,7 @@ import {options} from './args.js' import jwt from 'jsonwebtoken' import { randomBytes } from 'crypto' import Alarm from './alarm.js' +import * as CONSTANTS from './consts.js' const self = {} @@ -226,4 +227,49 @@ function logResponse (response) { }) } -export { getToken, getOpenIDConfiguration, tokens } +/** + * interval between IdP connectivity tests when in alarm condition + * @type {number} + */ +const alarmRetryDelay = 5000 +/** + * max number of IdP connectivity tests when in alarm condition + * @type {number} + */ +const alarmRetryLimit = 5 + +/** + * count of IdP connectivity tests when in alarm condition + * @type {number} + */ +let alarmRetryCount = 1 + +/** + * Handler for when 'authiOffline' alarm is raised. + * Tests for IdP connectivity by calling the OIDC metadata endpoint + * and increments alarmRetryCount until reaching the alarmRetryLimit + */ +function offlineRetryHandler() { + if (alarmRetryCount >= alarmRetryLimit) { + logger.info({ + conponent: 'auth', + message: 'IdP connectivity maximum tries reached, requesting shutdown' + }) + Alarm.shutdown(CONSTANTS.ERR_AUTHOFFLINE) + } + logger.info({ + conponent: 'api', + message: 'Testing if API is online' + }) + getOpenIDConfiguration() + .then(() => { + alarmRetryCount = 1 + Alarm.authOffline(false) + }) + .catch(() => { + alarmRetryCount++ + setTimeout(offlineRetryHandler, alarmRetryDelay) + }) +} + +export { getToken, getOpenIDConfiguration, offlineRetryHandler, tokens } diff --git a/lib/consts.js b/lib/consts.js new file mode 100644 index 0000000..b52d3c9 --- /dev/null +++ b/lib/consts.js @@ -0,0 +1,6 @@ +export const ERR_APIOFFLINE = 1 +export const ERR_AUTHOFFLINE = 2 +export const ERR_NOTOKEN = 3 +export const ERR_NOGRANT = 4 +export const ERR_UNKNOWN = 5 +export const ERR_FAILINIT = 6 diff --git a/lib/scan.js b/lib/scan.js index 8bf38ea..0b96eea 100644 --- a/lib/scan.js +++ b/lib/scan.js @@ -148,8 +148,9 @@ function initHistory() { } if (isHistoryFileWriteable()) { - // Handle the interrupt signal + // Handle the interrupt signal and shutdown event process.prependListener('SIGINT', interruptHandler) + Alarm.prependListener('shutdown', interruptHandler) // Set the write interval handler setInterval(writeIntervalHandler, options.historyWriteInterval) logger.verbose({ @@ -175,7 +176,7 @@ function initHistory() { function interruptHandler() { logger.info({ component, - message: `received SIGINT, try writing history to file` + message: `received SIGINT or shutdown event, try writing history to file` }) writeHistoryToFile() } From f642cb8b64b22d86769a69b91a4f648368995b87 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 18 Mar 2024 14:36:10 -0400 Subject: [PATCH 06/13] wip --- index.js | 9 ++++++--- lib/alarm.js | 16 ++++++++++++++++ lib/api.js | 29 +++++++++++++++-------------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 6127dff..2c63983 100755 --- a/index.js +++ b/index.js @@ -26,7 +26,7 @@ process.on('SIGINT', () => { }) Alarm.on('shutdown', (exitCode) => { - logger.info({ + logger.error({ component: 'main', message: `received shutdown event with code ${exitCode}, exiting` }) @@ -41,7 +41,7 @@ Alarm.on('alarmRaised', (alarmType) => { }) Alarm.on('alarmLowered', (alarmType) => { - logger.error({ + logger.info({ component: 'main', message: `Alarm lowered: ${alarmType}` }) @@ -133,7 +133,7 @@ async function preflightServices () { api.getScapBenchmarkMap() ] await Promise.all(promises) - setInterval(refreshCollection, 10 * 60000) + setInterval(refreshCollection, 1 * 60000) // OAuth scope 'stig-manager:user:read' was not required for early versions of Watcher // For now, fail gracefully if we are blocked from calling /user @@ -143,6 +143,7 @@ async function preflightServices () { } catch (e) { logger.warn({ component: 'main', message: `preflight user request failed; token may be missing scope 'stig-manager:user:read'? Watcher will not set {"status": "accepted"}`}) + Alarm.noGrant(false) } logger.info({ component: 'main', message: `prefilght api requests suceeded`}) } @@ -157,6 +158,7 @@ function getObfuscatedConfig (options) { async function refreshUser() { try { + if (Alarm.isAlarmed()) return await api.getUser() } catch (e) { @@ -166,6 +168,7 @@ async function refreshUser() { async function refreshCollection() { try { + if (Alarm.isAlarmed()) return await api.getCollection(options.collectionId) } catch (e) { diff --git a/lib/alarm.js b/lib/alarm.js index 55260fa..46feadc 100644 --- a/lib/alarm.js +++ b/lib/alarm.js @@ -89,6 +89,22 @@ class Alarm extends EventEmitter { this.#emitAlarmEvent( 'noGrant', state) } + /** + * Returns an array of the raised alarm types + * @returns {string[]} + */ + raisedAlarms () { + return Object.keys(this.#alarms).filter(key=>this.#alarms[key]) + } + + /** + * Returns true if any alarm is raised + * @returns {boolean} + */ + isAlarmed () { + return Object.values(this.#alarms).some(value=>value) + } + /** * Emits a shutdown event with the provied exitCode * @param {number} exitCode diff --git a/lib/api.js b/lib/api.js index 6e02a8a..597b3df 100644 --- a/lib/api.js +++ b/lib/api.js @@ -13,8 +13,7 @@ const cache = { scapBenchmarkMap: null, stigs: null } -const _cache = cache -export { _cache as cache } +export {cache} async function apiGet(endpoint, authenticate = true) { const requestOptions = { @@ -213,7 +212,7 @@ const alarmRetryLimit = 5 * count of API connectivity tests when in alarm condition * @type {number} */ -let alarmRetryCount = 1 +let alarmRetryCount = 0 /** * Handler for when 'apiOffline' alarm is raised. @@ -221,25 +220,27 @@ let alarmRetryCount = 1 * and increments alarmRetryCount until reaching the alarmRetryLimit */ function offlineRetryHandler() { - if (alarmRetryCount >= alarmRetryLimit) { - logger.info({ - conponent: 'api', - message: 'API connectivity maximum tries reached, requesting shutdown' - }) - Alarm.shutdown(CONSTANTS.ERR_APIOFFLINE) - } logger.info({ - conponent: 'api', + component: 'api', message: 'Testing if API is online' }) + alarmRetryCount++ getDefinition('$.info.version') .then(() => { - alarmRetryCount = 1 + alarmRetryCount = 0 Alarm.apiOffline(false) }) .catch(() => { - alarmRetryCount++ - setTimeout(offlineRetryHandler, alarmRetryDelay) + if (alarmRetryCount >= alarmRetryLimit) { + logger.info({ + component: 'api', + message: 'API connectivity maximum tries reached, requesting shutdown' + }) + Alarm.shutdown(CONSTANTS.ERR_APIOFFLINE) + } + else { + setTimeout(offlineRetryHandler, alarmRetryDelay) + } }) } export {offlineRetryHandler} From c8fb06f5f4111c26f09b6d872000b06a445057ca Mon Sep 17 00:00:00 2001 From: csmig Date: Wed, 20 Mar 2024 19:04:59 -0400 Subject: [PATCH 07/13] nightly --- index.js | 31 +++++++++----- lib/api.js | 113 ++++++++++++++++++++------------------------------ lib/auth.js | 18 ++++++-- lib/consts.js | 1 + 4 files changed, 82 insertions(+), 81 deletions(-) diff --git a/index.js b/index.js index 2c63983..d204626 100755 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ import { logger, getSymbol } from './lib/logger.js' import { options, configValid } from './lib/args.js' if (!configValid) { - logger.error({ component: 'main', message: 'invalid configuration... Exiting'}) + logger.error({ component, message: 'invalid configuration... Exiting'}) logger.end() process.exit(1) } @@ -16,10 +16,11 @@ import Alarm from './lib/alarm.js' import * as CONSTANTS from './lib/consts.js' const minApiVersion = '1.2.7' +const component = 'index' process.on('SIGINT', () => { logger.info({ - component: 'main', + component, message: 'received SIGINT, exiting' }) process.exit(0) @@ -27,7 +28,7 @@ process.on('SIGINT', () => { Alarm.on('shutdown', (exitCode) => { logger.error({ - component: 'main', + component, message: `received shutdown event with code ${exitCode}, exiting` }) process.exit(exitCode) @@ -35,14 +36,14 @@ Alarm.on('shutdown', (exitCode) => { Alarm.on('alarmRaised', (alarmType) => { logger.error({ - component: 'main', + component, message: `Alarm raised: ${alarmType}` }) }) Alarm.on('alarmLowered', (alarmType) => { logger.info({ - component: 'main', + component, message: `Alarm lowered: ${alarmType}` }) }) @@ -52,7 +53,7 @@ await run() async function run() { try { logger.info({ - component: 'main', + component, message: 'running', pid: process.pid, options: getObfuscatedConfig(options) @@ -88,7 +89,7 @@ function setupAlarmHandlers() { function logError(e) { const errorObj = { - component: e.component || 'main', + component: e.component || 'index', message: e.message, } if (e.request) { @@ -112,7 +113,7 @@ function logError(e) { async function hasMinApiVersion () { const [remoteApiVersion] = await api.getDefinition('$.info.version') - logger.info({ component: 'main', message: `preflight API version`, minApiVersion, remoteApiVersion}) + logger.info({ component, message: `preflight API version`, minApiVersion, remoteApiVersion}) if (semverGte(remoteApiVersion, minApiVersion)) { return true } @@ -125,7 +126,7 @@ async function preflightServices () { await hasMinApiVersion() await auth.getOpenIDConfiguration() await auth.getToken() - logger.info({ component: 'main', message: `preflight token request suceeded`}) + logger.info({ component, message: `preflight token request suceeded`}) const promises = [ api.getCollection(options.collectionId), api.getCollectionAssets(options.collectionId), @@ -142,10 +143,10 @@ async function preflightServices () { setInterval(refreshUser, 10 * 60000) } catch (e) { - logger.warn({ component: 'main', message: `preflight user request failed; token may be missing scope 'stig-manager:user:read'? Watcher will not set {"status": "accepted"}`}) + logger.warn({ component, message: `preflight user request failed; token may be missing scope 'stig-manager:user:read'? Watcher will not set {"status": "accepted"}`}) Alarm.noGrant(false) } - logger.info({ component: 'main', message: `prefilght api requests suceeded`}) + logger.info({ component, message: `prefilght api requests suceeded`}) } function getObfuscatedConfig (options) { @@ -159,6 +160,10 @@ function getObfuscatedConfig (options) { async function refreshUser() { try { if (Alarm.isAlarmed()) return + logger.info({ + component, + message: 'refreshing user cache' + }) await api.getUser() } catch (e) { @@ -169,6 +174,10 @@ async function refreshUser() { async function refreshCollection() { try { if (Alarm.isAlarmed()) return + logger.info({ + component, + message: 'refreshing collection cache' + }) await api.getCollection(options.collectionId) } catch (e) { diff --git a/lib/api.js b/lib/api.js index 597b3df..b01c08c 100644 --- a/lib/api.js +++ b/lib/api.js @@ -14,14 +14,16 @@ const cache = { stigs: null } export {cache} - -async function apiGet(endpoint, authenticate = true) { +async function apiRequest({method = 'GET', endpoint, json, authenticate = true, fullResponse = false}) { const requestOptions = { + method, + url: `${options.api}${endpoint}`, responseType: 'json', timeout: { - request: 5000 + request: CONSTANTS.REQUEST_TIMEOUT } } + if (authenticate) { try { await getToken() @@ -35,12 +37,20 @@ async function apiGet(endpoint, authenticate = true) { Authorization: `Bearer ${tokens.access_token}` } } + + if (json) requestOptions.json = json + try { - const response = await got.get(`${options.api}${endpoint}`, requestOptions) + const response = await got(requestOptions) logResponse (response ) - return response.body + return fullResponse ? response : response.body } catch (e) { + // accept a client error for POST /assets if it reports a duplicate name + if (e.response?.statusCode === 400 && e.response?.body?.message === 'Duplicate name') { + logResponse(e.response) + return fullResponse ? e?.response : e?.response?.body + } e.component = 'api' logError(e) // grant problem @@ -55,95 +65,64 @@ async function apiGet(endpoint, authenticate = true) { } export async function getScapBenchmarkMap() { - const response = await apiGet('/stigs/scap-maps') - cache.scapBenchmarkMap = new Map(response.map(apiScapMap => [apiScapMap.scapBenchmarkId, apiScapMap.benchmarkId])) + const body = await apiRequest({endpoint: '/stigs/scap-maps'}) + cache.scapBenchmarkMap = new Map(body.map(apiScapMap => [apiScapMap.scapBenchmarkId, apiScapMap.benchmarkId])) return cache.scapBenchmarkMap } async function getDefinition(jsonPath) { - cache.definition = await apiGet(`/op/definition${jsonPath ? '?jsonpath=' + encodeURIComponent(jsonPath) : ''}`, false) + cache.definition = await apiRequest({ + endpoint: `/op/definition${jsonPath ? '?jsonpath=' + encodeURIComponent(jsonPath) : ''}`, + authenticate: false + }) return cache.definition } export {getDefinition} export async function getCollection(collectionId) { - cache.collection = await apiGet(`/collections/${collectionId}`) + cache.collection = await apiRequest({endpoint: `/collections/${collectionId}`}) return cache.collection } export async function getCollectionAssets(collectionId) { - cache.assets = await apiGet(`/assets?collectionId=${collectionId}&projection=stigs`) + cache.assets = await apiRequest({endpoint: `/assets?collectionId=${collectionId}&projection=stigs`}) return cache.assets } export async function getInstalledStigs() { - cache.stigs = await apiGet('/stigs') + cache.stigs = await apiRequest({endpoint: '/stigs'}) return cache.stigs } export async function createOrGetAsset(asset) { - try { - await getToken() - const response = await got.post(`${options.api}/assets?projection=stigs`, { - headers: { - Authorization: `Bearer ${tokens.access_token}` - }, - json: asset, - responseType: 'json' - }) - logResponse(response) - return { created: true, apiAsset: response.body } - } - catch (e) { - if (e.response.statusCode === 400 && e.response.body.message === 'Duplicate name') { - logResponse(e.response) - return { created: false, apiAsset: e.response.body.data } - } - e.component = 'api' - throw (e) - } + const response = await apiRequest({ + method: 'POST', + endpoint: '/assets?projection=stigs', + json: asset, + fullResponse: true + }) + const created = response.statusCode === 201 + return { created, apiAsset: response.body } } -export async function patchAsset(assetId, body) { - try { - await getToken() - const response = await got.patch(`${options.api}/assets/${assetId}?projection=stigs`, { - headers: { - Authorization: `Bearer ${tokens.access_token}` - }, - json: body, - responseType: 'json' - }) - logResponse(response) - return response.body - } - catch (e) { - e.component = 'api' - throw (e) - } +export function patchAsset(assetId, body) { + return apiRequest({ + method: 'PATCH', + endpoint: `/assets/${assetId}?projection=stigs`, + json: body + }) } -export async function postReviews(collectionId, assetId, reviews) { - try { - await getToken() - const response = await got.post(`${options.api}/collections/${collectionId}/reviews/${assetId}`, { - headers: { - Authorization: `Bearer ${tokens.access_token}` - }, - json: reviews, - responseType: 'json' - }) - logResponse(response) - return response.body - } - catch (e) { - e.component = 'api' - throw (e) - } +export function postReviews(collectionId, assetId, reviews) { + return apiRequest({ + method: 'POST', + endpoint: `/collections/${collectionId}/reviews/${assetId}`, + json: reviews + }) } export async function getUser() { - cache.user = await apiGet('/user') + cache.user = await apiRequest({endpoint: '/user'}) return cache.user } @@ -185,7 +164,7 @@ function logResponse (response) { function logError (e) { logger.error({ component: 'api', - message: 'query error', + message: e.message, request: { method: e.request?.options?.method, url: e.request?.requestUrl diff --git a/lib/auth.js b/lib/auth.js index 373eb97..fd58e9a 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -34,9 +34,15 @@ async function getOpenIDConfiguration () { url: wellKnownUrl } }) + const requestOptions = { + responseType: 'json', + timeout: { + request: CONSTANTS.REQUEST_TIMEOUT + } + } let response try { - response = await got.get(wellKnownUrl, { responseType: 'json' }) + response = await got.get(wellKnownUrl, requestOptions) } catch (e) { if (e.response) { @@ -100,7 +106,10 @@ async function authenticateClientSecret () { username: options.clientId, password: options.clientSecret, scope: self.scope, - responseType: 'json' + responseType: 'json', + timeout: { + request: CONSTANTS.REQUEST_TIMEOUT + } } logger.debug({ @@ -155,7 +164,10 @@ async function authenticateSignedJwt () { client_assertion: signedJwt, scope: self.scope }, - responseType: 'json' + responseType: 'json', + timeout: { + request: CONSTANTS.REQUEST_TIMEOUT + } } logger.debug({ message: 'sending signed JWT authentication request', diff --git a/lib/consts.js b/lib/consts.js index b52d3c9..a584ca2 100644 --- a/lib/consts.js +++ b/lib/consts.js @@ -4,3 +4,4 @@ export const ERR_NOTOKEN = 3 export const ERR_NOGRANT = 4 export const ERR_UNKNOWN = 5 export const ERR_FAILINIT = 6 +export const REQUEST_TIMEOUT = 5000 From b189da1b3337b761601b2031bb6ea886cfb8837e Mon Sep 17 00:00:00 2001 From: csmig Date: Sat, 23 Mar 2024 09:13:43 -0400 Subject: [PATCH 08/13] refactor api.js --- index.js | 2 +- lib/api.js | 24 +++++++++++++++++++----- lib/auth.js | 35 ++++++++++++++++++----------------- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index d204626..f508bb4 100755 --- a/index.js +++ b/index.js @@ -134,7 +134,7 @@ async function preflightServices () { api.getScapBenchmarkMap() ] await Promise.all(promises) - setInterval(refreshCollection, 1 * 60000) + setInterval(refreshCollection, 1 * 60000) // todo: change back to 10 // OAuth scope 'stig-manager:user:read' was not required for early versions of Watcher // For now, fail gracefully if we are blocked from calling /user diff --git a/lib/api.js b/lib/api.js index b01c08c..f663bdc 100644 --- a/lib/api.js +++ b/lib/api.js @@ -14,7 +14,21 @@ const cache = { stigs: null } export {cache} -async function apiRequest({method = 'GET', endpoint, json, authenticate = true, fullResponse = false}) { + +/** + * Sends requests to the STIG Manager API and returns the response + * @async + * @function apiRequest + * @param {Object} options + * @property {'GET'|'POST'|'PATCH'} [options.method='GET'] optional, HTTP method + * @property {string} options.endpoint required, API endpoint + * @property {Object=} options.json optional, object to be stringified into request body + * @property {boolean} [options.authorize=true] optional, whether the request should be authorized with a JWT + * @property {boolean} [options.fullResponse=false] optional, whether to return the response body or the got response object + * @returns {Promise} the response body as a JS object or the got response object + * @throws {Error} If there was an error making the request + */ +async function apiRequest({method = 'GET', endpoint, json, authorize = true, fullResponse = false}) { const requestOptions = { method, url: `${options.api}${endpoint}`, @@ -24,7 +38,7 @@ async function apiRequest({method = 'GET', endpoint, json, authenticate = true, } } - if (authenticate) { + if (authorize) { try { await getToken() } @@ -73,7 +87,7 @@ export async function getScapBenchmarkMap() { async function getDefinition(jsonPath) { cache.definition = await apiRequest({ endpoint: `/op/definition${jsonPath ? '?jsonpath=' + encodeURIComponent(jsonPath) : ''}`, - authenticate: false + authorize: false }) return cache.definition } @@ -105,11 +119,11 @@ export async function createOrGetAsset(asset) { return { created, apiAsset: response.body } } -export function patchAsset(assetId, body) { +export function patchAsset(assetId, assetProperties) { return apiRequest({ method: 'PATCH', endpoint: `/assets/${assetId}?projection=stigs`, - json: body + json: assetProperties }) } diff --git a/lib/auth.js b/lib/auth.js index fd58e9a..fb26872 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -10,13 +10,14 @@ import * as CONSTANTS from './consts.js' const self = {} self.url = null -self.threshold = 10 //seconds! +self.threshold = 10 // seconds self.scope = 'openid stig-manager:collection stig-manager:stig:read stig-manager:user:read' self.key = options.clientKey self.authenticateFn = options.clientKey ? authenticateSignedJwt : authenticateClientSecret self.authentication = options.clientKey ? 'signed-jwt' : 'client-secret' let tokens, tokenDecoded +const component = 'auth' /** * Fetches OpenID configuration from the specified authority URL. * @async @@ -27,7 +28,7 @@ let tokens, tokenDecoded async function getOpenIDConfiguration () { const wellKnownUrl = `${options.authority}/.well-known/openid-configuration` logger.debug({ - component: 'auth', + component, message: `sending openId configuration request`, request: { method: 'GET', @@ -83,7 +84,7 @@ async function getToken () { } tokenDecoded = decodeToken(tokens.access_token) logger.debug({ - component: 'auth', + component, message: `received token response`, tokens, tokenDecoded @@ -99,13 +100,13 @@ async function getToken () { * @returns {Promise} The response from auth provider. */ async function authenticateClientSecret () { - const parameters = { + const requestOptions = { form: { - grant_type: 'client_credentials' + grant_type: 'client_credentials', + scope: self.scope }, username: options.clientId, password: options.clientSecret, - scope: self.scope, responseType: 'json', timeout: { request: CONSTANTS.REQUEST_TIMEOUT @@ -113,16 +114,16 @@ async function authenticateClientSecret () { } logger.debug({ - component: 'auth', + component, message: 'sending client secret authentication request', request: { method: 'POST', url: self.url, - parameters + requestOptions } }) - const response = await got.post(self.url, parameters) + const response = await got.post(self.url, requestOptions) logResponse(response) return response.body } @@ -156,7 +157,7 @@ async function authenticateSignedJwt () { signedJwt }) - const parameters = { + const requestOptions = { form: { grant_type: 'client_credentials', client_assertion_type: @@ -175,11 +176,11 @@ async function authenticateSignedJwt () { request: { method: 'POST', url: self.url, - parameters + requestOptions } }) - const response = await got.post(self.url, parameters) + const response = await got.post(self.url, requestOptions) logResponse(response) return response.body } @@ -225,7 +226,7 @@ function decodeToken (str) { */ function logResponse (response) { logger.http({ - component: 'auth', + component, message: 'http response', request: { method: response.request.options?.method, @@ -264,14 +265,14 @@ let alarmRetryCount = 1 function offlineRetryHandler() { if (alarmRetryCount >= alarmRetryLimit) { logger.info({ - conponent: 'auth', - message: 'IdP connectivity maximum tries reached, requesting shutdown' + component, + message: 'OIDC Provider connectivity maximum tries reached, requesting shutdown' }) Alarm.shutdown(CONSTANTS.ERR_AUTHOFFLINE) } logger.info({ - conponent: 'api', - message: 'Testing if API is online' + component, + message: 'Testing if OIDC Provider is online' }) getOpenIDConfiguration() .then(() => { From 84e7b0686ee62391cea9778478fb4614fce4b866 Mon Sep 17 00:00:00 2001 From: csmig Date: Sat, 23 Mar 2024 18:38:47 -0400 Subject: [PATCH 09/13] event mode alarm handling --- lib/events.js | 69 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/lib/events.js b/lib/events.js index 08cdcb8..ae7e18c 100644 --- a/lib/events.js +++ b/lib/events.js @@ -3,45 +3,72 @@ import { logger } from './logger.js' import { queue } from './parse.js' import { serializeError } from 'serialize-error' import { watch } from 'chokidar' +import Alarm from './alarm.js' const component = 'events' -export default function startFsEventWatcher () { - const awaitWriteFinishVal = options.stabilityThreshold ? { stabilityThreshold: options.stabilityThreshold } : false + +function startFsEventWatcher () { + const awaitWriteFinish = options.stabilityThreshold ? { stabilityThreshold: options.stabilityThreshold } : false const ignored = options.ignoreGlob ?? [] if (options.ignoreDot) ignored.push(/(^|[\/\\])\../) + const watcher = watch(options.path, { ignored, ignoreInitial: !options.addExisting, persistent: true, usePolling: options.usePolling, - awaitWriteFinish: awaitWriteFinishVal + awaitWriteFinish }) logger.info({component: component, message: `watching`, path: options.path}) - watcher.on('ready', e => { + watcher.on('ready', () => { if (options.oneShot) { watcher.close() } }) - - watcher.on('error', e => { - logger.error({ + watcher.on('error', onError ) + watcher.on('add', onAdd ) + Alarm.on('alarmRaised', onAlarmRaised) + Alarm.on('alarmLowered', onAlarmLowered) +} + +function onAdd (file) { + // chokidar glob argument doesn't work for UNC Windows, so we check file extension here + const extension = file.substring(file.lastIndexOf(".") + 1) + if (extension.toLowerCase() === 'ckl' || extension.toLowerCase() === 'xml' || extension.toLowerCase() === 'cklb') { + logger.info({ component: component, - error: serializeError(e) + message: 'file system event', + event: 'add', + file }) + queue.push( file ) + } +} + +function onError (e) { + logger.error({ + component: component, + error: serializeError(e) }) - - watcher.on('add', file => { - // chokidar glob argument doesn't work for UNC Windows, so we check file extension here - const extension = file.substring(file.lastIndexOf(".") + 1) - if (extension.toLowerCase() === 'ckl' || extension.toLowerCase() === 'xml' || extension.toLowerCase() === 'cklb') { - logger.info({ - component: component, - message: 'file system event', - event: 'add', - file: file - }) - queue.push( file ) - } +} + +function onAlarmRaised (alarmType) { + logger.info({ + component, + message: `pausing parse queue on alarm raised`, + alarmType }) + queue.pause() } + +function onAlarmLowered (alarmType) { + logger.info({ + component, + message: `resuming parse queue on alarm lowered`, + alarmType + }) + queue.resume() +} + +export default startFsEventWatcher \ No newline at end of file From 6406ac80817ac7172ba3cd98d9c7ac75927b6850 Mon Sep 17 00:00:00 2001 From: csmig Date: Sat, 23 Mar 2024 18:39:30 -0400 Subject: [PATCH 10/13] do not log tokens --- lib/auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/auth.js b/lib/auth.js index fb26872..140a59f 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -235,7 +235,7 @@ function logResponse (response) { }, response: { status: response.statusCode, - body: response.body + body: {...response.body, access_token: true, id_token: true} } }) } From 35f2f1289cc3882f643d50d24295a54e19b5c8e2 Mon Sep 17 00:00:00 2001 From: csmig Date: Sat, 23 Mar 2024 18:40:03 -0400 Subject: [PATCH 11/13] add oneShot envvar --- lib/args.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/args.js b/lib/args.js index 6ea8d27..3a6dc15 100644 --- a/lib/args.js +++ b/lib/args.js @@ -101,7 +101,7 @@ program .option('--event-polling', 'Use polling with `--mode events`, necessary for watching network files (`WATCHER_EVENT_POLLING=1`). Ignored if `--mode scan`, negate with `--no-event-polling`.', getBoolean('WATCHER_EVENT_POLLING', true)) .option('--no-event-polling', 'Don\'t use polling with `--mode events`, reduces CPU usage (`WATCHER_EVENT_POLLING=0`).') .option('--stability-threshold ', 'If `--mode events`, milliseconds to wait for file size to stabilize. May be helpful when watching network shares. (`WATCHER_STABILITY_THRESHOLD`). Igonred with `--mode scan`', parseIntegerArg, parseIntegerEnv(pe.WATCHER_STABILITY_THRESHOLD) ?? 0) -.option('--one-shot', 'Process existing files in the path and exit. Sets `--add-existing`.', false) +.option('--one-shot', 'Process existing files in the path and exit. Sets `--add-existing`.', getBoolean('WATCHER_ONE_SHOT', false)) .option('--log-color', 'Colorize the console log output. Might confound downstream piped processes.', false) .option('-d, --debug', 'Shortcut for `--log-level debug --log-file-level debug`', false) .option('--scan-interval ', 'If `--mode scan`, the interval between scans. Ignored if `--mode events`.', parseIntegerArg, parseIntegerEnv(pe.WATCHER_SCAN_INTERVAL) ?? 300000) From ee822aeff38d9e8611c5267fa7c38436b0e74735 Mon Sep 17 00:00:00 2001 From: csmig Date: Mon, 25 Mar 2024 13:37:52 -0400 Subject: [PATCH 12/13] cleanup events.js --- lib/events.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/events.js b/lib/events.js index ae7e18c..8508565 100644 --- a/lib/events.js +++ b/lib/events.js @@ -10,7 +10,7 @@ const component = 'events' function startFsEventWatcher () { const awaitWriteFinish = options.stabilityThreshold ? { stabilityThreshold: options.stabilityThreshold } : false const ignored = options.ignoreGlob ?? [] - if (options.ignoreDot) ignored.push(/(^|[\/\\])\../) + if (options.ignoreDot) ignored.push(/(^|[/\\])\../) const watcher = watch(options.path, { ignored, @@ -19,7 +19,7 @@ function startFsEventWatcher () { usePolling: options.usePolling, awaitWriteFinish }) - logger.info({component: component, message: `watching`, path: options.path}) + logger.info({component, message: `watching`, path: options.path}) watcher.on('ready', () => { if (options.oneShot) { @@ -37,7 +37,7 @@ function onAdd (file) { const extension = file.substring(file.lastIndexOf(".") + 1) if (extension.toLowerCase() === 'ckl' || extension.toLowerCase() === 'xml' || extension.toLowerCase() === 'cklb') { logger.info({ - component: component, + component, message: 'file system event', event: 'add', file @@ -48,7 +48,7 @@ function onAdd (file) { function onError (e) { logger.error({ - component: component, + component, error: serializeError(e) }) } From b4f30cc87910f07317b0596ebf7768f630316d64 Mon Sep 17 00:00:00 2001 From: csmig Date: Tue, 26 Mar 2024 12:35:58 -0400 Subject: [PATCH 13/13] revert refreshCollection interval to 10 minutes --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index f508bb4..0ede6f5 100755 --- a/index.js +++ b/index.js @@ -134,7 +134,7 @@ async function preflightServices () { api.getScapBenchmarkMap() ] await Promise.all(promises) - setInterval(refreshCollection, 1 * 60000) // todo: change back to 10 + setInterval(refreshCollection, 10 * 60000) // OAuth scope 'stig-manager:user:read' was not required for early versions of Watcher // For now, fail gracefully if we are blocked from calling /user